想要理解intern()方法必须先了解String创建对象的方式,对String及字符串常量池有深入了解的小伙伴可以直接跳到第二部分进行阅读,不了解String的小伙伴就先补补课吧。
String str = "计算机";
这行代码会直接在字符串常量池中创建一个字符串对象,然后将栈中的str变量指向它。
String str = "计算机";
String str2 = "计算机";
如果我们再创建一个“str2”,其值也是“计算机”的话就会直接将栈中的“str2”变量也指向字符串常量池中已存在的“计算机”对象,从而避免重复创建对象,字符串常量池存在的原因也是如此。除此之外,常量池中还会存在很多java关键字,避免每次出现都重新创建,比如“java”这个关键字无论你是否创建它都会一直存在于字符串常量池中。
此时如下代码一定会返回true,因为“str”和“str2”指向同一个地址的同一个对象。
System.out.println(str == str2); // true
上面说的两种方式都是直接创建字符串,如果通过new关键字创建字符串对象情况就会有很大不同。
String str = new String("计算机");
当代码执行到括号中的"计算机"的时候会检测常量池中是否存在“计算机”这个对象,如果不存在则在字符串常量池中创建一个对象。当整行代码执行完毕时会因为new关键字在堆中创建一个“计算机”对象,并把栈中的变量“str”指向堆中的对象,如下图所示。这也是为什么说通过new关键字在大部分情况下会创建出两个字符串对象,推荐直接创建字符串对象而不用new。
String str = new String("计算机");
String str2 = new String("计算机");
同理,如果我们此时再创建一个值也是“计算机”的“str2”对象,当执行到第二行括号中的“计算机”时首先检测常量池中是否有“计算机”这个对象,因为第一行代码已经将其创建,所以有的话就不创建了;当第二行代码执行完毕会因为new关键字在堆中创建出一个新的对象,并将栈中的变量str2指向该对象。
此时如下代码一定会返回false,因为“str”和“str2”指向不同地址的两个对象。
System.out.println(str == str2); // false
jdk1.8中是这样描述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()方法被调用的时候,如果字符串常量池中已经存在这个字符串对象了,就返回常量池中该字符串对象的地址;如果字符串常量池中不存在,就在常量池中创建一个指向该对象堆中实例的引用,并返回这个引用地址(jdk1.7之前会直接将对象赋值到常量池中)。
听起来比较拗口,下面我们举几个例子来解释一下。
示例一:
周志明老师在《深入理解Java虚拟机》一书中举例如下
String str1 = new StringBuilder("计算机").append("软件").toString();
System.out.println(str1.intern()==str1); // true
第一行代码
第二行代码
内存中的图示如下,由于str1和str1.intern()都指向堆中的对象,因此结果为true。
String str2 = new StringBuilder("ja").append("va").toString();
System.out.println(str2.intern() == str2); // false
第一行代码
第二行代码
内存中的图示如下,因为str2.intern()指向字符串常量池中的对象,而str2指向堆中的对象,因此结果为false。
下面会罗列大量的例子来解释这个intern()函数,小伙伴们看到哪个例子感觉自己又行了,都掌握了,就可以停止了。
示例二:
String s = new String("1");
s.intern();
String s2 = "1";
System.out.println(s == s2); // false
第一行代码
第二行代码执行完毕后会因为“1”已经在字符串常量池中了,而不做其他操作,只会把这个“1”的地址返回,但是我们并没有接收;
第三行代码执行完毕时会因为字符串常量池中已经有“1”这个对象了,而不执行创建操作,直接将栈中的变量s2指向这个对象;
第四行代码执行完毕后打印false;
内存中的图示如下,因为s指向堆中的对象,而s2指向字符串常量池中的对象,因此结果为false。
String s3 = new String("1") + new String("1");
s3.intern();
String s4 = "11";
System.out.println(s3 == s4); // true
第一行代码
第二行代码执行完毕后会因为字符串常量池中没有“11”这个对象,而在字符串常量池中创建一个引用,指向队中的“11”这个对象,也就是和s3的指向相同;
第三行代码执行完毕后发现字符串常量值中已经有“11”了,只不过它是一个指向堆中的引用(这里可以理解为堆中已经有“11”这个对象了),那么不重复创建,而是直接将s4指向该引用。
第四行代码执行完毕后打印true
内存中的图示如下,因为s3和s4同时指向堆中的对象,因此结果为true
String s3 = new String("1") + new String("1");
String s4 = "11";
s3.intern();
System.out.println(s3 == s4); // false
第一行代码
第二行代码执行完毕后发现常量池中没有“11”这个对象,因此在字符串常量池中创建这个对象,并将栈中的变量s4指向该对象;
第三行代码执行完毕后发现字符串常量池中已经有“11”这个对象了,因此返回该对象地址,但我们并没有接收;
第四行代码执行完毕后打印false;
内存中的图示如下,因为s3指向堆中的对象,而s4指向字符串常量池中的对象,因此结果为false。
示例三
String str1 = new String("SEU") + new String("Calvin");
System.out.println(str1.intern() == str1); // true
System.out.println(str1 == "SEUCalvin"); // true
第一行代码
第二行代码
第三行代码执行完毕后打印true;
内存中的图示如下,因为str1.intern()和str1同时指向堆中的同一个对象,因此第二行结果为true;因为字符串常量池中的“SEUCalvin”对象是一个指向队中“SEUCalvin”对象的引用,而str1也指向堆中的这个对象,因此第三行结果为true。
其实第二行和第三行代码比较的都是两个绿色部分是否相等。
String str2 = "SEUCalvin"; // 新加的一行代码,其余不变
String str1 = new String("SEU") + new String("Calvin");
System.out.println(str1.intern() == str1); // false
System.out.println(str1 == "SEUCalvin"); // false
这几行代码只是在最前面加了一行,其他三行完全是上面的代码,但结果却截然不同。
第一行代码会在字符串常量池中创建“SEUCalvin”对象,并将栈中的变量str2指向该对象;
第二行代码
第三行代码
第四行代码
内存中的图示如下,因为str1.intern()指向字符串常量池中的对象,而str1指向堆中的对象,因此结果为false。
补充:
String str1 = new String("SEU") + new String("Calvin");
此行代码会创建5个对象。分别是:
由于堆中的“SEU”对象和“Calvin”对象直接变成垃圾了,因此上文中并没有分析这两个对象。
本文结论为总结官方文档、个人理解及代码实战得出,如有错误,欢迎指正。