参考链接:黑马程序员JVM完整教程,全网超高评价,全程干货不拖沓_哔哩哔哩_bilibili
知识补充
常量池
1、常量池,就是一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量等信息
运行时常量池,常量池是 *.class 文件中的,当该类被加载,它的常量池信息就会放入运行时常量
池,并把里面的符号地址变为真实地址
2、程序执行时,常量池中的信息(符号)都会被加载到运行时常量池中,但是加载完之后,这些符号还没有成为java对象,只有执行到具体某一行代码的时候,才会转变为java对象,并将字符常量放进串池(StringTable,是一个哈希表,长度固定且不能扩容)中。
java中的数据类型
java中的数据类型分为基本类型和引用类型
1、java中有八种基本数据类型:
- 整数型(默认int)
byte - 1字节
short - 2字节
int - 4字节
long - 8字节,赋值时一般在数字后加上 l 或 L
- 浮点型(默认double)
float - 4字节,直接赋值时必须在数字后加上 f 或 F
double - 8字节,赋值时一般在数字后加 d 或 D
- 字符型
char - 2字节,存储 Unicode 码,用单引号赋值
- 布尔型
boolean - 1字节,只有 true 和 false 两个取值,一个字节就够了
2、所有的非基本数据类型都是引用数据类型,除了基本数据类型对应的引用类型外,类、 接口类型、 数组类型、 枚举类型、 注解类型、 字符串型都属于引用类型。
主要有以下区别:
- 基本变量类型在方法中定义的非全局基本数据类型变量的具体内容是存储在栈中的
- 引用数据类型变量其具体内容都是存放在堆中的,而栈中存放的是其具体内容所在内存的地址
- 基本数据类型是按值传递
- 引用数据类型是按引用传递
有了基础知识的补充之后,我们通过两个例子来学习String的intern方法
使用 intern 方法,主动将串池中还没有的字符串对象放入串池
- jdk1.8 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池, 会把串池中的对象返回
- jdk1.6 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有会把此对象复制一份,放入串池(也就是说串池中的对象和这个字符串对象不是同一个), 会把串池中的对象返回
例一、
String s1 = new String("a")+new String("b");
String s2=s1.intern();
System.out.println(s2=="ab");
System.out.println(s1=="ab");
第一行代码:
因为"a","b"是常量,被依次放入串池中,此时串池中有["a","b"]
new String("a") new String("b") 是新的对象,放进堆中,值和串池中的相等,但是对象不是同一个。
s1是通过StringBuilder拼接了"a"和"b",形成一个新对象 new String("ab"),也放入堆中,并没有放入串池中,因为"ab"是动态拼接成的,串池中是放常量字符串。
此时堆中有 [new String("a") new String("b") new String("ab")]
串池中有["a","b"]
第二行代码:
如果想将"ab"放入串池中,可以调用String的intern()方法,将s1这个字符串对象尝试放入串池,如果有则不会放入,如果没有则将s1放入串池,并把串池中的对象返回。
所以此时串池中有["a","b","ab"],s2引用的对象是串池中的对象
第三行代码:
执行第三行代码时,会在串池中先看看有没有"ab",结果发现有了,就不会再在串池中引用新的对象了,此时==后的"ab"就是串池中的"ab",因此第三行代码结果返回true
第四行代码:
执行第二行代码时已经把s1对象放入了串池,所以第四行代码返回true
程序执行结果如图:
例二
String s="ab";
String s1 = new String("a")+new String("b");
String s2=s1.intern();
System.out.println(s2==s);
System.out.println(s1==s);
第一行代码:
执行第一行代码前,串池是空的,因此在串池中放入常量"ab"
第二行代码:
同例一,在串池中依次放入"a","b",new String("a") new String("b") new String("ab")依次放入堆中
此时串池中有["ab","a,"b"],注意:此时堆中的 new String("ab")和串池中的"ab"不是一个对象
第三行代码:
执行intern方法,但是此时串池中已经有"ab",所以s1对象不会放入串池中,而返回的s2是串池中对象,所以执行第四行代码时,返回的结果是true,而第五行代码返回的是false
程序结果如图所示:
总结
String的intern方法,会将这个字符对象尝试放入串池中,如果串池中已经有这个常量,就不将这个对象放入池中,如果没有就将这个对象放入池中,,并且返回的是串池中的对象,也就是说intern方法返回的对象和调用方法的对象不一定是同一个对象,但是值是相同的。
字符串变量拼接的原理是StringBuilder
字符串常量拼接的原理是编译器优化
附录
另外还有一道面试题
String s1 = "a";
String s2 = "b";
String s3 = "a" + "b";
String s4 = s1 + s2;
String s5 = "ab";
String s6 = s4.intern();
// 问
System.out.println(s3 == s4);
System.out.println(s3 == s5);
System.out.println(s3 == s6);
String x2 = new String("c") + new String("d");
String x1 = "cd";
x2.intern();
// 问,如果调换了【最后两行代码】的位置呢,如果是jdk1.6呢
System.out.println(x1 == x2);
我们不运行程序来看一下执行结果:
前三行代码执行完之后,串池中有["a","b","ab"]
s4,s1+s2实际上是通过StringBuilder方法拼接成"ab",放入堆中
s5的"ab"在串池中已经存在,因此不放入串池中,但是和s3是同一个对象
s6行代码执行了intern方法,因为串池中已经有"ab"对象了,所以s4并不放入串池中,凡是返回的是串池中的"ab",因此s6和s3是同一个对象
综上,s3==s4结果为false
s3==s5结果为true
s3==s6结果为true
同理 x1==x2返回false,如果两行代码调换顺序,结果为true
如果是jdk1.6,无论代码换不换,结果都为false,因为在1.6中的intern方法返回的对象和调用方法的对象不是同一个