(Java中equals和==的区别 - BarneyZhang - 博客园)
==比较两个变量本身的值,即两个对象在内存中的首地址。
java中,对象的首地址是它在内存中存放的起始地址,它后面的地址是用来存放它所包含的各个属性的地址,所以内存中会用多个内存块来存放对象的各个参数,而通过这个首地址就可以找到该对象,进而可以找到该对象的各个属性。
java中的数据类型,可分为两类:
byte,short,char,int,long,float,double,boolean,他们之间的比较,应用双等号(==),比较的是他们的值。
int a =1; int b =1; //true System.out.println(a==b);
当他们用(==)进行比较的时候,比较的是他们在内存中的存放地址,所以,除非是同一个new出来的对象,他们比较后的结果为true,否则比较后结果为false。
JAVA当中所有的类都是继承于Object这个基类的,在Object中的基类中定义了一个equals的方法,这个方法的初始行为是比较对象的内存地址。
public boolean equals(Object obj) { return (this == obj); }
对于复合数据类型之间进行equals比较,在没有覆写equals方法的情况下,他们之间的比较还是基于他们在内存中的存放位置的地址值的,因为Object的equals方法也是用双等号(==)进行比较的,所以比较后的结果跟双等号(==)的结果相同。
但在一些类库当中这个方法被覆盖掉了,如String,Integer,Date在这些类当中equals有其自身的实现,而不再是比较类在堆内存中的存放地址了。
public class TestString { public static void main(String[] args) { String s1 = "Monday"; String s2 = "Monday"; //true if (s1 == s2) { System.out.println("s1 == s2"); } else { System.out.println("s1 != s2"); } } }
编译并运行程序,输出:s1 == s2说明:s1 与 s2 引用同一个 String 对象 -- "Monday"!
public class TestString2 { public static void main(String[] args) { String s1 = "Monday"; String s2 = new String("Monday"); //地址不一样 if (s1 == s2) { System.out.println("s1 == s2"); } else { System.out.println("s1 != s2"); } //内容相同 if (s1.equals(s2)) { System.out.println("s1 equals s2"); } else { System.out.println("s1 not equals s2"); } } }
s1 != s2 s1 equals s2 说明:s1 s2分别引用了两个"Monday"String对象
原来,程序在运行的时候会创建一个字符串缓冲池。
第一段程序。当使用 s1 = "Monday" 这样的表达是创建字符串的时候,程序首先会在这个String缓冲池中寻找相同值的对象,s1先被放到了池中,所以在s2被创建的时候,程序找到了具有相同值的 s1。然后s2引用s1所引用的对象"Monday"
第二段程序中,使用了 new 操作符,他明白的告诉程序:"我要一个新的!不要旧的!"
于是一个新的"Monday"Sting对象被创建在内存中。他们的值相同,但是位置不同,一个在池中游泳一个在岸边休息。哎呀,真是资源浪费,明明是一样的非要分开做什么呢?
public class TestString3 { public static void main(String[] args) { String s1 = "Monday"; String s2 = new String("Monday"); s2 = s2.intern(); if (s1 == s2) { System.out.println("s1 == s2"); } else { System.out.println("s1 != s2"); } if (s1.equals(s2)) { System.out.println("s1 equals s2"); } else { System.out.println("s1 not equals s2"); } } }
这次加入:s2 = s2.intern();
程序输出:
s1 == s2 s1 equals s2
原 来,java.lang.String的intern()方法比如"abc".intern()方法的返回值还是字符串"abc",表面上看起来好像这个方 法没什么用处。
但实际上,它做了个小动作:检查字符串池里是否存在"abc"这么一个字符串,如果存在,就返回池里的字符串;如果不存在,该方法会 把"abc"添加到字符串池中,然后再返回它的引用。 )
public class TestString4 { public static void main(String[] args) { String s3 = "abc", s4 ="abc" ; s1 = new String("abc"); s2 = new String("abc"); System.out.println("s1==s2:"+(s1==s2)); System.out.println("s1==s3:"+(s1==s3)); System.out.println("s3==s4:"+(s3==s4)); System.out.println("s1.equals(s2):"+(s1.equals(s2))); System.out.println("s1.equals(s3):"+(s1.equals(s3))); System.out.println("s3.equals(s4):"+(s3.equals(s4))); } }
package base01; import com.sun.scenario.effect.impl.sw.sse.SSEBlend_SRC_OUTPeer; import java.lang.reflect.Field; public class StringTest { public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, ClassNotFoundException { String str1 = "abc"; String str2 = new String("abc"); String str3 = str2.intern(); String str4 = "abc"; String str5 = new String("abc".intern()); System.out.println(str1 == str2); System.out.println(str2 == str3); System.out.println(str1 == str3); System.out.println(str1==str4); System.out.println(str1==str5); System.out.println(System.identityHashCode(str1)); System.out.println(System.identityHashCode(str2)); System.out.println(System.identityHashCode(str3)); System.out.println(System.identityHashCode(str4)); System.out.println(System.identityHashCode(str5)); System.out.println("--------------------------"); Class> clazz = Class.forName("java.lang.String"); Field[] declaredFields = clazz.getDeclaredFields(); Field field = null; for (int i = 0; i < declaredFields.length; i++) { if (declaredFields[i].getName().equals("value")) { field = declaredFields[i]; } } field.setAccessible(true); //获取str1的value字段类型是char基本类型 Object o1 = field.get(str1); //获取str2的value字段类型是char基本类型 Object o2 = field.get(str2); //获取str2的value字段类型是char基本类型 Object o3 = field.get(str3); System.out.println(o1.hashCode()); System.out.println(o2.hashCode()); System.out.println(o3.hashCode()); System.out.println(System.identityHashCode(o1)); System.out.println(System.identityHashCode(o2)); System.out.println(System.identityHashCode(o3)); System.out.println(o1==o2); System.out.println(o2==o3); char[] a = new char[3]; a[0] = 'a'; a[1] = 'b'; a[2] = 'c'; System.out.println(a.hashCode()); char[] b = new char[3]; b[0] = 'a'; b[1] = 'b'; b[2] = 'c'; System.out.println(b.hashCode()); char[] c = {'a','b','c'}; System.out.println(c.hashCode()); char[] d = (char[]) o1; System.out.println(d.hashCode()); /*** 为什么上面的(o1,o2,o3)hashCode和下面的(a,b,c,d)hashCode不一样? 注意看String的构造方法: public String(String original) { this.value = original.value; this.hash = original.hash; } 将传入的original的value字段给了当前要创建的String的value value的声明如下: private final char value[]; 这意味着什么呢? String str1 = "abc"; String str2 = new String("abc"); String str3 = new String("abc".intern()); str1创建了1个abc放入了字符串常量池 str2使用构造方法创建了1个String对象传入的是"abc" str3使用构造方法创建了1个String对象传入的是"abc".intern() 那么str2中的"abc"和str3中的"abc"是1个吗? 答案是1个 String str2 = new String("abc"); 相当于 String str3 = new String("abc".intern()); 所以o1=o2=o3 */ } }
结果输出
false false true true false 1300109446 1020371697 1300109446 1300109446 789451787 -------------------------- 1229416514 1229416514 1229416514 1229416514 1229416514 1229416514 true true 2016447921 666988784 1414644648 1229416514
当代码中使用第一种方式创建字符串对象时,相当于“abc”.intern();
JVM 首先会检查该对象是否在字符串常量池中,如果在,就返回该对象引用,否则新的字符串将在常量池中被创建。这种方式可以减少同一个值的字符串对象的重复创建,节约内存。
String str = new String(“abc”) 这种方式,首先在编译类文件时,常量字符串"abc"将会被放入到常量结构中,在类加载时,“abc"将会在常量池中创建;
其次,在调用 new 时,JVM 命令将会调用 String 的构造函数,同时引用常量池中的"abc” 字符串,在堆内存中创建一个 String 对象;最后,str 将引用 String 对象。
这里附上一个你可能会想到的经典反例。
平常编程时,对一个 String 对象 str 赋值“hello”,然后又让 str 值为“world”,这个时候 str 的值变成了“world”。那么 str 值确实改变了,为什么我还说 String 对象不可变呢?
首先,我来解释下什么是对象和对象引用。
Java 初学者往往对此存在误区,在 Java 中要比较两个对象是否相等,往往是用 ==,判断的是两个对象的地址值是否相等。
这是因为 str 只是 String 对象的引用,并不是对象本身。对象在内存中是一块内存地址,str 则是一个指向该内存地址的引用。
所以在刚刚我们说的这个例子中,第一次赋值的时候,创建了一个“hello”对象,str 引用指向“hello”对象的引用地址;第二次赋值的时候,又重新创建了一个对象“world”,str 引用指向了“world”,但“hello”对象依然存在于内存中。
也就是说 str 并不是对象,而只是一个对象引用。真正的对象依然还在内存中,没有被改变。
String str1= "abc"; //相当于“abc”.intern();会检查该对象是否在字符串常量池中,如果在,就返回该对象引用,否则新的字符串将 //在常量池中被创建。 String str2= new String("abc"); //在编译类文件时,"abc"常量字符串将会放入到常量结构中,在类加载时,“abc"将会在常量池中创建; //其次,在调用 new 时,JVM 命令将会调用 String 的构造函数new一个String 对象 //String 对象会引用常量池中的"abc”字符串的属性value[]。 //在堆内存中创建一个 String 对象;最后,str2 将引用 String 对象。 //这解释了为什么str1和str2的value的hashcode是一样的1229416514。 //因为char数组是复用的。 //因为char数组是复用的。 通过new对象的方式创建字符串对象str2,引用地址存放在堆内存中,str1则存放在字符串常量池中。 str1 == str2?显然是false //会检查该对象是否在字符串常量池中,如果在,就返回该对象引用,否则新的字符串将 //在常量池中被创建。因为str1已经将字符串常量放入常量池所以 str1==str3 String str3= str2.intern(); 通过new对象的方式创建字符串对象str2,引用地址存放在堆内存中,abc则存放在字符串常量池中。 str3是放在字符串常量池中str2引用地址存放在堆内存中所以2者不相等