简单的符号,不简单的对象

Java基础面试题中"=="和"equals"的区别是什么?出现的频率还是蛮高的(听别人说的)。知道他们的作用,也就知道他们有什么区别了(这怕不是废话)。但是在使用中出现的结果有时候不是你以为的结果。主要原因是在比较的对象身上。

简单的符号

"=="和"equals"的作用是对两个对象进行比较。具体如下:

  • 对于==,如果作用于基本数据类型的变量,则直接比较其存储的 “值”是否相等;如果作用于引用类型的变量,则比较的是所指向的对象的地址。

  • 对于equals,如果没有对equals方法进行重写,则比较的是引用类型的变量所指向的对象的地址;反之比较的则是所指向的对象的内容。

不简单的对象

基本数据类型

Java中8种基本数据类型:

  • 整型:byte(1 byte), short(2 byte), int(4 byte) , long(8 byte)

  • 浮点型:float(4 byte), double(8 byte)

  • 字符型: char(2 byte)

  • 布尔型: boolean(所占的空间大小并没有给出精确的定义,网上的回答也是五花八门)

注意:equals方法不能作用于基本数据类型的变量。

Java中的基本数据的类型的比较相对而言是最简单的。使用"=="进行比较,判断其是否相等。

String类型

点击查看代码
public static void main(String[] args) {
        //第一种创建方式
        String a = "abc" ;
        String b = "abc" ;
      	//第二种创建方式
        String c = new String("abc");
        String d = new String("abc");
        
        System.out.println(a==b);//true
        System.out.println(a.equals(b));//true  
        
        System.out.println(c==d);//false
        System.out.println(c.equals(d));//true

        System.out.println(a==c);//false
        System.out.println(a.equals(c));//true     
    }
上面的例题非常简单,对字符串"abc"进行比较。
  • 对于"equals",因为String中对equals进行了重写,比较的是对象的内容,所以比较结果都是true,对于这个结果在意料之中。
  • 对于"==",结果如果让你皱起了眉头,那就继续往下看。再次提醒其作用引用类型变量时,比较的是所指向的对象的地址。

使用第一种方式时,首先在常量池中查询并通过equals来判断是否已经存在"abc",如果有就不会创建的新的对象,直接返回已有对象的引用,由于String类被final修饰,所以它一经创建就不会被修改。也就不必担心共享而带来的问题。也就不难理解为什么a==b的结果为true。

使用第二种方式时,无论常量池中有没有"abc"字符串,都会在堆内存中开辟一片新的空间存放对象,即使堆内存中存在"abc",也会开辟一块新的空间存放对象,所以c == d 、 a == c的结果则均为false。

Integer类型

点击查看代码
public static void main(String[] args) {
        Integer i1 = 127;
        Integer i2 = 127;
        System.out.println(i1 == i2);//true
        System.out.println(i1.equals(i2));//true
        Integer i3 = 128;
        Integer i4 = 128;
        System.out.println(i3 == i4);//false
        System.out.println(i3.equals(i4));//true
    }
上面已经了解了String类型,接下来我们来看下Integer类型。Integer是Java为基本类型int提供的包装类。

Java 为基本数据类型提供了对应的包装器类型

  • Integer:对应封装了基本类型 int;

  • Long:对应封装了基本类型 long;

  • Float:对应封装了基本类型 float;

  • Double:对应封装了基本类型 double;

  • Boolean:对应封装了基本类型 boolean;

  • String:对应封装了字符串类型 char[]。

包装类型与基本类型之间的相互转换称作装箱和开箱。

所谓的自动装箱,就是自动将基本数据类型转换为包装器类型。所谓的自动拆箱,也就是自动将包装器类型转化为基本数据类型。

数值是基本数据类型 int,当赋值给包装器类型(Integer)变量的时候,触发自动装箱操作,创建一个 Integer 类型的对象,并且赋值给变量。

  • 对于"equals",因为比较的是对象的内容,所以比较结果都是true,对于这个结果在意料之中。
  • 对于"==",上面提到触发自动装箱时,会创建一个对象,按这说法来看,i1 == i2、i3 == i4的结果应该都是false,为什么i1 == i2的结果是true呢?

实际上,当通过自动装箱,也就是调用 valueOf() 来创建 Integer 对象的时候,如果要创建的 Integer 对象的值在 -128 到 127 之间,会从 IntegerCache 类中直接返回,否则才调用 new 方法创建。看代码更加清晰一些,Integer 类的 valueOf() 函数的具体代码如下所示:

点击查看代码
public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}
为什么 IntegerCache 只缓存 -128 到 127 之间的整型值呢?

在 IntegerCache 的代码实现中,当这个类被加载的时候,缓存的享元对象会被集中一次性创建好。毕竟整型值太多了,我们不可能在 IntegerCache 类中预先创建好所有的整型值,这样既占用太多内存,也使得加载 IntegerCache 类的时间过长。所以,我们只能选择缓存对于大部分应用来说最常用的整型值,也就是一个字节的大小(-128 到 127 之间的数据)。

实际上,JDK 也提供了方法来让我们可以自定义缓存的最大值,有下面两种方式。如果你通过分析应用的 JVM 内存占用情况,发现 -128 到 255 之间的数据占用的内存比较多,你就可以用如下方式,将缓存的最大值从 127 调整到 255。不过,这里注意一下,JDK 并没有提供设置最小值的方法。

点击查看代码
//方法一:
-Djava.lang.Integer.IntegerCache.high=255
//方法二:
-XX:AutoBoxCacheMax=255
实际上,除了 Integer 类型之外,其他包装器类型,比如 Long、Short、Byte 等,也都利用了享元模式来缓存 -128 到 127 之间的数据。
上一篇:自动装箱与自动拆箱


下一篇:Redis中设置key的过期时间