Java面试--你真的了解==吗?

96 阅读4分钟

本文将从==和equals这道经典面试题入手,深入讲解==。本文会涉及到一些JVM的知识,预计阅读时间:5-10分钟

先从一道面试题入手:==和equals有什么区别

  1. 首先说明equals其实就是==,只不过Integer和String等重写了equals方法,重写后的equals是比较值是否相等
  2. 然后又分成两种情况:
  • 对于基本数据类型,==比较的是值是否相等
  • 对于引用数据类型,==比较的是地址是否相等

下面看一下代码:

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的比较

  1. 案例一

    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 排版