String 1.8
类的定义
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence
String
类使用final
关键字修饰,限制了该类无法被继承,里面的方法也无法被重写。这是String
对象不可变的第一点。
类的成员变量
/** The value is used for character storage. */
private final char value[];
/** Cache the hash code for the string */
private int hash; // Default to 0
String
类常用的两个成员变量value
和hash
value
:final
关键字修饰的字符类型的数组,这是String
对象不可变的第二点。
hash
: 对应字符串的哈希值。
String为什么要设置为不可变的
- 保证
String
对象的安全性,防止String
对象被恶意修改 - 保证
hash
属性值不会频繁变更,确保了唯一性,使得类似HashMap
容器能够实现相应的 key-value 存储 - 可以实现字符串常量池
String对象的创建方式
- 通过字符串常量的方式
String str1 = "abc";
使用这种形式创建字符串时, JVM 会在字符串常量池中先检查是否存在该对象,如果存在,返回该对象的引用地址,如果不存在,则在字符串常量池中创建该字符串对象并且返回引用。使用这种方式创建的好处是:避免了相同值的字符串重复创建,节约了内存。
- 通过
String()
构造函数的方式
String str3 = new String("abc");
这种创建方式分为两个阶段,首先在编译时,字符串
abc
会被加入到常量结构中,类加载时候就会在常量池中创建该字符串。然后就是在调用new()
时,JVM 将会调用String
的构造函数,同时引用常量池中的abc
字符串,在堆内存中创建一个String
对象并且返回堆中的引用地址。
intern()方法
- 在JDK 6中,
intern()
方法会把首次遇到的字符串实例复制到永久代的字符串常量池中存储,返回的也是永久代里面这个字符串实例的引用 - 而JDK 7之后,字符串常量池移到Java堆中,
intern()
方法不需要拷贝字符串的实例到永久代,只需要在常量池里记录一下首次出现的实例引用即可
intern()
方法用来返回常量池中的某字符串,如果常量池中已经存在该字符串,则直接返回常量池中该对象的引用。否则,在常量池中加入该对象,然后返回引用。
使用intern()
方法后,构造相同值的字符串对象时,返回相同的对象引用地址,能够节约不少空间。
代码实例
String str1 = "abc";
String str2 = "abc";
String str3 = new String("abc");
String str4 = new String("abc").intern();
System.out.println(str1 == str2);
System.out.println(str1 == str3);
System.out.println(str1 == str4);
/* output:
* true
* false
* true
*/
字符串常量池
- JDK1.6中,字符串常量池放在方法区
- JDK1.7 和 JDK1.8 中,字符串常量池放在堆内存
- 所以导致
String
的intern()
方法因为以上变化在不同版本会有不同表现
String str = "abcd";
// 字符串常量池
String s0 = new StringBuilder().append("he").append("llo").toString();
System.out.println(s0.intern() == s0);
String s1 = new StringBuilder().append("ab").append("cd").toString();
System.out.println(s1.intern() == s1);
/**output:
* true
* false
*/
代码详解:
这段代码在JDK 6中运行,会得到两个false
,而在JDK 7中运行,会得到一个true
和一个false
。
产生差异的原因是,在JDK 6中,intern()
方法会把首次遇到的字符串实例复制到永久代的字符串常量池中存储,返回的也是永久代里面这个字符串实例的引用,而由StringBuilder
创建的字符串对象实例在Java堆上,所以必然不可能是同一个引用,结果将返回false
。
而JDK 7之后的intern()
方法实现就不需要再拷贝字符串的实例到永久代了,既然字符串常量池已经移到Java堆中,那只需要在常量池里记录一下首次出现的实例引用即可,因此intern()
返回的引用和由StringBuilder
创建的那个字符串实例就是同一个。而对s1
比较返回false
,这是因为abcd
这个字符串在执行StringBuilder.toString()
之前就已经出现过了,字符串常量池中已经有它的引用,不符合intern()
方法要求“首次遇到”的原则,hello
这个字符串则是首次出现的,因此结果返回true
。