Java中的==和equals的区别有哪些

本篇内容主要讲解“Java中的==和equals的区别有哪些”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Java中的==和equals的区别有哪些”吧!

站在用户的角度思考问题,与客户深入沟通,找到泊头网站设计与泊头网站推广的解决方案,凭借多年的经验,让设计与互联网技术结合,创造个性化、用户体验好的作品,建站类型包括:网站制作、成都网站设计、企业官网、英文网站、手机端网站、网站推广、国际域名空间、雅安服务器托管、企业邮箱。业务覆盖泊头地区。

java内存知识点:

引用对象存储的内存是引用对象的内存地址,类似0xa5、0xa6;基本数据类型存储的常量值,例如1、2、5。==比较的就是存储内存内容,因为有可能是内存地址,有可能是常量值,所以结果会产生混淆。

在详细了解==与equals底层原理之前,先知道怎么使用:

  • == 遇到两侧都是对象时,则比较对象的引用地址是否相同,否则全是比较其常量值是否相同

  • equals 左侧必须是对象,调用该对象的equals方法,返回true则相等,对象的equals方法可被重写

等号(==)比较的内容与JVM底层原理

在说明之前先看下例子

public static void test1() {
     Integer a = new Integer(3);
     Integer b = 3;
     Integer c = 3;
     int d = 3;
     System.out.println(a == b);   // false
     System.out.println(a == d);   // true
     System.out.println(b == c);   // true
     System.out.println(b == d);   // true
}
a == b 为false的原因

== 两侧a和b都是对象类型,所以这里比较的是对象的引用地址是否相同。其中a是通过new关键字在堆内存开辟的空间,其引用是指向该内存空间的地址。而b则涉及到了Integer的享元模式,即JVM在启动时会针对Integer实例化一批Integer数据放到缓存池中,这批数据的范围默认是[-128,127],可以通过JVM参数调整,不在该范围的Integer则通过new方式创建。通过享元模式在开发中使用该范围内的Integer数据时,会直接从缓存池中获取。而这里Integer b=3则是从缓存池中取出的,其引用地址也是批量初始化时开辟的内存空间。二者不同,故返回false

a == d 为true的原因

==两侧中d是基本数据类型,另一侧为包装类型,这时会导致另一侧自动拆箱,Integer转为int,转换方法为Integer#intValue,然后才去比较。这样==两侧就都是int类型。故二者相等。了解自动拆箱能更清楚这点。

b == c 为true的原因

== 两侧都是对象类型,因此会比较地址。而这两个对象在创建时符合Integer享元数据故从缓存池[-128,127]中获取,二者引用地址指向的都是缓存池中Integer=3的内存地址,故二者相等

b == d 为true的原因

==两侧中c是基本数据类型,另一侧为包装类型,比较原理与a == d一致。

