Java-Object类

每日一言

Object类

Java中Object类是所有类的父类,也就是说Java中所有的类都继承了Object,子类可以使用Object的所有方法。

当你在创建一个类时,如果没有显示的继承任何类,那么他会自动继承Object类:

1
2
3
4
5
6
 public class Runoob extends Object{

}
public class Runoob {

}

以上两种创建类的效果完全一致。

Object提供了一些通用的方法,Java中的所有类都有这些方法,所以可以实现一些通用的功能,也可以通过重写来实现自定义的操作。

Object.clone()

clone() 方法会返回调用对象的一份潜拷贝

参数

返回值

返回一个Object类的拷贝

浅拷贝的特点:

  1. 基本数据类型 :直接复制其值。
  2. 引用类型 :复制的是引用地址,而不是引用对象本身。这意味着拷贝后的对象和原对象共享同一个引用对象。

注意:由于 Object 本身没有实现 Cloneable 接口,所以不重写 clone 方法并且进行调用的话会发生 CloneNotSupportedException 异常。

我们看如下例子:

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
30
31
32
33
34
35
36
37
38
class Work{
String name;
int salary;
}

class Person implements Cloneable{
String name;
Work work;

public static void main(String[] args) {
// 创建一个 Person 对象并设置其属性
Person person = new Person();
person.name = "John Doe";
person.work = new Work();
person.work.name = "Software Engineer";
person.work.salary = 100000;

// 创建一个Person 的拷贝
try{
Person personCopy = (Person)person.clone();

//修改拷贝对象的属性
personCopy.name = "ulna";
//修改引用对象内的属性
personCopy.work.name = "Software Engineer 2";
personCopy.work.salary = 200000;
// 打印原对象和拷贝对象的属性
System.out.println("Original Person: " + person.name + ", Work: " + person.work.name + ", Salary: " + person.work.salary);
System.out.println("Copied Person: " + personCopy.name + ", Work: " + personCopy.work.name + ", Salary: " + personCopy.work.salary);
}
catch (Exception e) {
System.out.println(e);
}

}
}


程序输出如下:

1
2
Original Person: John Doe, Work: Software Engineer 2, Salary: 200000
Copied Person: ulna, Work: Software Engineer 2, Salary: 200000

修改拷贝对象的属性后,Work类的引用由于是潜拷贝,只拷贝了引用对象的地址,所以对于拷贝对象的修改会影响原对象。

这时可能有人就会产生疑问了:

String 类也是一个引用对象,为什么对于拷贝对象的修改没有影响原对象?

这恰好是String类的不可变性造成的,在String类那一章节中我们讲到String 类是一个不可变类,因此在对String 类赋值或者修改时,会自动创建一个新的String 对象。正是由于这个原因,当我们对拷贝对象的String 类赋值时,其实是将其引用指向了另一个地址,原对象指向的String类自然不受影响。

但是倘若我们使用的是StringBuffer类呢?

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
30
31
32
33
34
35
36
37
class Work{
StringBuffer name;
int salary;
}

class Person implements Cloneable{
StringBuffer name;
Work work;

public static void main(String[] args) {
// 创建一个 Person 对象并设置其属性
Person person = new Person();
person.name = new StringBuffer("John Doe");
person.work = new Work();
person.work.name = new StringBuffer("Software Engineer");
person.work.salary = 100000;

// 创建一个Person 的拷贝
try{
Person personCopy = (Person)person.clone();

//修改拷贝对象的属性
personCopy.name.replace(0, personCopy.name.length() , "ulna");
//修改引用对象内的属性
personCopy.work.name.replace(0,personCopy.work.name.length(),"Software Engineer 2");
personCopy.work.salary = 200000;
// 打印原对象和拷贝对象的属性
System.out.println("Original Person: " + person.name + ", Work: " + person.work.name + ", Salary: " + person.work.salary);
System.out.println("Copied Person: " + personCopy.name + ", Work: " + personCopy.work.name + ", Salary: " + personCopy.work.salary);
}
catch (Exception e) {
System.out.println(e);
}

}
}

那么运行结果如下:

1
2
Original Person: ulna, Work: Software Engineer 2, Salary: 200000
Copied Person: ulna, Work: Software Engineer 2, Salary: 200000

Object.equals()

equals()方法判断两个对象的引用是否相等。

1
object.equals(Object obj)

参数

  • Object - 要比较的对象

返回值

  • boolean :相等返回true,否则返回false

默认的equals()只会比较两个对象的引用即地址是否相同,不会比较内容。

1
2
3
4
5
6
7
Object obj1 = new Object();
Object obj2 = new Object();
Object obj3 = obj1;


System.out.println(obj1.equals(obj2)); // false
System.out.println(obj1.equals(obj3)); // true

重写equals()

大多数情况下,我们想要的都不是比较对象引用是否相等,而是对象中一些内容是否相等。这时我们就需要重写equals()。

我们来看下面的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Person
{
String name;
int age;
String address;

public Person(String name, int age, String address) {
this.name = name;
this.age = age;
this.address = address;
}

public boolean equals(Person person) {
if(super.equals(person)) return true; // 检查是否是同一个对象
return age == person.age && name.equals(person.name) && address.equals(person.address);
}
}

