Object类

!!! note 目录

Object类

一、Object类

在Java中,Object 类是所有类的根类。也就是说,所有的类都是直接或间接地继承自 Object 类。
Object 类定义了一些所有对象都共享的方法,这些方法可以在任何类的对象上调用。

下面是一些 Object 类中常用的方法:

  1. equals(Object obj): 用于比较两个对象是否相等。默认情况下,equals() 方法比较的是对象的引用是否相同,但是可以在子类中重写该方法来定义自定义的相等性比较逻辑。

  2. hashCode(): 返回对象的哈希码值。这个方法通常与 equals() 方法一起使用,确保相等的对象具有相同的哈希码。

  3. toString(): 返回对象的字符串表示。默认情况下,toString() 方法返回一个由类名和对象的哈希码组成的字符串

  4. getClass(): 返回对象的运行时类的引用,即 Class 对象。

  5. clone(): 用于创建并返回一个对象的副本。要实现对象的克隆,需要在子类中实现 Cloneable 接口,并重写 clone() 方法。

  6. finalize(): 在垃圾收集器删除对象之前调用。可以在子类中重写该方法以执行资源清理等操作。

  7. notify(), notifyAll(), wait(): 这些方法是用于多线程编程的,用于线程之间的通信和同步。

  8. getClassLoader(): 返回对象的类加载器。

二、toString方法

2.1、Object类中的toString()方法。

对Object类中的toString不满意可以重写。

当println()输出的是一个引用的时候,会自动调用 “引用.toString()”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//println底层源码
public void println(Object x) {
String s = String.valueOf(x);
if (getClass() == PrintStream.class) {
// 自第一次调用以来,需要再次应用 String.valueOf
// might return null
writeln(String.valueOf(s));
} else {
synchronized (this) {
print(s);
newLine();
}
}
}

//valueOf底层源码
public static String valueOf(Object obj) {
return (obj == null) ? "null" : obj.toString(); //这里对空引用进行了处理,不会报空异常。
}
1
2
3
4
5
6
7
8
9
10

public class Test {
public static void main(String[] args) {
Car car = new Car();
car=null;
System.out.println(car); //输出null,源代码对null进行了处理。
System.out.println(car.toString()); //输出空指针异常。
//由源码可以看出。
}
}

三、equals方法

3.1、Object类中的equals方法

Java中的equals()方法用于比较两个对象的内容是否相等。
在Object类中,equals()方法的默认实现是比较两个对象的引用是否相等(即是否指向同一块内存地址),这相当于使用==运算符进行比较。

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

3.2 ==运算符

==运算规则:比较两个变量中保存的值是否相等。

1
2
3
4
5
6
7
8
9
10
11
class Test {
public static void main(String[] args) {
//==
int a = 10;
int b = 10;
System.out.println(a == b); //true
Object object1 = new Object();
Object object2 = new Object();
System.out.println(object1 == object2); //false,两个引用保存的值不同,即地址不同。
}
}

因为equal对于对象而言比较的是地址,即引用变量存储的值。但大多数我们想比较的是内容,所以要重写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
//EG:
/**
* 比较两个日期对象的内容是否相等。
*
* @param obj 要比较的对象
* @return 如果两个日期对象的年、月、日都相等,则返回true;否则返回false
* 注意:重写equals方法传参必须是Object,若不是则不能构成重写而是重载。
*/
@Override
public boolean equals(Object obj) {
// 如果传入的对象为null,则返回false
if(obj == null) return false;

// 如果传入的对象是当前对象的引用,直接返回true
if(this == obj) return true;

// 如果传入的对象是Date类型的实例,则进行内容比较
if(obj instanceof Date){
Date d = (Date) obj; //向下转型,涉及Date独特的方法。
return this.year == d.year && this.month == d.month && this.day == d.day;
}

// 如果传入的对象不是Date类型的实例,则返回false
return false;
}

四、hashCode方法(入门)

关于Object类的hashCode()方法:

  • hashCode:返回一个对象的哈希值,通常作为在哈希表中查找该对象的键值。
  • Object类的默认实现是根据对象的内存地址生成一个哈希码(即将对象的内存地址转换为整数作为哈希值)。
  • hashCode()方法是为了HashMap、Hashtable、HashSet等集合类进行优化而设置的,以便更快地查找和存储对象
  • hashCode()方法在Object类中的默认实现:
  • public native int hashCode();
  • 这是一个本地方法,底层调用了C++写的动态链接库程序:xxx.dll

五、finalize方法(已过时,作为了解)

finalize:当java对象被回收时,由GC自动调用被回收对象的finalize方法,通常在该方法中完成销毁前的准备。从Java9开始,这个方法被标记已过时,不建议使用。作为了解。

  • 可以在finalize中进行关闭连接操作。
    1
    2
    //在Object类中是这样实现的:很显然,这个方法是需要子类重写的。
    protected void finalize() throws Throwable { }
