這篇文章帶給大家的內容是關於全面解析Java String類別的內容(附程式碼),有一定的參考價值,有需要的朋友可以參考一下,希望對你有幫助。
開始寫 Java 一年來,一直都是遇到什麼問題再去解決,還沒有主動的深入的去學習過 Java 語言的特性和深入閱讀 JDK 的源碼。既然決定今後靠 Java
吃飯,還是得花些心思在上面,放棄一些打遊戲的時間,系統深入的去學習。
Java String 是 Java 程式設計中最常用的類別之一,也是 JDK 提供的最基礎的類別。所以我決定先從 String 類別入手,深入的研究一番來開個好頭。
類別定義與類別成員
開啟 JDK 中的 String 原始碼,最先應關注 String 類別的定義。
public final class String implements java.io.Serializable, Comparable<string>, CharSequence</string>
不可繼承與不可變
寫過 Java 的人都知道, 當 final 關鍵字修飾類別時,代表這類不可繼承。所以 String 類別是不能被外部繼承。這時候我們可能會好奇,String 的設計者
為什麼要把它設計成不可繼承的。我在知乎上找到了相關的問題和討論,
我覺得首位的回答已經說的很明白了。 String 做為 Java 的最基礎的引用資料類型,最重要的一點就是不可變性,所以使用 final 就是為了**禁止繼承
破壞了 String 的不可變的性質**。
實作類別的不可變性,不光是用final 修飾類別這麼簡單,從原始碼可以看到,String 其實是對一個字元陣列的封裝,而字元陣列是私有的,並且沒有提供
任何可以修改字元陣列的方法,所以一旦初始化完成, String 物件就無法被修改。
序列化
從上面的類別定義我們看到了 String 實作了序列化的介面 Serializable,所以 String 是支援序列化和反序列化的。
什麼是Java物件的序列化?相信很多跟我一樣的 Java 菜鳥都有這樣疑問。深入分析Java的序列化與反序列化這篇文章中的這段話
解釋的很好。
只有當JVM處於執行時,這些物件才可能存在,
即,這些物件的生命週期不會比JVM的生命週期更長。但在現實應用中,
就可能要求在JVM停止運行之後能夠保存(持久化)指定的對象,並在將來重新讀取被保存的對象。
Java物件序列化就能夠幫助我們實現該功能。
使用Java物件序列化,在儲存物件時,會把其狀態儲存為一組位元組,在未來,再將這些位元組組裝成物件。
必須注意地是,物件序列化保存的是物件的”狀態”,即它的成員變數。由此可知,物件序列化不會關注類別中的靜態變數。
除了在持久化物件時會用到物件序列化之外,當使用RMI(遠端方法呼叫),或在網路中傳遞物件時,都會用到物件序列化。
Java序列化API為處理物件序列化提供了一個標準機制,該API簡單易用。
在 String 原始碼中,我們也可以看到支援序列化的類別成員定義。
/** use serialVersionUID from JDK 1.0.2 for interoperability */ private static final long serialVersionUID = -6849794470754667710L; /** * Class String is special cased within the Serialization Stream Protocol. * * A String instance is written into an ObjectOutputStream according to * <a> * Object Serialization Specification, Section 6.2, "Stream Elements"</a> */ private static final ObjectStreamField[] serialPersistentFields = new ObjectStreamField[0];
serialVersionUID 是一個序列化版本號,Java 透過這個UID 來判定反序列化時的位元組流與本地類別的一致性,如果相同則認為一致,
可以進行反序列化,如果不同就會拋出異常。
serialPersistentFields 這個定義則比上一個少見許多,大概猜到是與序列化時的類別成員有關係。為了弄清楚這個欄位的意義,我google 百度齊上,也
僅僅只找到了JDK 文件對類別ObjectStreamField的一丁點描述, `A description of a Serializable field from a Serializable class.
An array of ObjectStreamFields is used to declare the Serializable fields of a class.` 大意是這個類別用來描述序列化類別的一個序列化字段,
如果定義一個此類的數組則可以聲明類別需要被序列化的字段。但是還是沒有找到這個類別的具體用法和作用是怎樣的。後來我仔細看了一下這個字段的定義,
與 serialVersionUID 應該是同樣通過具體字段名來定義各種規則的,然後我直接搜索了關鍵字 serialPersistentFields,終於找到了它的具體作用。
即,**預設序列化自訂包括關鍵字 transient 和靜態欄位名稱 serialPersistentFields,transient 用於指定哪個欄位不被預設序列化,
serialPersistentFields 用於指定哪些欄位需要被預設序列化。如果同時定義了 serialPersistentFields 與 transient,transient 會被忽略。 **
我自己也測試了一下,確實是這個效果。
知道了 serialPersistentFields 的作用以后,问题又来了,既然这个静态字段是用来定义参与序列化的类成员的,那为什么在 String 中这个数组的长度定义为0?
经过一番搜索查找资料以后,还是没有找到一个明确的解释,期待如果有大佬看到能解答一下。
可排序
String 类还实现了 Comparable 接口,Comparable
即可用 Collections.sort 或 Arrays.sort 等方法对该类的对象列表或数组进行排序。
在 String 中我们还可以看到这样一个静态变量,
public static final Comparator<string> CASE_INSENSITIVE_ORDER = new CaseInsensitiveComparator(); private static class CaseInsensitiveComparator implements Comparator<string>, java.io.Serializable { // use serialVersionUID from JDK 1.2.2 for interoperability private static final long serialVersionUID = 8575799808933029326L; public int compare(String s1, String s2) { int n1 = s1.length(); int n2 = s2.length(); int min = Math.min(n1, n2); for (int i = 0; i <p>从上面的源码中可以看出,这个静态成员是一个实现了 Comparator 接口的类的实例,而实现这个类的作用是比较两个忽略大小写的 String 的大小。</p> <p>那么 Comparable 和 Comparator 有什么区别和联系呢?同时 String 又为什么要两个都实现一遍呢?</p> <p>第一个问题这里就不展开了,总结一下就是,Comparable 是类的内部实现,一个类能且只能实现一次,而 Comparator 则是外部实现,可以通过不改变<br>类本身的情况下,为类增加更多的排序功能。<br>所以我们也可以为 String 实现一个 Comparator使用。</p> <p>String 实现了两种比较方法的意图,实际上是一目了然的。实现 Comparable 接口为类提供了标准的排序方案,同时为了满足大多数排序需求的忽略大小写排序的情况,<br>String 再提供一个 Comparator 到公共静态类成员中。如果还有其他的需求,那就只能我们自己实现了。</p> <h2 id="类方法">类方法</h2> <p>String 的方法大致可以分为以下几类。</p> <ul class=" list-paddingleft-2"> <li><p>构造方法</p></li> <li><p>功能方法</p></li> <li><p>工厂方法</p></li> <li><p>intern方法</p></li> </ul> <p>关于 String 的方法的解析,这篇文章已经解析的够好了,所以我这里也不再重复的说一遍了。不过<br>最后的 intern 方法值得我们去研究。</p> <p>intern方法</p> <p>字符串常量池</p> <p>String 做为 Java 的基础类型之一,可以使用字面量的形式去创建对象,例如 String s = "hello"。当然也可以使用 new 去创建 String 的对象,<br>但是几乎很少看到这样的写法,久而久之我便习惯了第一种写法,但是却不知道背后大有学问。下面一段代码可以看出他们的区别。</p> <pre class="brush:php;toolbar:false">public class StringConstPool { public static void main(String[] args) { String s1 = "hello world"; String s2 = new String("hello world"); String s3 = "hello world"; String s4 = new String("hello world"); String s5 = "hello " + "world"; String s6 = "hel" + "lo world"; String s7 = "hello"; String s8 = s7 + " world"; System.out.println("s1 == s2: " + String.valueOf(s1 == s2) ); System.out.println("s1.equals(s2): " + String.valueOf(s1.equals(s2))); System.out.println("s1 == s3: " + String.valueOf(s1 == s3)); System.out.println("s1.equals(s3): " + String.valueOf(s1.equals(s3))); System.out.println("s2 == s4: " + String.valueOf(s2 == s4)); System.out.println("s2.equals(s4): " + String.valueOf(s2.equals(s4))); System.out.println("s5 == s6: " + String.valueOf(s5 == s6)); System.out.println("s1 == s8: " + String.valueOf(s1 == s8)); } } /* output s1 == s2: false s1.equals(s2): true s1 == s3: true s1.equals(s3): true s2 == s4: false s2.equls(s4): true s5 == s6: true s1 == s8: false */
从这段代码的输出可以看到,equals 比较的结果都是 true,这是因为 String 的 equals 比较的值( Object 对象的默认 equals 实现是比较引用,
String 对此方法进行了重写)。== 比较的是两个对象的引用,如果引用相同则返回 true,否则返回 false。s1==s2: false和 s2==s4: false
说明了 new 一个对象一定会生成一个新的引用返回。s1==s3: true 则证明了使用字面量创建对象同样的字面量会得到同样的引用。
s5 == s6 实际上和 s1 == s3 在 JVM 眼里是一样的情况,因为早在编译阶段,这种常量的简单运算就已经完成了。我们可以使用 javap 反编译一下 class 文件去查看
编译后的情况。
➜ ~ javap -c StringConstPool.class Compiled from "StringConstPool.java" public class io.github.jshanet.thinkinginjava.constpool.StringConstPool { public io.github.jshanet.thinkinginjava.constpool.StringConstPool(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); Code: 0: ldc #2 // String hello world 2: astore_1 3: return }</init>
看不懂汇编也没关系,因为注释已经很清楚了......
s1 == s8 的情况就略复杂,s8 是通过变量的运算而得,所以无法在编译时直接算出其值。而 Java 又不能重载运算符,所以我们在 JDK 的源码里也
找不到相关的线索。万事不绝反编译,我们再通过反编译看看实际上编译器对此是否有影响。
public class io.github.jshanet.thinkinginjava.constpool.StringConstPool { public io.github.jshanet.thinkinginjava.constpool.StringConstPool(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); Code: 0: ldc #2 // String hello 2: astore_1 3: new #3 // class java/lang/StringBuilder 6: dup 7: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V 10: aload_1 11: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 14: ldc #6 // String world 16: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 19: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 22: astore_2 23: return }</init></init>
通过反编译的结果可以发现,String 的变量运算实际上在编译后是由 StringBuilder 实现的,s8 = s7 + " world" 的代码等价于
(new StringBuilder(s7)).append(" world").toString()。Stringbuilder 是可变的类,通过 append 方法 和 toString 将两个 String 对象聚合
成一个新的 String 对象,所以到这里就不难理解为什么 s1 == s8 : false 了。
之所以会有以上的效果,是因为有字符串常量池的存在。字符串对象的分配和其他对象一样是要付出时间和空间代价,而字符串又是程序中最常用的对象,JVM
为了提高性能和减少内存占用,引入了字符串的常量池,在使用字面量创建对象时, JVM 首先会去检查常量池,如果池中有现成的对象就直接返回它的引用,如果
没有就创建一个对象,并放到池里。因为字符串不可变的特性,所以 JVM 不用担心多个变量引用同一个对象会改变对象的状态。同时运行时实例创建的全局
字符串常量池中有一个表,总是为池中的每个字符串对象维护一个引用,所以这些对象不会被 GC 。
intern 方法的作用
上面说了很多都没有涉及到主题 intern
方法,那么 intern
方法到作用到底是什么呢?首先查看一下源码。
/** * Returns a canonical representation for the string object. * <p> * A pool of strings, initially empty, is maintained privately by the * class {@code String}. * </p><p> * When the intern method is invoked, if the pool already contains a * string equal to this {@code String} object as determined by * the {@link #equals(Object)} method, then the string from the pool is * returned. Otherwise, this {@code String} object is added to the * pool and a reference to this {@code String} object is returned. * </p><p> * It follows that for any two strings {@code s} and {@code t}, * {@code s.intern() == t.intern()} is {@code true} * if and only if {@code s.equals(t)} is {@code true}. * </p><p> * All literal strings and string-valued constant expressions are * interned. String literals are defined in section 3.10.5 of the * <cite>The Java™ Language Specification</cite>. * * @return a string that has the same contents as this string, but is * guaranteed to be from a pool of unique strings. */ public native String intern();</p>
Oracle JDK 中,intern
方法被 native
关键字修饰并且没有实现,这意味着这部分到实现是隐藏起来了。从注释中看到,这个方法的作用是如果常量池
中存在当前字符串,就会直接返回当前字符串,如果常量池中没有此字符串,会将此字符串放入常量池中后再返回。通过注释的介绍已经可以明白这个方法的作用了,
再用几个例子证明一下。
public class StringConstPool { public static void main(String[] args) { String s1 = "hello"; String s2 = new String("hello"); String s3 = s2.intern(); System.out.println("s1 == s2: " + String.valueOf(s1 == s2)); System.out.println("s1 == s3: " + String.valueOf(s1 == s3)); } } /* output s1 == s2: false s1 == s3: true */
这里就很容易的了解 intern
实际上就是把普通的字符串对象也关联到常量池中。
以上是全面解析Java String類別的內容(附程式碼)的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本文討論了使用Maven和Gradle進行Java項目管理,構建自動化和依賴性解決方案,以比較其方法和優化策略。

本文使用Maven和Gradle之類的工具討論了具有適當的版本控制和依賴關係管理的自定義Java庫(JAR文件)的創建和使用。

本文討論了使用咖啡因和Guava緩存在Java中實施多層緩存以提高應用程序性能。它涵蓋設置,集成和績效優勢,以及配置和驅逐政策管理最佳PRA

本文討論了使用JPA進行對象相關映射,並具有高級功能,例如緩存和懶惰加載。它涵蓋了設置,實體映射和優化性能的最佳實踐,同時突出潛在的陷阱。[159個字符]

Java的類上載涉及使用帶有引導,擴展程序和應用程序類負載器的分層系統加載,鏈接和初始化類。父代授權模型確保首先加載核心類別,從而影響自定義類LOA


熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

AI Hentai Generator
免費產生 AI 無盡。

熱門文章

熱工具

SAP NetWeaver Server Adapter for Eclipse
將Eclipse與SAP NetWeaver應用伺服器整合。

Atom編輯器mac版下載
最受歡迎的的開源編輯器

ZendStudio 13.5.1 Mac
強大的PHP整合開發環境

VSCode Windows 64位元 下載
微軟推出的免費、功能強大的一款IDE編輯器

禪工作室 13.0.1
強大的PHP整合開發環境