1. Java数据类型
- 基本数据类型:基本类型数据不存在“引用”概念,其值直接存储在内存中的内存栈的栈空间里面;
- 引用数据类型:简单来说,“引用”是存储在内存的内存栈上,对象本身存储在内存堆上。
- 举个例子:
Object o = new Object();
o 是存在于栈中指向堆里面的实例化对象 Object。此时 o 为 null,代表 o 指向为空,是没有地址存在的。但是 Object o = “”,o是有地址的,只是地址代表的内容为空。
2. == 与 equals() 的区别
-
对于基本数据类型之间的比较,应用“==”进行比较,比较的是他们的值;
-
对于引用数据类型,用“”进行比较,比较的是他们在内存中的存放地址。
Java所有的类都是继承于Object基类,在Object中的基类中定义了一个equals()方法,这个方法的初始行为就是比较对象的内存地址。
因此,对于没有重写 equals() 方法的类,他们之间还是比较的是内存地址的值,“”和equals() 是等价的。但我们常用的String、Integer、Date等这些类重写了equals() 方法,他们就不是比较内存地址了。案例如下:String str1 = "ab"; String str2 = "cd"; String str3 = "abcd"; String str4 = "abcd"; String str5 = new String("abcd"); String str6 = new String("abcd"); boolean b1 = str1 == str2; //false boolean b2 = (str1 + str2) == str3; //false boolean b3 = str4 == str3; //true boolean b4 = str5 == str6; //false boolean b5 = (str1 + str2).equals(str4); //true boolean b6 = str5.equals(str6); //true
3. 重写对象的 equals 方法
我们看下面这段代码
public class EqualsTest {
public static void main(String[] args) {
Cat cat1 = new Cat("red", "10", 2);
Cat cat2 = new Cat("red", "10", 2);
System.out.println(cat1 == cat2); //false
System.out.println(cat1.equals(cat2)); //false
}
}
class Cat {
private String color;
private String height;
private int age;
public Cat() {}
public Cat(String color, String height, int age) {
this.color = color;
this.height = height;
this.age = age;
}
}
从前面我们所知道的“==”和 equals() 的定义,我们打印出来的结果都得为 false,事实也是如此。如果我们定义两只 Cat 的 color、height 和 age 相等时,那这两只 Cat 就是同一只。那么我们只能通过重写 Cat 类的 equals 方法来实现我们的需求。
@Override
public boolean equals(Object object){
if (object == null) {
return false;
}
if (object instanceof Cat) {
Cat c = (Cat)object;
if (c.getColor().equals(this.getColor()) && c.getHeight().equals(this.getHeight())&& c.getAge() == this.getAge()) {
return true;
}
}
return false;
}
4. equals 和 HashCode 详解
问题引入:
我们都知道 Collection 下有个 Set 集合,它的元素是无序的且不可重复。当我们往里面存数据的时候,它是怎么保证元素不重复?或者说是依据什么来判断?如果我们使用 equals() 方法(被重写过),每增加一个元素就检查一次,当里面有成百上千个元素时,我们每增加一个元素就需要调用成百上千次 equals() 方法,这显然是不可取的,效率太低。
因此有人发明了一种哈希算法来提高从集合中查找元素的效率,这种方式将集合分成若干个存储区域,每个对象可以算出一个哈希码,将哈希码分组并分别对应到储存区域,因此根据一个对象的哈希码就可以确定对象储存在哪个区域(我的理解是就像在一根绳子上挂灯笼,比如有5个节点可以挂灯笼,根据不同的灯笼样式挂在不同的节点,如果两个灯笼样式一致,但是却不是同一个,我们就把这个灯笼挂在上一个的下面)。因此简单的来说,当我们往 Set 集合中储存元素时,会先根据元素的 hashCode()方法获取元素的哈希码,然后找到对应储存区域,并通过 equals 方法与该区域内的其他元素进行比较;
这样就可以不遍历集合中所有的元素来判重,进而提高效率。
那么问题来了,如果我们重写了对象的 equals() 方法,而不重写的它的 hashCode() 方法,那我们上述论述毫无意义;因为 Object 类中的 hashCode() 方法始终返回的是一个对象的 hash 地址,而这个地址是永远不相等的,即使重写了 equals() 方法,但一开始 hashCode() 方法返回的哈希值都不在一个区域的话那么就不会去调用 equals() 方法了。
上述我们就能得出一个结论:
equals() 相等的两个对象,hashCode() 一定相等;equals() 不相等的两个对象,hashCode() 有可能相等。
5. hashCode 使用注意事项
首先我们看个例子,我们重写了测试类的 equals() 和 hashCode() 方法,get 和 set 方法并没有贴上来:
class ObjectTest {
private int x;
private int y;
@Override
public boolean equals(Object object) {
if (object == null) {
return false;
}
if (object instanceof ObjectTest) {
ObjectTest t = (ObjectTest)object;
if (t.getX() == this.getX() && t.getY() == this.getY()) {
return true;
}
}
return false;
}
@Override
public int hashCode() {
final int temp = 11;
int result = 1;
result = temp * result + x;
result = temp * result + y;
return result;
}
}
测试:
public static void main(String[] args) {
HashSet<ObjectTest> set = new HashSet<>();
ObjectTest o1 = new ObjectTest(2, 2);
ObjectTest o2 = new ObjectTest(3, 3);
ObjectTest o3 = new ObjectTest(2, 2);
set.add(o1);
set.add(o2);
set.add(o3);
set.add(o1);
System.out.println(set.size());
}
打印结果为:2。这个很好理解,存入了o1、o2;
当我们把 hashCode 的方法注释掉,打印结果为:3。因为Object中的hashCode方法返回的是对象本地内存地址的换算结果,o1与o3虽然equals相同,但是hashCode不同,就不会去验证equals的方法;