Object

Object类

java.lang.Object类是Java语言中的根类,即所有类的父类。它中描述的所有方法子类都可以使用。在对象实例化的时候,最终找的父类就是Object

如果一个类没有特别指定父类,那么默认则继承自Object类。

1
2
3
public class MyClass /*extends Object*/ {
// ...
}

toString

**public String toString()**:返回该对象的字符串表示。

toString方法返回该对象的字符串表示,其实该字符串内容就是对象的类型+@+内存地址值。

由于toString方法返回的结果是内存地址,而在开发中,经常需要按照对象的属性得到相应的字符串表现形式,因此也需要重写它

1
2
3
4
//Person类默认继承了Object类,所以可以使用Object类中的toString方法
Person p = new Person("张三",18);
String s = p.toString();
System.out.println(s); //com.itheima.demo01.Object.Person@16e8e0a

覆盖重写

如果不希望使用toString方法的默认行为,则可以对它进行覆盖重写。例如自定义的Person类:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Person { 
private String name;
private int age;

@Override
public String toString() {
return "Person{" + "name='" + name + '\'' + ", age=" + age + '}';
}
// 省略构造器与Getter Setter
}
Person p = new Person("张三",18);
String s = p.toString();
System.out.println(s); //Person{name='张三', age=18}

equals

**public boolean equals(Object obj)**:指示其他某个对象是否与此对象“相等”

调用成员方法equals并指定参数为另一个对象,则可以判断这两个对象是否是相同的。这里的“相同”有默认和自定义两种方式

equals方法源码:

1
2
3
public boolean equals(Object obj) {
return (this == obj);
}

如果没有覆盖重写equals方法,那么Object类中默认进行==运算符的对象地址比较,只要不是同一个对象,结果必然为false

== 对于基本类型和引用类型的作用效果是不同的:

  • 对于基本数据类型来说,== 比较的是值。
  • 对于引用数据类型来说,== 比较的是对象的内存地址。

equals() 方法存在两种使用情况:

  • 类没有重写 equals()方法:通过equals()比较该类的两个对象时,等价于通过“==”比较这两个对象,使用的默认是 Objectequals()方法
  • 类重写了 equals()方法:一般我们都重写 equals()方法来比较两个对象中的属性是否相等;若它们的属性相等,则返回 true(即,认为这两个对象相等)

如果希望进行对象的内容比较,即所有或指定的部分成员变量相同就判定两个对象相同,则可以覆盖重写equals方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class Person {  
private String name;
private int age;

@Override
public boolean equals(Object obj) {
//增加一个判断,传递的参数obj如果是this本身,直接返回true,提高程序的效率
if(obj==this){
return true;
}

//增加一个判断,传递的参数obj如果是null,直接返回false,提高程序的效率
if(obj==null){
return false;
}

//增加一个判断,防止类型转换异常ClassCastException,如list不能和Person比较
if(obj instanceof Person){
//使用向下转型,把obj转换为Person类型
Person p = (Person)obj;
//比较两个对象的属性,一个对象是this(p1),一个对象是p(obj->p2)
boolean b = this.name.equals(p.name) && this.age==p.age;
return b;
}

//不是Person类型直接返回false
return false;
}
}

这段代码充分考虑了对象为空、类型一致等问题,但方法内容并不唯一。

Objects类中的equals方法

在JDK7添加了一个Objects工具类,它提供了一些方法来操作对象,它由一些静态的实用方法组成,这些方法是null-save(空指针安全的)或null-tolerant(容忍空指针的),用于计算对象的hashcode、返回对象的字符串表示形式、比较两个对象。

在比较两个对象的时候,Object的equals方法容易抛出空指针异常,而Objects类中的equals方法就优化了这个问题。

public static boolean equals(Object a, Object b): 判断两个对象是否相等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import java.util.Objects;

String s1 = null;
String s2 = "abc";

public static boolean equals(Object a, Object b) {

return (a == b) || (a != null && a.equals(b));

}

boolean b2 = Objects.equals(s1, s2);

System.out.println(b2); //false

hashcode

hashCode方法用来返回对象的哈希值,提供该方法是为了支持哈希表,例如HashMap,HashTable等,在Object类中的代码如下

1
public native int hashCode();

这是一个native声明的本地方法,返回一个int型的整数。由于在Object中,因此每个对象都有一个默认的哈希值。

HashCode的哈希值是怎么确定的

ObjecthashCode() 方法是本地方法,也就是用 C 语言或 C++ 实现的。

⚠️ 注意:该方法在 Oracle OpenJDK8 中默认是 “使用线程局部状态来实现 Marsaglia’s xor-shift 随机数生成“, 并不是 “地址” 或者 “地址转换而来”, 不同 JDK/VM 可能不同在 Oracle OpenJDK8 中有六种生成方式 (其中第五种是返回地址), 通过添加 VM 参数: -XX:hashCode=4 启用第五种。