JVM汇编指令分析

  public static void test1();
    Code:
       0: new           #3                  // class java/lang/Integer  ## 在堆内存开辟空间,其引用入栈, stack[0]=ref (ref表示为引用对象)
       3: dup                               ## 复制栈顶元素一份, stack[1]=ref stack[0]=ref
       4: iconst_3							## 入栈常量int=3,刚开辟空间的引用指向常量int=3,stack[1]=ref_3 stack[0]=ref_3		
       5: invokespecial #4                  // Method java/lang/Integer."":(I)V  ## 调用JVM内部生成的方法,该方法返回this=stack[0],且出栈,此时stack[0]=ref_3
       8: astore_0                          ## 出栈->入槽,将栈顶元素放入槽0 即stack[0]->salt[0]=ref_3,之后栈空(这里指的是操作数栈)
       9: iconst_3                          ## 入栈,从常量池中获取int=3放入栈顶,即 stack[0]=3
      10: invokestatic  #5                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;  ## 调用静态方法Integer#valueOf
      13: astore_1							## 出栈->入槽1,stack[0]=salt[1]=Integer.valueOf(3).注意该方法
      14: iconst_3							## 入栈常量3,stack[0]=3
      15: invokestatic  #5                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;  ## 调用静态方法Integer#valueOf
      18: astore_2							## 出栈->入槽2,stack[0]=salt[2]=Integer.valueOf(3).注意该方法	 
      19: iconst_3                          ## 入栈常量3,stack[0]=3
      20: istore_3							## 出栈->入槽3,stack[0]=salt[3]=3	 		
      21: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream; ## 调用打印方法
      24: aload_0                           ## 出槽0->入栈 salt[0]=stack[0]=ref_3
      25: aload_1                           ## 出槽1->入栈 salt[1]=stack[0]=Integer.valueOf(3),stack[1]=ref_3
      26: if_acmpne     33					## 比较栈顶2元素引用类型的值(这里的值是内存地址,例如0xa5,0xa6),当结果不等时跳转到33 			
      29: iconst_1                          ## 分支1:上边指令不跳转,则继续,这里为入栈int1  
      30: goto          34                  ## 跳转34直接打印结果,即两引用类型不等则直接打印,否则跳转33
      33: iconst_0                          ## 分支2: 对比两引用类型相等,则入栈int=0,然后打印,这里的boolean类型,实际为int类型(0、1)
      34: invokevirtual #7                  // Method java/io/PrintStream.println:(Z)V
      37: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream; ##第一个System.out.println(a == b);结束
      40: aload_0                           // 出槽0->入栈 salt[0]=stack[0]=ref_3
      41: invokevirtual #8                  // Method java/lang/Integer.intValue:()I   ## 调用静态方法Integer#intValue
      44: iload_3                           ## 出栈->入槽3,stack[0]=salt[3]=3	 
      45: if_icmpne     52                  ## 比较int类型数值大小,当结果不等时跳转52,否则继续
      48: iconst_1                          ## 分支1:数值相等时执行,入栈常量1 stack[0]=1,这里同上,boolean值=true
      49: goto          53                     
      52: iconst_0                          ## 分支2:数值不相等时执行,入栈常量0,stack[0]=0,这里同上,boolean值=false
      53: invokevirtual #7                  // Method java/io/PrintStream.println:(Z)V  ## 第二个 System.out.println(a == d); 结束
      56: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream; 
      59: aload_1                           ## 出槽1->入栈 salt[1]=stack[0]=Integer.valueOf(3)
      60: aload_2                           ## 出槽2->入栈 salt[2]=stack[0]=Integer.valueOf(3),stack[0]=Integer.valueOf(3)
      61: if_acmpne     68                  ## 引用地址比较: 
      64: iconst_1                          ## 分支1: true
      65: goto          69
      68: iconst_0                          ## 分支2: false 
      69: invokevirtual #7                  // Method java/io/PrintStream.println:(Z)V    ## 第三个System.out.println(b == c);结束
      72: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
      75: aload_1							## 出槽1->入栈 salt[1]=stack[0]=Integer.valueOf(3)
      76: invokevirtual #8                  // Method java/lang/Integer.intValue:()I 调用静态方法Integer#intValue
      79: iload_3                           ## 出槽3->入栈 salt[3]=stack[0]=3,stack[0]=Integer.valueOf(3).intValue
      80: if_icmpne     87                  ## 比较int类型数值大小,当结果不等时跳转87,否则继续
      83: iconst_1                          ## 分支1: true
      84: goto          88
      87: iconst_0                          ## 分支2: false
      88: invokevirtual #7                  // Method java/io/PrintStream.println:(Z)V   ## 第四个System.out.println(b == d);结束
      91: return                            ## 方法结束
汇编指令结论

== 两侧都是引用类型时,会直接比较引用类型的存储值(存储的是指向的对象的地址)来判断结果

== 两侧任何一侧为引用类型时,会将引用类型转为基本数据类型(存储的是常量值),然后对比数值大小来判断结果

基本数据类型装箱流程(这也是包装类型的构建过程):加载基本数据常量,调用其对应的包装类型的valueOf方法将基本数据类型转为包装类型。

包装类型拆箱流程:调用其对应的xxValue方法获取其基本数据类型常量,例如Integer的intValue

再看一个示例

    public static void test2() {
        Integer a = new Integer(150);
        Integer b = 150;
        Integer c = 150;
        int d = 150;
        System.out.println(a == b);   // false
        System.out.println(a == d);   // true
        System.out.println(b == c);   // false
        System.out.println(b == d);   // true
    }
b == c 为false的原因

== 两侧都是对象类型。根据包装类型的构建(装箱过程)会先调用ValueOf方法来实例化改包装对象

针对Integer

public static Integer valueOf(int i) {
    // 享元范围数据直接返回缓存对象 
	if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)];
	// 否则构建对象
	return new Integer(i);
}

这里150不在默认的享元范围,则会重新构建对象(开辟空间,加载常量,引用指向),此时==两侧比较的地址则不相等

针对Long: 享元范围不可调[-128,127]

public static Long valueOf(long l) {
        final int offset = 128;
        if (l >= -128 && l <= 127) { // will cache
            return LongCache.cache[(int)l + offset];
        }
        return new Long(l);
    }

