4.5 Object类
Object 是所有类的祖宗——你写的任何类不写 extends,都默认继承 Object。它送每个对象三件天生自带的工具:toString(打印时说人话)、equals(判断相不相同)、hashCode(住址编号)。三件默认版都很"敷衍",实际开发里 toString 和 equals 十有八九要重写。
1. 收网:为什么空类也有 toString()?
4.1 留过一个谜:你写的空类,什么方法都没定义,sout(对象) 却不报错——本节给答案:
class Dog { } // 什么都不写的类
// 编译器等价于:
class Dog extends Object { }
任何类不写 extends,自动继承 Object。所以每个对象天生拥有 Object 的全部方法——toString()、equals()、hashCode() 都是祖传的。4.1 说"Object 万物之祖",今天正式认识这位祖宗。
这也带来一个多态的终极应用:
Object box = new Dog(); // Object 钥匙能开任何车
Object box2 = "你好"; // String 也是 Object 的子孙
Object box3 = 42; // 连 42 都行(包装类,第 6 章揭晓)
万物皆 Object——你天天调的 System.out.println(x) 能打印一切类型,就是因为它的参数类型是 Object。
2. toString():解开"类名@乱码"之谜
那串乱码是怎么来的
打印对象时你大概见过这个:
System.out.println(dog); // 输出:Dog@1b6d3586 ← 这是什么鬼?
真相(上面那张图的暗道):
println(dog) 内部自动执行了 dog.toString()
→ dog 没重写 toString,执行的是 Object 祖传默认版
→ 默认版返回:类名 + @ + 哈希值的十六进制(约等于"住址编号")
→ 所以屏幕上是 Dog@1b6d3586——不是乱码,是"类名@住址"
注意两点:① println(对象) 隐式调 toString,println(dog.toString()) 结果一模一样;② 运行的是对象自己的版本——4.4 的"运行看右边"在这里上岗。
重写 toString:让对象说人话
class Dog {
String brand;
int age;
@Override
public String toString() {
return "Dog{brand='" + brand + "', age=" + age + "}";
}
}
System.out.println(dog); // 输出:Dog{brand='旺财', age=3} ← 这才是人话
Alt + Insert(Windows)→ 选 toString() → 勾选属性 → 确定。生成完顺手补上模板没给的业务字段即可。重写时务必带上 @Override 注解——拼错方法名它会当场报错,防"重写了个寂寞"。
3. equals():判断"相同",比的是内容还是地址?
默认版:和 == 一模一样
Dog d1 = new Dog();
Dog d2 = new Dog();
System.out.println(d1.equals(d2)); // false!明明都是狗
Object 祖传的 equals 源码就一行:return this == obj;——默认比的是地址(两个 new 就是两个住址),不是内容。
== 和 equals 的完整对比表(本节考试中心)
| 写法 | 基本类型(int 等) | 引用类型(对象) |
|---|---|---|
== |
比值(8 == 8 → true) | 比地址(是不是同一个对象) |
equals |
不能用(编译报错) | 默认比地址,重写后比内容 |
一句话:基本类型比值用 ==;对象比内容用 equals(前提是重写过)。
String 是个特例,也是个大坑
String 已经替你重写过 equals(比内容),但很多人栽在 == 上:
String s1 = "abc";
String s2 = "abc";
String s3 = new String("abc");
s1 == s2 // true!字面量走"常量池",两把钥匙指向同一份
s1 == s3 // false!new 造了新地址
s1.equals(s3) // true——比内容,永远对
== 有时对有时错(取决于字符串怎么来的),靠它写出的 bug 最难查。从今天起养成肌肉记忆。
自定义类重写 equals
@Override
public boolean equals(Object obj) {
if (this == obj) return true; // 自己和自己,直接同
if (!(obj instanceof Dog)) return false; // 不是狗,不比
Dog other = (Dog) obj; // 4.4 的向下转型验货
return this.age == other.age && this.brand.equals(other.brand);
}
Alt + Insert → equals() and hashCode() 一键生成。生成版逻辑严密,读懂它比你手写收获大。
4. hashCode():混个脸熟,谜底留到集合
hashCode 返回对象的"住址编号"(int)。现在只需要记住一条规矩:
重写了 equals,就必须重写 hashCode——规则:两个对象 equals 为 true,它们的 hashCode 必须相同。
为什么?HashSet/HashMap(第 6 章集合)先按 hashCode 找格子,再用 equals 确认——如果两个"内容相同"的对象编号不同,就会被当成两个东西存两份。谜底到集合章正式收网,今天记住这条搭伴规矩就行。
5. 动手:给 Dog 装上"人话"和"识狗眼"
在 IDEA04 里新建 TestObject:
class Dog {
String brand;
int age;
public Dog(String brand, int age) {
this.brand = brand;
this.age = age;
}
@Override
public String toString() {
return "Dog{brand='" + brand + "', age=" + age + "}";
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof Dog)) return false;
Dog other = (Dog) obj;
return age == other.age && brand.equals(other.brand);
}
}
public class TestObject {
public static void main(String[] args) {
Dog d1 = new Dog("旺财", 3);
Dog d2 = new Dog("旺财", 3);
Dog d3 = new Dog("小黑", 2);
System.out.println(d1); // 重写前:Dog@xxx;重写后:Dog{brand='旺财', age=3}
System.out.println(d1.equals(d2)); // true(内容相同)
System.out.println(d1 == d2); // false(两个 new 两个地址)
System.out.println(d1.equals(d3)); // false(内容不同)
System.out.println("abc".equals(new String("abc"))); // true——String 早替你重写好了
}
}
成功标准:5 行输出依次是——人话版对象描述、true、false、false、true。第 2、3 行对照着看,"equals 比内容、== 比地址"就长这样。
三个 30 秒实验
- 删掉 toString 重写再跑——看
Dog@1b6d3586乱码回归,确认"乱码 = 默认版 toString"。 - 删掉 equals 重写再跑——
d1.equals(d2)从 true 变 false,亲眼见默认版比地址。 - 拿 int 用 equals——写
int x = 8; x.equals(8);看编译报错"int 不能被解引用"——基本类型只能用 ==,表格第一列长这样。
6. 常见误区
恰恰相反——Object 默认版就是 ==,比地址。String 让你产生"equals 天生比内容"的错觉,那是因为 String 重写过。自定义类不重写,equals 就是 ==。
int 没有方法,x.equals(8) 编译直接报错。基本类型只有 == 一条路(包装类出场后才有 equals 可用)。
那 true 是常量池的巧合(两个变量恰好指向同一份"abc"),不是 == 在比内容。换个写法 new String("abc") 它就翻脸——别赌巧合,永远用 equals。
编译器确实不拦,但集合章等着收你:内容相同的两个对象被存进 HashSet 当成两个元素——逻辑错误,程序照跑,结果不对。规矩今天立下:equals 和 hashCode 必须搭伴重写。
字符串拼接("信息:" + dog)同样隐式调 toString;日志框架打对象、调试时看变量值,背后全是它。重写 toString 是性价比最高的重写,任何实体类都值得配一个。
7. 自测题(先自己答,再展开)
任何类不写 extends 都默认继承 Object,toString/equals/hashCode 是 Object 的祖传方法,每个对象天生自带。
println 隐式调用了 dog 的 toString(),默认版(Object 提供)返回"类名@哈希值的十六进制"。在 Dog 里重写 toString(带 @Override),返回属性拼接的可读字符串即可;或 IDEA Alt+Insert 一键生成。
==:基本类型比值,引用类型比地址(是不是同一个对象)。equals:不能用于基本类型;引用类型默认比地址,重写后比内容。口诀:基本类型比值用 ==,对象比内容用 equals。
s1 == s2 是 false(new 造了新地址,s1 指向常量池、s2 指向堆里新对象);s1.equals(s2) 是 true(String 重写过 equals,比内容)。结论:字符串内容比较永远用 equals。
规则要求:equals 相同的两个对象,hashCode 必须相同。将来 HashSet/HashMap 这类集合先按 hashCode 定位格子、再用 equals 确认是否相同——只重写 equals 不重写 hashCode 时,"内容相同"的两个对象编号不同,会被当成两个元素存两份(去重失效、查找出错)。今天只需记住搭伴规矩,具体机理第 6 章集合(HashSet/HashMap)收网。
8. 下一步学什么
- ☑ 4.6 内部类 ✅ —— 四兄弟家谱:成员内部类能摸外部 private(自己人规则)、匿名内部类 = 接口/抽象类的免起名一次性实现(多态实战主场)。点此复习
相关笔记
- 4.4 多态 ← 上一篇:toString/equals 的重写全是"运行看右边"的实战
- 4.1 继承 ← "Object 万物之祖"伏笔的本尊
- 2.3 Java中的运算符 ← == 的老家:基本类型比值
- 3.2 类与对象 ← 地址、new、堆里造车:== 比地址的原理地基
- 📇 Java基础 · 索引
- 🏠 返回主页