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的区别有哪些
网站URL:http://cdiso.cn/article/jieeej.html