Java-Object类
每日一言
Object类
Java中Object类是所有类的父类,也就是说Java中所有的类都继承了Object,子类可以使用Object的所有方法。
当你在创建一个类时,如果没有显示的继承任何类,那么他会自动继承Object类:
1 |
|
以上两种创建类的效果完全一致。
Object提供了一些通用的方法,Java中的所有类都有这些方法,所以可以实现一些通用的功能,也可以通过重写来实现自定义的操作。
Object.clone()
clone() 方法会返回调用对象的一份潜拷贝。
参数
- 无
返回值
返回一个Object类的拷贝
浅拷贝的特点:
- 基本数据类型 :直接复制其值。
- 引用类型 :复制的是引用地址,而不是引用对象本身。这意味着拷贝后的对象和原对象共享同一个引用对象。
注意:由于 Object 本身没有实现 Cloneable 接口,所以不重写 clone 方法并且进行调用的话会发生 CloneNotSupportedException 异常。
我们看如下例子:
1 |
|
程序输出如下:
1 |
|
修改拷贝对象的属性后,Work类的引用由于是潜拷贝,只拷贝了引用对象的地址,所以对于拷贝对象的修改会影响原对象。
这时可能有人就会产生疑问了:
String 类也是一个引用对象,为什么对于拷贝对象的修改没有影响原对象?
这恰好是String类的不可变性造成的,在String类那一章节中我们讲到String 类是一个不可变类,因此在对String 类赋值或者修改时,会自动创建一个新的String 对象。正是由于这个原因,当我们对拷贝对象的String 类赋值时,其实是将其引用指向了另一个地址,原对象指向的String类自然不受影响。
但是倘若我们使用的是StringBuffer类呢?
1 |
|
那么运行结果如下:
1 |
|
Object.equals()
equals()方法判断两个对象的引用是否相等。
1 |
|
参数
- Object - 要比较的对象
返回值
- boolean :相等返回true,否则返回false
默认的equals()只会比较两个对象的引用即地址是否相同,不会比较内容。
1 |
|
重写equals()
大多数情况下,我们想要的都不是比较对象引用是否相等,而是对象中一些内容是否相等。这时我们就需要重写equals()。
我们来看下面的例子:
1 |
|
或许作为java初学者的你会觉得这是一个可能略有瑕疵但总体没有大问题的equals()方法。但其实如上的写法是一个完全错误的equals重写。
首先这不是Java中的重写,而是重载。当传入不同类型的参数时会调用不同参数列表的equals()方法:
1
2
3
4
5
6
7
8
9
10
11public class Main{
public static void main (String args[]){
Person person1 = new Person("John", 25, "123 Main St");
Person person2 = new Person("John", 25, "123 Main St");
Object person3 = new Person("John", 25, "123 Main St");
//这里调用euqals(Person person)方法
System.out.println(person1.equals(person2)); // true
//这里调用euqals(Object obj)方法
System.out.println(person1.equals(person3)); // false
}
}由于我们没有重写equals()方法,所以在集合类数据结构中:
HashSet
、HashMap
、HashTable
这些集合中时,会导致错误重复数据或错误的查找行为,因为他们仍然在使用euqals(Object obj)
方法1
2
3
4
5
6
7
8
9
10
11
12
13public class Main{
public static void main (String args[]){
Person person1 = new Person("John", 25, "123 Main St");
Person person2 = new Person("John", 25, "123 Main St");
Object person3 = new Person("John", 25, "123 Main St");
HashSet<Person> set = new HashSet<>();
set.add(person1);
set.add(person2); // 这行代码会调用equals方法,判断person1和person2是否相等
System.out.println(set.size()); // 结果为 2,而不是 1,因为 equals() 未被重写
}
}潜在的
NullPointerException
风险,没有对传入的参数进行null
检查1
2
3
4
5
6
7
8
9public class Main{
public static void main (String args[]){
Person person1 = new Person("John", 25, "123 Main St");
Person person2 = null;
//这里调用euqals(Person person)方法
System.out.println(person1.equals(person2)); // true
}
}上面的程序编译时并不会报错,但是运行时会奔溃并打印错误:
java.lang.NullPointerException
缺少类型检查,没有确认传入的类型是否为Person类
由此,我们可以得到正确重写equals()的结果如下:
1 |
|
Object.getClass()方法
Object getClass() 方法用于获取对象的运行时对象的类。
语法
1 |
|
参数
- 无
返回值
返回对象的类
特点
- 返回值 :
- 返回一个
Class<?>
对象,表示调用该方法的对象的运行时类。
- 不可重写 :
getClass()
方法是final
的,不能被子类重写。
- 用途 :
- 获取对象的运行时类型。
- 用于反射操作(如获取类的字段、方法、构造函数等)。
- 运行时类型 :
- 即使对象被赋值给父类引用,
getClass()
方法仍然返回实际的运行时类,而不是引用变量的类型。
1 |
|
输出示例:
1 |
|
同时Java提供了另一种方式来获取类的 Class
对象,直接通过类名后加 .class
的方式。这是一种静态的方式可以直接通过类名来获取 Class
对象。
与instanceof的区别
instanceof
检查的是对象是否是某个类或其子类的实例。getClass()
检查的是对象的 精确类型 。
1 |
|
instanceof
只要前面的实例是后面的类或子类的实例就会返回true
而 getClass()
只有在两个类对象完全相等时才会true
一个编译错误
对于如上的例子,可能有些同学会改为如下:
1 |
|
但是会发现一个报错导致无法编译通过:
1 |
|
两段代码的区别只有animal实例的引用为Animal类和Dog类
- 当引用类型为
Animal
类时,编译器将animal.getClass()
的返回类型视为Class<? extends Animal>
- 即
animal
对象的类为Animal
类或者Animal
类的子类
- 即
- 当引用类型为
Dog
类型时,编译器将animal.getClass()
的返回类型视为Class<? extends Dog>
- 即
animal
对象的类为Dog
类或者Dog
类的子类
- 即
而Animal.class返回的类为 Class<Animal>
Java编译器认为:将 Class<? extends Dog>
与 Class<Animal>
进行比较是没有意义的,因为一个 Class<? extends Dog>
类并不可能是 Animal
的实例。
只有 Class<? extends Animal>
类才有可能是 Animal
类的实例。这一点也与我们在Java多态中学习到的:
- 父类的引用可以指向子类的实例
- 子类引用不能指向父类实例
相契合。
Object.hashCode()方法
语法
1 |
|
参数
- 无
返回值
返回对象哈希值,是一个整数,表示在哈希表中的位置。
hashCode方法的主要用途在于:
- 为了支持基于哈希的集合类:如
HashMap
、HashSet
、Hashtable
等- 定位桶的位置
- 这些集合需要快速查找、存储和检索对象
- 先通过哈希码缩小查找范围,再使用equals()方法确认其相等性
hashCode()与equals()的关系
一致性约定:Java中约定如果两个对象 equals()
方法比较是相等的,那么它们的 hashCode()
方法必须返回相同的值
反之不成立:两个对象有相同的哈希码,它们不一定相等(”哈希冲突”)
重写hashCode()
最简单也是最推荐的重写hashCode()的方法是直接使用 Objects.hash()
1 |
|
- 不包含所有影响equals()的字段 :
hashCode()
应包含所有在equals()
中用于比较的字段 - 使用可变字段 :尽量避免使用可变字段计算哈希码,否则对象插入集合后修改这些字段会导致查找失败
- 过度复杂 :哈希码计算应该简单高效,不要加入过多的复杂逻辑
- 忽略性能 :对于频繁使用的类,可以考虑缓存哈希码值
由于剩下的方法涉及多线程,博主还不会,先不写了以后有机会再补全
Object.wait()
wait()方法让当前线程进入等待状态。直到其他线程调用此对象的notify()方法。或notifyAll()方法。
语法
1 |
|
参数
- 无
返回值
没有返回值