或许作为java初学者的你会觉得这是一个可能略有瑕疵但总体没有大问题的equals()方法。但其实如上的写法是一个完全错误的equals重写。

  1. 首先这不是Java中的重写,而是重载。当传入不同类型的参数时会调用不同参数列表的equals()方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public 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
    }
    }
  2. 由于我们没有重写equals()方法,所以在集合类数据结构中:HashSetHashMapHashTable这些集合中时,会导致错误重复数据或错误的查找行为,因为他们仍然在使用 euqals(Object obj)方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public 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() 未被重写
    }
    }
  3. 潜在的 NullPointerException风险,没有对传入的参数进行 null检查

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public 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

  4. 缺少类型检查,没有确认传入的类型是否为Person类

由此,我们可以得到正确重写equals()的结果如下:

1
2
3
4
5
6
7
8
9
10
11
@Override
public boolean equals(Object obj) {
if (this == obj) return true; //如果是同一个对象,直接返回true
if (obj == null || getClass() != obj.getClass()) return false; //如果obj为null或者不是同一个类,返回false

Person person = (Person) obj; //将Object类型转换为Person类型
//比较属性值
if (age != person.age) return false;
if (name != null ? !name.equals(person.name) : person.name != null) return false;
return address != null ? address.equals(person.address) : person.address == null;
}

Object.getClass()方法

Object getClass() 方法用于获取对象的运行时对象的类。

语法

1
object.getClass()

参数

返回值

返回对象的类

特点

  1. 返回值
  • 返回一个 Class<?> 对象,表示调用该方法的对象的运行时类。
  1. 不可重写
  • getClass() 方法是 final 的,不能被子类重写
  1. 用途
  • 获取对象的运行时类型。
  • 用于反射操作(如获取类的字段、方法、构造函数等)。
  1. 运行时类型
  • 即使对象被赋值给父类引用,getClass() 方法仍然返回实际的运行时类,而不是引用变量的类型。
1
2
3
4
5
6
7
8
9
10
11
12
13
class Animal {
}

class Dog extends Animal {
}

public class Main {
public static void main(String[] args) {
Animal animal = new Dog(); // 父类引用指向子类对象
System.out.println("运行时类: " + animal.getClass().getName());
System.out.println("是否为 Dog 类: " + (animal.getClass() == Dog.class));
}
}

输出示例:

1
2
运行时类: Dog
是否为 Dog 类: true

同时Java提供了另一种方式来获取类的 Class对象,直接通过类名后加 .class 的方式。这是一种静态的方式可以直接通过类名来获取 Class 对象。

与instanceof的区别

  • instanceof 检查的是对象是否是某个类或其子类的实例。
  • getClass() 检查的是对象的 精确类型
1
2
3
Animal animal = new Dog();
System.out.println(animal instanceof Animal); // true
System.out.println(animal.getClass() == Animal.class); // false

instanceof 只要前面的实例是后面的类或子类的实例就会返回true

getClass()只有在两个类对象完全相等时才会true

一个编译错误

对于如上的例子,可能有些同学会改为如下:

1
2
3
Dog animal = new Dog();
System.out.println(animal instanceof Animal); // true
System.out.println(animal.getClass() == Animal.class); // false

但是会发现一个报错导致无法编译通过:

1
Incompatible operand types Class<capture#1-of ? extends Dog> and Class<Animal>

两段代码的区别只有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
object.hashCode()

参数

返回值

返回对象哈希值,是一个整数,表示在哈希表中的位置。

hashCode方法的主要用途在于:

  • 为了支持基于哈希的集合类:如 HashMapHashSetHashtable
    • 定位桶的位置
  • 这些集合需要快速查找、存储和检索对象
    • 先通过哈希码缩小查找范围,再使用equals()方法确认其相等性

hashCode()与equals()的关系

一致性约定:Java中约定如果两个对象 equals()方法比较是相等的,那么它们的 hashCode()方法必须返回相同的值

反之不成立:两个对象有相同的哈希码,它们不一定相等(”哈希冲突”)

重写hashCode()

最简单也是最推荐的重写hashCode()的方法是直接使用 Objects.hash()

1
2
3
4
@Override
public int hashCode() {
return Objects.hash(field1, field2, field3);
}
  1. 不包含所有影响equals()的字段hashCode()应包含所有在 equals()中用于比较的字段
  2. 使用可变字段 :尽量避免使用可变字段计算哈希码,否则对象插入集合后修改这些字段会导致查找失败
  3. 过度复杂 :哈希码计算应该简单高效,不要加入过多的复杂逻辑
  4. 忽略性能 :对于频繁使用的类,可以考虑缓存哈希码值

由于剩下的方法涉及多线程,博主还不会,先不写了以后有机会再补全

Object.wait()

wait()方法让当前线程进入等待状态。直到其他线程调用此对象的notify()方法。或notifyAll()方法。

语法

1
public final void wait()

参数

返回值

没有返回值


Java-Object类
http://blog.ulna520.com/2025/04/06/Java-Object类_20250406_160811/
Veröffentlicht am
April 6, 2025
Urheberrechtshinweis