- 0
- 378 words
上篇聊了 equals 和 hashCode,这两个是 Object 类中被重写得最多的方法。这篇我们把剩下的方法扫一遍——有的天天用,有的已经过时了,还有的根本不需要你自己去调用。
一、toString()——打印对象时自动调用的方法
你写 System.out.println(obj) 的时候,实际上就是在调用 obj.toString()。
public class Student {
String name = "张三";
int age = 20;
}
Student s = new Student();
System.out.println(s);
// 输出:Student@1a2b3c4d ← 类名@十六进制哈希值(Object 默认实现)
// 这玩意儿谁看得懂?
默认的 toString() 是类名@哈希值的十六进制——说白了就是对象的内部编号。调试的时候默认输出什么也看不出来,所以几乎所有自定义类都应该重写 toString():
@Override
public String toString() {
return "Student{name='" + name + "', age=" + age + "}";
}
// 再用 System.out.println(s) 输出:
// Student{name='张三', age=20}
// 这才是人能看的格式
IDE 也一样能一键生成 toString()。建议每个实体类(POJO、DTO、VO)都加上——写日志、调试、排查问题的时候,每一条日志都能看清对象的内容,省多少时间。
二、getClass()——运行时的类型信息
getClass() 返回的是这个对象的 Class 对象,里面包含了类的完整信息——类名、方法、字段、注解等等。它是 Java 反射的入口。
Student s = new Student();
Class<?> clazz = s.getClass();
System.out.println(clazz.getName()); // com.example.Student
System.out.println(clazz.getSimpleName()); // Student
System.out.println(clazz.getSuperclass()); // class java.lang.Object
上篇写 equals 的时候我们用 getClass() != obj.getClass() 做类型检查,这就是 getClass() 最典型的用法——判断两个对象是不是同一个类。
这里补充一个知识点:instanceof 和 getClass() 的区别。
class Animal {}
class Dog extends Animal {}
class Cat extends Animal {}
Animal dog = new Dog();
System.out.println(dog instanceof Dog); // true
System.out.println(dog instanceof Animal); // true(因为 Dog 继承 Animal)
System.out.println(dog.getClass() == Dog.class); // true
System.out.println(dog.getClass() == Animal.class); // false(运行时类型是 Dog)
instanceof 检查的是「是不是这个类型或子类型」;getClass() == xxx.class 检查的是「是不是精确地这个类型」。面试经常考这个区别。
三、clone()——对象的拷贝
你想复制一个对象:
Student s1 = new Student("张三", 20);
Student s2 = s1; // 这根本不是复制——s2 和 s1 指向同一个对象!
s2.setName("李四");
System.out.println(s1.getName()); // 李四 —— s1 也被改了
要真正复制一个对象,Object 提供了 clone() 方法。但它有几个限制:
- 你的类必须实现
Cloneable接口(标记接口,里面没有方法) clone()是protected的,你需要在子类中重写为 public- 默认的
clone()是浅拷贝——只能复制基本类型,引用类型只复制引用
public class Student implements Cloneable {
private String name;
private int age;
private Address address; // 引用类型
public Student(String name, int age, Address address) {
this.name = name;
this.age = age;
this.address = address;
}
@Override
public Student clone() {
try {
return (Student) super.clone(); // 浅拷贝
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
}
浅拷贝的问题:如果 address 是个引用类型,两个 Student 的 address 指向同一个地址对象——改一个另一个也跟着变。要「深拷贝」需要手动复制所有引用字段:
@Override
public Student clone() {
try {
Student cloned = (Student) super.clone();
cloned.address = this.address.clone(); // 引用字段也复制
return cloned;
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
说实话,Java 的 clone() 机制设计得有点别扭——又是 Cloneable 接口(标记而已),又是 protected 方法,还要抛异常……
现在更推荐用拷贝构造器或者工厂方法来代替 clone():
// 拷贝构造器
public Student(Student other) {
this.name = other.name;
this.age = other.age;
this.address = new Address(other.address); // 深拷贝
}
// 使用
Student s2 = new Student(s1);
清爽,可控,不依赖 Cloneable 这种怪胎。
四、finalize()——别碰这个方法
finalize() 是 Object 类中的一个方法,GC 在回收对象之前会调用它一次。听起来好像很美好——可以在对象被回收前释放资源?
但实际上:
- 它什么时候被调用不确定——GC 的时机不归你控制
- 它可能根本不被调用——程序退出时如果 GC 没跑,
finalize()就永远不会执行 - 性能差——会显著拖慢 GC 速度
Java 9 就已经标记为 @Deprecated(since="9") 了。现在要释放资源就用 try-with-resources 或者 AutoCloseable 接口。
// ✅ 正确做法
try (FileInputStream fis = new FileInputStream("test.txt")) {
// 使用 fis...
} // 自动关闭,不需要 finalize
// ❌ 别用 finalize
五、notify() / notifyAll() / wait()
这三个方法是 多线程通信 用的,不涉及多线程的时候基本用不到。等我们讲到多线程那部分的时候再展开。先知道有这么几个方法就行了。
六、三兄弟总结
| 方法 | 默认行为 | 建议 |
|---|---|---|
toString() | 类名@哈希值十六进制 | 强烈建议重写,IDE 生成即可 |
getClass() | 返回运行时的 Class<?> 对象 | 直接使用,别重写(final 方法重写不了) |
clone() | 浅拷贝,需实现 Cloneable | 优先用拷贝构造器替代 |
finalize() | GC 前调用一次 | 别用,Java 9 已弃用 |
七、Object 方法之外的补充:Objects 工具类
跟 Object 类相似但不相同的是 java.util.Objects——它是一个工具类(全是静态方法),用来处理对象相关的操作。前面我们已经用过 Objects.equals() 和 Objects.hash(),这里再补充几个好用的:
String s = null;
// Objects.toString() —— 对象转字符串,null 安全
System.out.println(Objects.toString(s)); // "null"
System.out.println(Objects.toString(s, "默认值")); // "默认值"
// Objects.requireNonNull() —— 快速判空,返回非空值或抛异常
public Student(String name) {
this.name = Objects.requireNonNull(name, "姓名不能为空");
}
// Objects.isNull() / nonNull() —— 判空
if (Objects.isNull(s)) {
System.out.println("s 是 null");
}
这些工具方法让代码更简洁,而且避免了空指针——平时写代码多留意一下 java.util 包下的工具类,能省不少事。
八、小结
toString()推荐每个实体类都重写,提升开发效率getClass()获取运行时 Class 信息,反射的入口clone()默认浅拷贝,有坑;更推荐拷贝构造器finalize()已过时,别碰Objects工具类提供了一大堆null安全的操作方法,记得用
Object 类两篇讲完。下一篇我们聊最后一个子分类——枚举与包装类。enum 关键字、Integer 缓存池、自动装箱/拆箱……全是面试常客。