本文将从==和equals这道经典面试题入手,深入讲解==。本文会涉及到一些JVM的知识,预计阅读时间:5-10分钟
先从一道面试题入手:==和equals有什么区别
-
首先说明equals其实就是==,只不过Integer和String等重写了equals方法,重写后的equals是比较值是否相等
-
然后又分成两种情况:
-
对于基本数据类型,==比较的是值是否相等
-
对于引用数据类型,==比较的是地址是否相等
下面看一下代码:
int a = 10;
int b = 10;
System.out.println(a == b);//true
String s1 = "string";
String s2 = "string";
System.out.println(s1 == s2);//true
System.out.println(s1.equals(s2));//true
下面我们来看看这个知识点可以延申出什么?
关于Integer的比较
-
案例一
Integer a = 1024; Integer b = new Integer(1024); Integer c = 1024; int d = 1024; Integer e = Integer.valueOf(1024);
System.out.println(a == b);//false System.out.println(a == c);//false System.out.println(b == c);//false System.out.println(a == e);//false System.out.println(b == e);//false
System.out.println(a.equals(b));//true System.out.println(a.equals(c));//true
System.out.println(a == d);//true System.out.println(b == d);//true System.out.println(c == d);//true
-
首先明确一点:用Integer.valueOf()声明和用字面量声明是一回事
-
Integer是引用数据类型,所以第一组比较的是地址,这四个对象都是在堆区,地址是不一样的
-
第二组是因为Integer重写了equals方法,equals是值比较
-
第三组是自动拆箱
2.上面的案例想必大家都已经明白,我们再来看一个“诡异”一点的例子
Integer a = 127;
Integer b = new Integer(127);
Integer c = 127;
int d = 127;
Integer e = Integer.valueOf(127);
System.out.println(a == b);//false
System.out.println(a == c);//true
System.out.println(b == c);//false
System.out.println(a == e);//true
System.out.println(b == e);//false
System.out.println(a.equals(b));//true
System.out.println(a.equals(c));//true
System.out.println(a == d);//true
System.out.println(b == d);//true
System.out.println(c == d);//true
-
最后两组想必大家都知道为什么
-
第一组:Integer会对[-128, 127]区间(双闭区间)之内的对象做缓存
-
用字面量声明的Integer在这个区间内的就不会再创建新的对象,所以a == c == e,这三个常量会缓存在**运行时常量池**中,**运行时常量池在方法区**中
-
new Integer总是会创建新的对象,对象创建在堆区
关于String的比较
1.案例一
String s1 = "string";
String s2 = "string";
String s3 = new String("string");
String s4 = String.valueOf("string");
System.out.println(s1 == s2);//true
System.out.println(s1 == s3);//false
System.out.println(s1 == s4);//true
System.out.println(s1.equals(s2));//true
System.out.println(s1.equals(s3));//true
-
首先用字面量声明和用String.valueOf()声明是一回事
-
new String总是会在堆上创建新的对象
-
不同于Integer缓存[-128, 127],String对于字符串常量都会缓存;而且缓存在**字符串常量池**中,**字符串常量池在堆区(1.8)** (缓存Integer的**运行时常量池在方法区**)。所以s1 == s2 == s4
2.字符串拼接
String s1 = "string";
String s2 = "str" + "ing";
String s3 = new String("str") + new String("ing");
String s4 = "str";
String s5 = "ing";
String s6 = s4 + s5;
String s7 = s4 + "ing";
String s8 = "str" + s5;
String s9 = s8.intern();
System.out.println(s1 == s2);//true
System.out.println(s1 == s3);//false
System.out.println(s1 == s6);//false
System.out.println(s3 == s6);//false
System.out.println(s7 == s1);//false
System.out.println(s8 == s1);//false
System.out.println(s3 == s7);//false
System.out.println(s3 == s8);//false
System.out.println(s1 == s9);//true
-
常量与常量的拼接结果在常量池中,原理是编译期优化
-
常量池中不会存在重复的常量(常量池底层是基于哈希表实现的)
-
new String总是会在堆上创建新的对象
-
只要拼接的部分中有一个是变量,拼接的结果就是在堆中的,变量拼接的原理是StringBuilder
-
对拼接的结果调用intern方法(这个方法后续文章会介绍),会主动将常量池中还没有的字符串对象放入字符串常量池中,并返回此对象的地址。如果字符串常量池中已经有了这个常量,那就直接返回在字符串常量池中的地址
G1垃圾回收器的String去重操作
-
去重去的是堆上的重复对象
-
不是字符串常量池上的,字符串常量池上的本来就不是重复的
-
这个功能默认是不开启的
Java是值传递还是引用传递
-
对于基本数据类型和String(String对象是不可变的),是值传递。即函数传入参数为基本数据类型或String类型时,在函数内部对这个变量进行修改,这个修改是不会作用到函数外部的
-
对于引用类型,以及数组(数组传入函数的时候传的也是地址),在函数内对这两个做修改是真的会影响到函数外部的,因为这种情况下是引用传递
本文使用 mdnice 排版