一、Java中==和equals()的区别
1、前述
2、数据到底存在哪
在方法中声明的变量:
在类中声明的变量:
3、“==”与equals()
相同点:
不同点:
Object类中的equals():
4、String类的equals()
源代码及解析:
5、String类的特殊性
代码演示:
运行结果:
说明:
“”和new关键字创建字符串的区别:
关于toString()方法的说明:
6、手动定制equals()方法
代码及解析:
说明:
正如我们所知,一个变量,如果存储的数据是基本数据类型,那么变量所指向的就是它实际的值,但如果这个变量存储的数据是引用数据类型,那么变量所指向的就是它的引用地址(实际变量值存储在堆内存中,变量名指向的是变量的堆内存地址)。我们有太多场景需要比较两个变量的值是否相等,其中最常用的就是“==”,“==”是一种浅层次的比较,比较的仅仅是变量指向的内容,要么是实际的值,要么是引用地址。对于基本数据类型,我们自然能够得到想要的结果,但对于引用数据类型,我们所比较的仅仅是引用地址,而不是实际的变量值,这不是我们所想要的。所以我们想到了equals()方法,但实际上equals()方法原本与“==”是没有什么区别的,只不过有一些类重写了它,使它能够在比较引用数据类型的数据时比较的是实际的值,下面让我们进行一个详细的探讨;
在方法中声明的变量是局部变量,每当程序调用方法时,系统会为该方法建立一个方法栈,其所在方法栈声明的变量就存放在方法栈中,当方法执行完毕,系统会释放该方法栈,在方法中声明的变量随着方法栈的释放而被销毁,这就是局部变量只能在方法中有效的原因;
(1)当声明的是基本数据类型的变量时,其变量名及值存储在栈内存中;
(2)当声明的是引用数据类型的变量时,其变量存储在栈内存中,变量的值存储的是所指向对象的引用地址,该变量所指向的对象存储在堆内存中;
在类中声明的变量是成员变量,也叫全局变量,存放在堆中,不随着某方法执行结束而被销毁;
(1)当声明的是基本数据类型的变量时,其变量名及其值放在堆内存中;
(2)当声明的是引用数据类型的变量时,其变量名及其值也放在堆内存中,变量的值存储的也是所指向对象的引用地址,该变量所指向的对象同样存储在堆内存中;
默认情况下,比较的都是变量名直接对应的值,基本数据类型变量名对应的就是实际的值,而引用数据类型所对应的是实际值的引用地址;
equals()是一个来自Object类的方法,可以被子类重写,所以开发人员可以定制equals()方法的实际作用,使其对变量实际的值进行比较,但"=="无法做到;
public boolean equals(Object obj) {
// 我们可以看到,简单粗暴,默认的equals()方法就是通过“==”进行比较,与使用“==”直接比较没有区别
return (this == obj);
}
public boolean equals(Object anObject) {
// 如果引用地址相同,就说明内容相等(因为是同一个字符串对象,肯定相等)
if (this == anObject) {
return true;
}
// 再判断传入的对象是否是字符串实例
if (anObject instanceof String) {
// 如果是字符串,向下转型成字符串,按字符串进行操作
String anotherString = (String)anObject;
// 先判断长度是否相等
int n = value.length; // 这个长度是调用者的长度,value是调用者的字符数组
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++;
}
// 一切没问题,说明字符串的内容相同,返回true
return true;
}
}
return false;
}
package com.zibo.java.february.second;
public class MyStr {
public static void main(String[] args) {
String a = "訾博";
String b = "訾博";
String c = new String("訾博");
System.out.println(a == b);
System.out.println(a == c);
c = c.intern();
System.out.println(a == c);
System.out.println(a.equals(b));
System.out.println(a.equals(c));
}
}
true
false
true
true
true
我们前面讲到,对于引用数据类型“==”比较的是内存地址,但a和b看上去好像是两个对象,却返回true,而a和c,看上去与a和b是一个意思,但返回的是false,使用equals()方法进行比较无疑都是true,下面让我们详细说明一下使用“”和new关键字创建字符串的区别;
咱们借助上面的代码进行说明,顺带介绍一个intern()方法:
package com.zibo.java.february.second;
public class MyStr {
public static void main(String[] args) {
String a = "訾博"; // 此时,系统从堆内存中的字符串常量池中查找“訾博”,没查到,创建并返回引用地址给a
String b = "訾博"; // 此时,系统从堆内存中的字符串常量池中查找“訾博”,查到了,直接返回引用地址给b
// 也就是说,a和b指向的是同一个String对象,地址也是相同的地址
// 使用new这种方式创建字符串,系统不会从字符串常量池进行查找,而是重新创建一个并返回引用地址给c
// 所以此时,变量a和b的所存储的引用地址是同一个,但和c不是同一个
String c = new String("訾博");
System.out.println(a == b); // 所以这里比较引用地址,返回true
System.out.println(a == c); // 这里返回false
c = c.intern(); // 这里的intern()方法返回的还是“訾博”,只不过是通过""创建的“訾博”,做了这么一件事:
// 从字符串常量池中查找“訾博”,如果存在就返回其地址,在这里也就意味着将a、b和c同时指向同一个“訾博”了
System.out.println(a == c); // 所以这里变成了true
System.out.println(a.equals(b)); // 内容肯定一致
System.out.println(a.equals(c));
}
}
我们平常打印一个对象,打印的是引用地址,类似下面:
函数执行前的student:
com.zibo.java.february.first.Student@4554617c
函数执行后的student:
com.zibo.java.february.first.Student@4554617c
除非我们重写类的toString()方法,对打印内容进行定制,但是打印字符串就直接打印的是值,原因也是String自己重写了toString()方法,我们来看一眼源代码:
public String toString() {
return this; // 直接返回对象本身
}
我们再看一眼Object类的toString()方法:
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
(注意,这里是有一个问题的,重写equals()方法的时候必须重写hashCode()方法,至于为什么,我下一面博客就会专门写到,写完我把文章链接写到这!)
文章链接:为什么重写equals()方法时必须重写hashCode()方法【详解】_訾博ZiBo的博客-CSDN博客_为什么实现equals必须先实现hash方法;
package com.zibo.java.february.second;
public class MyEquals {
public static void main(String[] args) {
Student s1 = new Student("訾博", 24);
Student s2 = new Student("訾博", 24);
System.out.println(s1.equals(s2)); // true
}
}
// 这里省略了setter和getter方法
class Student{
private String name;
private int age;
public Student() {
}
public Student(String name, Integer age) {
this.name = name;
this.age = age;
}
// 咱们来手动定制equals()方法
@Override
public boolean equals(Object o) {
// 两个对象堆内存地址相同,说明是同一个对象,自然内容也相同
if(this == o){
return true;
}
// 先判断是否是一个Student的实例对象,如果是向下转型进行比较,反之直接返回false
if(!(o instanceof Student)){
return false;
}
// 向下转型,继续判断每个值是否相等
Student student = (Student)o;
return this.name.equals(student.name) && this.age == student.age;
}
}
也可以用idea自动生成equals()方法,跟上面咱自己写的类似;