1
2
3
4
5
6
7
8
//EG
public class Person{

@Override
protected void finalize() throws Throwable {
System.out.println(this + "即将被回收");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Test3 {

public static void main(String[] args) {
for (int i = 0; i < 10000; i++) {
Person p1 = new Person();
p1 = null;

// 建议启动垃圾回收器(这只是建议启动垃圾回收器)
if(i % 1000 == 0){
System.gc();
}
}
}
}

六、clone方法

Object 类中,clone() 方法被声明为 protected。它的签名如下:

1
protected native Object clone() throws CloneNotSupportedException;

clone() 方法允许创建并返回一个对象的副本。但是,需要注意以下几点:

  1. clone() 方法在默认情况下是 protected 的,这意味着只有类本身或其子类可以调用这个方法。因此,如果你希望在类外部调用 clone() 方法,必须在类中重新定义这个方法并将其设置为 public
  2. 调用 clone() 方法时,被克隆的类必须实现 Cloneable 接口。否则,将会抛出 CloneNotSupportedException 异常。
  3. clone() 方法的实现通常由 native 关键字修饰,这表示它的实现是由底层的本地代码完成的。这使得 clone() 方法能够访问对象的内部状态,从而创建对象的精确副本。

使用 clone() 方法需要谨慎,因为它是浅拷贝,即它只复制了对象本身以及其引用的内部数据结构。如果对象包含其他对象的引用,那么这些引用仍然指向相同的内存地址。在这种情况下,你可能需要实现深拷贝来确保所有对象及其引用的对象都被正确复制。

6.1、浅克隆

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
package com.camellia.object2;
//标志接口就像是一张空白的身份证,没有任何个人信息,只有一张照片和一串特定的标签。
//当一个类实现了某个标志接口,就像给这个类的身份证上贴上了特定的标签,告诉别人这个类具备了某种特定的能力或性质。
//比如,如果一个类实现了 `Cloneable` 接口,就像给这个类的身份证上贴上了“可以被复制”的标签,这样其他人就知道这个类的对象可以使用 `clone()` 方法进行复制了。

public class User implements Cloneable{ //凡事参加克隆的对象,必须实现一个标志接口:java.lang.Cloneable
private int age;

public User() {
}

public User(int age) {
this.age = age;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

@Override
public String toString() {
return "User{" +
"age=" + age +
'}';
}

@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}

}
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
//浅克隆
public class UserTest {
public static void main(String[] args) throws CloneNotSupportedException {
// 创建User对象
User user = new User(20);
System.out.println(user);

// 克隆一个user对象
// 报错原因:因为Object类中的clone()方法是protected修饰的。
// protected修饰的只能在:本类,同包,子类中访问。
// 但是以下这行代码不满足以上所说条件。
// 这是一种浅克隆/浅拷贝。
Object obj = user.clone();
System.out.println(user);

// 修改克隆之后的对象的age属性
User copyUser = (User) obj;
copyUser.setAge(100);
System.out.println("克隆之后的新对象的年龄:" + copyUser.getAge());

System.out.println("原始对象的年龄:" + user.getAge());
UserTest userTest = new UserTest();
userTest.clone();

}
}

6.2、深克隆

深克隆解决的问题是在复制对象时,确保对象及其所有引用的对象都被完全复制,而不仅仅是复制了对象本身。
通常,当我们进行对象的复制时,如果对象包含了其他对象的引用,浅复制只是复制引用而不是复制引用的对象本身,
这样就会导致新对象和原对象共享相同的引用对象,一旦其中一个对象修改了共享的引用对象,另一个对象也会受到影响,这可能会导致意外的行为或错误的结果。

6.2.1、浅克隆问题举例

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
39
40
41
//EG:浅克隆问题
package com.camellia.object2;

public class Address implements Cloneable{
private String city;
private String street;


@Override
public String toString() {
return "Address{" +
"city='" + city + '\'' +
", street='" + street + '\'' +
'}';
}

public Address() {
}

public Address(String city, String street) {
this.city = city;
this.street = street;
}

public String getCity() {
return city;
}

public void setCity(String city) {
this.city = city;
}

public String getStreet() {
return street;
}

public void setStreet(String street) {
this.street = street;
}
}

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
39
40
41
42
43
package com.powernode.javase.oop41;

public class User implements Cloneable{
private String name;
private Address addr; //问题所在,包含其他对象的引用。

public User() {
}

public User(String name, Address addr) {
this.name = name;
this.addr = addr;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Address getAddr() {
return addr;
}

public void setAddr(Address addr) {
this.addr = addr;
}

@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", addr=" + addr +
'}';
}

@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.powernode.javase.oop41;

public class Test {
public static void main(String[] args) throws CloneNotSupportedException {

// 创建住址对象
Address a = new Address("北京", "海淀");
// 创建User对象
User user1 = new User("李四", a);

// 克隆一个User对象
User user2 = (User)user1.clone();

System.out.println(user1);
System.out.println(user2);

user2.getAddr().setCity("天津");
System.out.println("===================================");

System.out.println(user1); //原本的值受到影响。
System.out.println(user2);
}
}

6.2.2、深克隆举例

1
2
3
4
5
6
7
8
9
10
11
//改为深克隆,修改User clone方法。
@Override
public Object clone() throws CloneNotSupportedException {
// 重写方法,让其达到深克隆的效果。
// User要克隆,User对象关联的Address对象也需要克隆一份。
Address copyAddr = (Address)this.getAddr().clone();

User copyUser = (User)super.clone();
copyUser.setAddr(copyAddr);
return copyUser;
}
1
2
3
4
5
6
//同时Address也要重写clone方法。
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}