对于这个问题,可以帮助我们很好的理解Java对象的创建,赋值以及== 和equals()的用法。
我们通过如下实例来说明,先看一个简单的代码:
public class Practice1 {
public static void main(String[] args) {
String str1=new String("hello");
String str2=new String("hello");
String str3="hello";
String str4="hello";
//结果为true
System.out.println("equals: "+str1.equals(str2));
//1.结果为true
System.out.println("equals: "+str1.equals(str3));
//2.结果为false
System.out.println("str==str1 "+(str1==str3));
//结果为true
System.out.println("str2==str1 "+(str3==str4));
}
}
在回答问题之前,我们需要再来明确某些内容,它们可以帮助我们更好的理解问题的答案。
前文叙述有点长,也可先看第三部分,如果看完之后还不是很懂,可以回来从此再看。
虽然我们都知道Java是面向对象的编程,但并非完全的面向对象。比如说Java中的基本数据类型,用int,double等创建的变量都不是对象。一般我们都是通过new 关键字来创建对象,而基本数据类型创建的变量并不能用new 的方式获取。虽然如此,但Java对基本数据类型也有相应的解决办法——封装与其相应的类,即Integer对应int,Double对应double,它们皆是为了解决基本数据类型面向对象用的。
明确这些之后,我们再来看类型是如何分配的,基本数据类型在栈中进行分配,而对象类型在堆中进行分配。基本类型之间的赋值是创建新的拷贝,而对象之间的赋值只是传递引用。所有方法的参数(基本类型除外)传递的都是引用而非本身的值。
现在,再来聊聊String s="hello";以及String s = new String("hello");
我在之前的一篇博客中简单的提到过String s="hello";(变量&数据类型),它与new不同,同时也是java中唯一不需要new就可以产生对象的途径。这种形式的赋值称为——直接量,它被放在一个叫作字符串拘留池(常量池)的地方;而new 创建的对象都放在堆上。String s="hello" 这种形式的字符串,会在JVM(Java虚拟机)中发生字符串拘留。
那什么是字符串拘留呢?我们通过一个例子来理解这种机制,当我们声明一个字符串String s="hello";时,JVM会先从常量池中查找有没有值为"hello"的对象。如果有,会把该对象赋给当前引用,也就是说原来的引用和现在的引用指向同一个对象;如果没有,则在拘留池中创建一个值为"hello"的对象,如果下一次还有类似的语句,例如String str="hello";时,又会将str指向"hello"这个对象。以这种形式声明字符串,无论有多少个都指向同一个对象。
再来说说String s = new String("hello");
这种形式创建的对象就和其他new 创建的对象一样了,每执行一次就生成一个新对象,也就是说String str = new String("hello");与s毫无关系,他们是两个独立的对象,只不过巧了,他们的值或是说内容相等。
我们也可以简单的理解为:
String str = "hello"; 先在内存中找是否有"hello"这个对象,如果有就可以直接从常量池中拿来用,不用再从内存中创建空间来存储。如果没有,就创建一块新内存存着,以后要是有对象要用就直接给它用了。
String str=new String ("hello") 就是不管内存里有没有"hello"这个对象,都新建一个对象保存"hello"。
看几个例子:
String s1 = "qibao"; // 放在常量池中,没找到,新建一个
String s2 = "qibao"; // 从常量池中查找,找到了,直接引用。s1,s2指向同一个对象
String s3 = new String("qibao"); // s3 为一个引用
String s4 = new String("qibao"); // s4 也是一个引用。虽然s3,s4对象的内容一样,但他们却占着两块地。
String s5 = "qi" + "bao"; //字符串常量相加,在编译时就会计算结果,s1 == s5 返回ture
String s6 = "qi"; String s7 = "bao"
String s8 = s6 + s7; //字符串变量相加,编译时无法计算,s1 == s8 返回false
class Person{
String name;
Person(String name) { this.name = name;}
}
Person p1 = new Person("qibao");
Person p2 = new Person("qibao");
p1.name == p2.name //返回true
先解释几个名词:
== 或 != 比较的是 栈中存放的对象引用 在堆上的地址,
即判断两对象的堆地址是否相同,即是否是指相同一个堆对象。
对于基本类型,== 和 != 是比较值。
对于对象来说,== 和 != 是比较两个引用,即判断两个对象的地址是否相同.
我们先来看Object中定义的equals()
public boolean equals(Object obj) {
return (this == obj);
}
Object.equals()使用的算法区分度高,只要两对象不是同一个就是错误的。由于所有的类都继承自Object类,所以equals()适用于所有对象。Object中的equals方法返回 == 的判断,即对象的地址判断。 虽然如此,但可以对Object.equals()进行覆盖,String类则实现覆盖。我们再来看看String.equals():
private final char value[];
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
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;
}
查看String对equals覆盖的源码会发现,String.equals()相等的条件是:比较二者同为String类型,长度相等,且字符串值完全相同,包括顺序和值,不再要求两者为同一对象。也可以理解为String.equals()将原本的String对象拆分成单个字符之间值的比较,每个字符的比较完之后返回一个最终的boolean类型的值,即将原本可能指向不同堆地址的两个对象 "间接的" 指向了同一个地址,以到达比较值的目的。
看完上边这些内容之后,我们再来看这两行代码:
String str1=new String("hello");
String str3="hello";
//1.结果为true
System.out.println("equals: "+str1.equals(str3));
//2.结果为false
System.out.println("str==str1 "+(str1==str3));
现在,我们就可以很清楚的知道为什么1 返回true:
String.equal() 只看两者是否为String,长度是否相等,以及每个字符的值是否相等,很显然str1和str2满足这三点要求,所以返回结果为真。
至于2 返回false,我们也明白为何了:
== 是对对象地址的判断,而这两种声明方式的存储形式是不同的,因此它们的地址不同,自然返回false了。
通过上文我们知道可以对Object.equals()实现覆盖,那也就意味着我们可以自己写equals方法,关于这方面的内容,我会在后面的博客覆盖equals方法中说明。
(由于本人技术有限,如有不正确的地方,欢迎大家留言指正,本人将不胜感激。)