JDK每次升级都会做很多优化,我们使用最多的String常量类也在不断被优化。这次和大家分享的是JDK1.8中对String的优化之一,intern()方法的使用。
我们可以看到,常量池在JVM的内存结构中属于方法区。常量池是一个统称,细分的话有分为:Class常量池(静态常量池)、运行时常量池和字符串常量池。
在JDK1.8中,运行时常量池和字符串常量池逻辑上属于方法区,但它们实际存放位置又在堆中。
接下来,我们讨论一下字符串常量池的使用。
理解字符串常量池的入口是我们平时使用最多的String类。
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** 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类有两个成员变量char数组、hash值。String类被final关键字修饰,而且变量char数组也被final修饰了。
我们知道类被 final 修饰代表该类不可继承,而 char[]被private+final修饰,代表String对象不可被更改。
String str = “abc”
当代码中使用这种方式创建字符串对象时,JVM 首先会检查该对象是否在字符串常量池中,如果在,就返回该对象引用,否则新的字符串将在常量池中被创建。最后str引用常量池中的对象。这种方式可以减少同一个值的字符串对象的重复创建,节约内存。
String str = new String(“abc”)
2、new对象的方式,如。
代码编辑加载时,会在常量池中创建“abc”;在new对象时,引用常量池中的“abc”,并在堆中创建一个String对象。最后str引用堆中String对象。
对比后推荐使用第一种方式,简单且节省内存空间。
回到今天的主题,intern()方法。如果我们对着英文注释一句一句翻译,来理解它的话会很有限很苦涩,结合代码示例理解会更好一些(个人经验)。
String的intern()方法及其注释如下:
/**
* Returns a canonical representation for the string object.
*
* A pool of strings, initially empty, is maintained privately by the
* class {@code String}.
*
* 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.
*
* 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}.
*
* All literal strings and string-valued constant expressions are
* interned. String literals are defined in section 3.10.5 of the
* The Java™ Language Specification.
*
* @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();
intern()是一个native方法,该方法的作用在注释中有描述,就是通过常量池复用技术来节省内存空间和重复创建的开销。
下面我们结合示例来理解intern()的使用及作用。
public static void main(String[] args) {
String str1 = "abc";
String str2 = "abc";
String str3 = new String("abc");
String str4 = new String("abc");
System.out.println(str1 == str2); // true
System.out.println(str3 == str4); // false
System.out.println(str1 == str3); // false
}
这个示例,是平时大家使用最多的,没有异议。
public static void main(String[] args) {
String str1 = "abc";
String str2 = "abc";
String str3 = new String("abc").intern();
String str4 = new String("abc").intern();
String str5 = new String("abc");
System.out.println(str1 == str2); // true
System.out.println(str3 == str4); // true
System.out.println(str1 == str3); // true
System.out.println(str1 == str5); // false
System.out.println(str3 == str5); // false
}
分析,在intern()被调用时, “abc” 已经在字符串常量池中被创建了,new的String对象被intern()修饰后,str3、str4引用的是字符串常量池中的“abc” 。所以有了上面的结果。
intern()方法注释里也做了说明:
s.intern() == t.intern() is true if and only if s.equals(t) is true.
也是就是两个String对象,当且仅当它俩equals()比较值为true,那它俩intern()的“==”操作也为true。
public static void main(String[] args) {
String str1 = new String("abc");
String str2 = str1.intern();
String str3 = "abc";
System.out.println(str1 == str3); //false
System.out.println(str2 == str3); //true
}
分析,str1引用的是堆内创建的String对象,str2、str3引用的是字符串常量池中的“abc”。值得关注的是str1调用intern()后,str1的引用并不会被改变,只会返回作用后的引用,如果需要使用这个引用就要赋值给新的String对象,用法上类似于String的replace方法。
public static void main(String[] args) {
String str1 = new String("abc") + new String("abc");
String str2 = str1.intern();
String str3 = "abc" + "abc";
String str4 = new String("abc") + new String("abc");
String str5 = "abc" + "abc";
System.out.println(str1 == str3); //true
System.out.println(str2 == str3); //true
System.out.println(str4 == str5); //false
}
上一个示例中,“str1 == str3”返回的是false;该示例的区别仅仅是,new的字符串对象相加后再调用intern(),“str1 == str3”返回的却是true。intern()作用到了相加后的对象的本身。
str4没有调用intern(),结果“str4 == str5”返回的是false。
public static void main(String[] args) {
Teacher teacher = new Teacher();
teacher.setName("abc");
String name = teacher.getName();
String str = "abc";
System.out.println(name == str); // true
}
static class Teacher {
private String name;
private Integer age;
/** get()/set() **/
}
}
Teacher类的String类型属性值,引用的也是字符串常量池中的对象。
至此,本篇结束。