对象在不重写的情况下使用的是Object的equals方法和hashcode方法,从Object类的源码我们知道,默认的equals 判断的是两个对象的引用指向的是不是同一个对象;而hashcode也是根据对象地址生成一个整数数值

  • 如果两个对象的equals的结果是相等的,则两个对象的 hashCode 的返回结果也必须是相同的。
  • 任何时候重写equals,都必须同时重写hashCode。
1
2
3
4
5
6
7
8
9
10
11
12
13
@Override
public boolean equals(Object o) {
if (this == o) return true;
//getClass()!=o.getClass() 使用反射技术,判断o是否是Person类型 等效于 obj isntanceof Person
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age && Objects.equals(name, person.name);
}

@Override
public int hashCode() {
return Objects.hash(age, name);
}

为什么要有 hashCode

我们以“HashSet 如何检查重复”为例子来说明为什么要有 hashCode

下面这段内容摘自我的 Java 启蒙书《Head First Java》:

当你把对象加入 HashSet 时,HashSet 会先计算对象的 hashCode 值来判断对象加入的位置,同时也会与其他已经加入的对象的 hashCode 值作比较,如果没有相符的 hashCodeHashSet 会假设对象没有重复出现。但是如果发现有相同 hashCode 值的对象,这时会调用 equals() 方法来检查 hashCode 相等的对象是否真的相同。如果两者相同,HashSet 就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。这样我们就大大减少了 equals 的次数,相应就大大提高了执行速度。+5

那为什么 JDK 还要同时提供equals和hashCode方法呢?

这是因为在一些容器(比如 HashMapHashSet)中,有了 hashCode() 之后,判断元素是否在对应容器中的效率会更高(参考添加元素进HashSet的过程)!

我们在前面也提到了添加元素进HashSet的过程,如果 HashSet 在对比的时候,同样的 hashCode 有多个对象,它会继续使用 equals() 来判断是否真的相同。也就是说 hashCode 帮助我们大大缩小了查找成本。

那为什么不只提供 hashCode() 方法呢?

这是因为两个对象的hashCode 值相等并不代表两个对象就相等。

那为什么两个对象有相同的 hashCode 值,它们也不一定是相等的?

因为 hashCode() 所使用的哈希算法也许刚好会让多个对象传回相同的哈希值。越糟糕的哈希算法越容易碰撞,但这也与数据值域分布的特性有关(所谓哈希碰撞也就是指的是不同的对象得到相同的 hashCode )。

总结下来就是:

  • 如果两个对象的hashCode 值相等,那这两个对象不一定相等(哈希碰撞)。
  • 如果两个对象的hashCode 值相等并且equals()方法也返回 true,我们才认为这两个对象相等。
  • 如果两个对象的hashCode 值不相等,我们就可以直接认为这两个对象不相等。

为什么重写 equals() 时必须重写 hashCode() 方法?

因为两个相等的对象的 hashCode 值必须是相等。也就是说如果 equals 方法判断两个对象是相等的,那这两个对象的 hashCode 值也要相等。

如果重写 equals() 时没有重写 hashCode() 方法的话就可能会导致 equals 方法判断是相等的两个对象,hashCode 值却不相等。

思考:重写 equals() 时没有重写 hashCode() 方法的话,使用 HashMap 可能会出现什么问题。

总结

  • equals 方法判断两个对象是相等的,那这两个对象的 hashCode 值也要相等。
  • 两个对象有相同的 hashCode 值,他们也不一定是相等的(哈希碰撞)

wait,notify和notifyAll

  1. wait() 方法
    • 当一个线程调用某个对象的 wait() 方法时,它会释放该对象的锁,并使线程进入等待状态(WAITING状态),直到其他线程调用相同对象的 notify()notifyAll() 方法。
    • 在调用 wait() 方法之前,线程必须拥有该对象的锁,通常通过 synchronized 块或方法来获得。
    • 调用 wait() 方法后,线程会释放锁,使得其他线程可以进入同步块或同步方法。
    • 线程在等待期间不会消耗CPU资源,因为它不会执行任何操作。
  2. notify() 方法
    • 当一个线程调用某个对象的 notify() 方法时,它会唤醒在该对象上等待的单个线程。
    • 被唤醒的线程将从等待状态变为阻塞状态,并且再次尝试获取对象的锁。
    • 如果对象的锁可用,线程将进入就绪状态,等待CPU调度执行。
    • 如果没有线程在该对象上等待,notify() 方法不会做任何事情。
  3. notifyAll() 方法
    • 类似于 notify(),但 notifyAll() 会唤醒在该对象上等待的所有线程。
    • 所有被唤醒的线程将从等待状态变为阻塞状态,并尝试获取对象的锁。
    • 通常,notifyAll() 用于确保所有等待的线程都有机会重新竞争锁。

Object
http://example.com/2023/05/13/Java/Java常用类/Object/
作者
PALE13
发布于
2023年5月13日
许可协议