先贴一段代码:
/**
* @author admin
* @date 2021/4/7 20:32
*/
public class Test004 {
public static void main(String[] args) {
String str1 = new StringBuilder("hello").append("world").toString();
String str2 = str1.intern();
String str3 = new StringBuilder("ja").append("va").toString();
String str4 = str3.intern();
String str5 = "java";
String str6 = str5.intern();
String str7 = "ja";
String str8 = "va";
String str9 = str7 + str8;
String str10 = "ja" + "va";
System.out.println(str1 == str2);
System.out.println(str3 == str4);
System.out.println(str5 == str6);
System.out.println(str5 == str3);
System.out.println(str5 == str4);
System.out.println(str5 == str9);
System.out.println(str5 == str10);
}
}
上面这段代码的输出结果是什么呢?
答案:
① true
② false
③ true
④ false
⑤ true
⑥ false
⑦ true
不知道大家的答案和这样是否一样
首先,先说一下“==”这个运行符号。
1、对于基本数据类型,比较的是值是否相等
2、对于引用数据类型,比较的是在内存中的存放地址。
再说一下intern()这个方法:
intern() 方法返回字符串对象的规范化表示形式。
它遵循以下规则:对于任意两个字符串 s 和 t,当且仅当 s.equals(t) 为 true 时,s.intern() == t.intern() 才为 true。
具体可以看一下源码中的解释:
解析:
String str1 = new StringBuilder("hello").append("world").toString();
String str2 = str1.intern();
String str3 = new StringBuilder("ja").append("va").toString();
String str4 = str3.intern();
这几行代码看起来逻辑结构是一样的,只是str1与str2的值都是“helloworld”,str3与str4的值都是“java”,但是为什么经过“==”运算符会出现不一样的结果呢?接下来咱们再来一一分析这个结果。
①结果为true,在new出来一个str1也就是“helloworld”之前,先在堆内存new一块空间,然后去常量池查找是否存在“helloworld”,存在就将常量池中的字符复制到堆内存,然后在栈内存保存这个String对象的地址,不存在就去常量池开辟一个空间去存储“helloworld”.然后后续一样在堆内存保存,栈内存放地址。str2执行intern()方法,是不会再去重新创建“helloworld”这个对象的,而是去常量池找这个字符对象,有的话直接拿出来,没有就将创建好String对象的存入常量池并返回这个“helloworld”的栈内存中的地址。所以说new出来的“helloworld”对象和从常量池中取出来的对象是同一个对象,所以说“==”运算,它的引用地址是一样的,所以返回结果为“true”。
②第二个结果是false,大家可能会想,这个“java”字符串和“helloworld”字符串一样啊,还是先new一个,然后执行intern()方法就不会去创建这个对象了,结果应该还是ture啊,其实并不是这样。
在java程序执行时,在类加载过程中执行system类时,会先去初始化system类,在线程初始化后调用,这里我们看一下它的initializeSystemClass()方法,在这个方法中又调用了sun.misc.Version.init(),我们看一下这个内部结构
我们发现,“java”这个字符串已经被定义过了,因为就是说在执行sun.misc.Version.init()时,已经将“java”这个字符串加载到运行时字符常量池中,所以,我们在new一个str3-“java”字符串后,再去执行intern()方法时,这str4拿到的“java”字符串并不是将str3这个对象存放入常量池,而是从常量池拿到初始化system类时已经创建出来的“java”字符串,所以new出来的“java”和运行时常量池中的“java”不是一个对象,所以str3与str4进行“==”运算时,得到的内存地址是不一样的。所以返回false.
可以看一下initializeSystemClass()这个方法的源码注释:
③str5是直接定义的一个字符串“java”,也就是直接去常量池查找是否存在"java",显然这个在常量池中是存在的,str6是对str5执行intern()方法,还是从常量池中取“java”这个值,所以返回的就是true;
其实"java"这个字符比较特殊因为在初始化system类时,本身已经定义了“java”这个字符串,我们换个字符串“helloworld”,
String str11 = "helloworld";
System.out.println(str11 == str1);
显然这个的结果也是false,一个是new出来的,在栈内存有它的地址,一个是直接定义的,虽然值还是常量池中的值,但是会在栈内存重新开辟一块空间来存放地址。所以引用地址不一样,,这里大家可以看一下String通过字面量赋值和New一个对象赋值两种方式的区别。
④false,str5是直接在常量池中拿到初始化system类时定义的“java”,str3是新创建出来的,地址不一样。
⑤ture,str5是直接在常量池中拿到初始化system类时定义的“java”,str4虽然是对str3执行intern()方法,但是还是从常量池中拿到初始化system类时定义的“java”,
⑥false,str5是直接在常量池中拿到初始化system类时定义的“java”,str9中含有变量,不会进入常量池查找,所以地址不一样。
⑦true,str5是直接在常量池中拿到初始化system类时定义的“java”,str10是字符串相加得到的,因为都是静态字符串的结果会添加到字符串池,所以str10还是一样的java地址。