针对Boolean:是直接缓存的2个对象,从始至终只有2个实例:TRUE、FALSE

    public static Boolean valueOf(String s) {
        return parseBoolean(s) ? TRUE : FALSE;
    }

针对Byte

    public static Byte valueOf(byte b) {
        final int offset = 128;
        return ByteCache.cache[(int)b + offset];
    }

针对Short

    public static Short valueOf(short s) {
        final int offset = 128;
        int sAsInt = s;
        if (sAsInt >= -128 && sAsInt <= 127) { // must cache
            return ShortCache.cache[sAsInt + offset];
        }
        return new Short(s);
    }

针对Character

    public static Character valueOf(char c) {
        if (c <= 127) { // must cache
            return CharacterCache.cache[(int)c];
        }
        return new Character(c);
    }

针对Float: 无缓存,全部开辟新空间

    public static Float valueOf(float f) {
        return new Float(f);
    }

针对Double:无缓存,全部开辟新空间

    public static Double valueOf(double d) {
        return new Double(d);
    }

equals比较的内容

equals比较时左侧一定为对象,那么先看下Object的equals方法

    public boolean equals(Object obj) {
        return (this == obj);
    }

可以看出来,默认的equals还是调用的==,而且是两侧都是对象的==,所以对比的内存地址。equals的使用产生混淆的主要原因是equals能被重写。

针对Integer: 重写为基本数据类型值比大小

    public boolean equals(Object obj) {
        if (obj instanceof Integer) {
            return value == ((Integer)obj).intValue();
        }
        return false;
    }

针对Long: 重写为基本数据类型值比大小

    public boolean equals(Object obj) {
        if (obj instanceof Long) {
            return value == ((Long)obj).longValue();
        }
        return false;
    }

针对Byte

    public boolean equals(Object obj) {
        if (obj instanceof Byte) {
            return value == ((Byte)obj).byteValue();
        }
        return false;
    }

针对Boolean

    public boolean equals(Object obj) {
        if (obj instanceof Boolean) {
            return value == ((Boolean)obj).booleanValue();
        }
        return false;
    }

针对Short

    public boolean equals(Object obj) {
        if (obj instanceof Short) {
            return value == ((Short)obj).shortValue();
        }
        return false;
    }

针对Float: 浮点类型转int,然后比大小,转换方式为将浮点存储的二进制数据当做int类型二进制直接读取

    public boolean equals(Object obj) {
        return (obj instanceof Float)
               && (floatToIntBits(((Float)obj).value) == floatToIntBits(value));
    }

针对Double:浮点类型转long,然后比大小,转换方式为将浮点存储的二进制数据单子long类型二进制直接读取

    public boolean equals(Object obj) {
        return (obj instanceof Double)
               && (doubleToLongBits(((Double)obj).value) ==
                      doubleToLongBits(value));
    }

建议:Float与Double比较大小时,使用equals,如果使用==则比较的是引用地址。

主要的混淆对象是String

针对Character:比较的是引用地址

public final boolean equals(Object obj) {
  return (this == obj);
}

针对String: 先比较地址,地址相同则为true,否则比较每一个char的地址,一个不同则为false

    public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        if (anObject instanceof String) {
            String anotherString = (String)anObject;
            int n = value.length;
            if (n == anotherString.value.length) {
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = 0;
                while (n-- != 0) {
                    if (v1[i] != v2[i])
                        return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }

示例

    public static void test4(){
        String str1 = "const";
        String str2 = "const";
        String str3 = "con" + "st";
        String str4 = "con" + new String("st");
        String str5 = new String("const");
        String str6 = str5.intern();
        String str7 = "con";
        String str8 = "st";
        String str9 = str7 + str8;
        System.out.println(str1 == str2); //true
        System.out.println(str1 == str3); //true
        System.out.println(str1 == str4); //false
        System.out.println(str4 == str5); //false
        System.out.println(str1 == str6); //true
        System.out.println(str1 == str9); //false
    }

这里比较,涉及到了常量与变量的区别:常量在编译期会直接放入常量池中,变量只会在运行时进行赋值。

编译期优化: 常量相加可被直接优化,存储的是优化后的结果。

String#intern,会从常量池中获取首次创建该字符串的地址引用,如果不存在则创建后放入,再返回该引用。注意该引用是常量池引用,与new出来的堆引用不是一个。

到此,相信大家对“Java中的==和equals的区别有哪些”有了更深的了解,不妨来实际操作一番吧!这里是创新互联网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!


当前文章:Java中的==和equals的区别有哪些
网页路径:http://ybzwz.com/article/jieeej.html