Object是所有类的祖宗
Java里所有的类都直接或间接继承自Object,它是类层次结构的根
就像人类的”共同祖先”一样,Object定义了所有对象都应该有的基本能力
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @Test public void testObjectIsRoot() {
String s = "hello"; Integer n = 42; int[] arr = {1, 2, 3};
System.out.println(s instanceof Object); System.out.println(n instanceof Object); System.out.println(arr instanceof Object);
}
|
Object的核心方法一览
| 方法 |
作用 |
是否常重写 |
equals() |
判断两个对象是否”相等” |
是 |
hashCode() |
返回对象的哈希值 |
是 |
toString() |
返回对象的字符串表示 |
是 |
clone() |
克隆对象 |
有时 |
getClass() |
获取运行时类信息 |
否 |
finalize() |
垃圾回收前调用(已废弃) |
否 |
wait()/notify() |
线程通信 |
否 |
equals():判断两个对象是否”相等”
默认的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 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| @Test public void testEquals() {
class Student { String name; int age; Student(String name, int age) { this.name = name; this.age = age; } }
Student s1 = new Student("张三", 18); Student s2 = new Student("张三", 18); System.out.println(s1.equals(s2)); System.out.println(s1 == s2); }
@Test public void testEqualsOverride() {
class Student { String name; int age; Student(String name, int age) { this.name = name; this.age = age; }
@Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Student other = (Student) obj; return this.age == other.age && Objects.equals(this.name, other.name); } }
Student s1 = new Student("张三", 18); Student s2 = new Student("张三", 18); System.out.println(s1.equals(s2)); }
|
重写equals的五大原则
自反性:a.equals(a) 必须是true
对称性:a.equals(b) 和 b.equals(a) 结果一样
传递性:a.equals(b) 且 b.equals(c),则 a.equals(c)
一致性:多次调用结果一样(对象没变的话)
非空性:a.equals(null) 必须是false
参考 运算符 中 == 和 equals 的区别,以及 String类与字符串操作 中字符串比较
hashCode():对象的”身份证号”
hashCode就像人的身份证号,用来快速定位对象在哈希表中的位置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| @Test public void testHashCode() { String s1 = "hello"; String s2 = "hello"; String s3 = new String("hello");
System.out.println(s1.hashCode()); System.out.println(s2.hashCode()); System.out.println(s3.hashCode());
Object obj1 = new Object(); Object obj2 = new Object(); System.out.println(obj1.hashCode()); System.out.println(obj2.hashCode()); }
|
hashCode和equals的契约(必须遵守!)
equals 返回true → hashCode 必须相同
hashCode 相同 → equals 不一定为true(哈希冲突)
equals 返回false → hashCode 可以相同也可以不同
为什么重写equals必须同时重写hashCode
因为HashMap、HashSet这些集合依赖这个契约来工作
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 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
| @Test public void testEqualsHashCodeContract() {
class BadStudent { String name; BadStudent(String name) { this.name = name; }
@Override public boolean equals(Object obj) { if (obj instanceof BadStudent) { return this.name.equals(((BadStudent) obj).name); } return false; } }
BadStudent s1 = new BadStudent("张三"); BadStudent s2 = new BadStudent("张三"); System.out.println(s1.equals(s2));
Set<BadStudent> set = new HashSet<>(); set.add(s1); set.add(s2); System.out.println(set.size());
System.out.println(s1.hashCode()); System.out.println(s2.hashCode()); }
@Test public void testCorrectOverride() {
class GoodStudent { String name; int age; GoodStudent(String name, int age) { this.name = name; this.age = age; }
@Override public boolean equals(Object obj) { if (this == obj) return true; if (!(obj instanceof GoodStudent)) return false; GoodStudent other = (GoodStudent) obj; return age == other.age && Objects.equals(name, other.name); }
@Override public int hashCode() { return Objects.hash(name, age); } }
GoodStudent s1 = new GoodStudent("张三", 18); GoodStudent s2 = new GoodStudent("张三", 18);
Set<GoodStudent> set = new HashSet<>(); set.add(s1); set.add(s2); System.out.println(set.size()); }
|
toString():对象的”自我介绍”
默认打印 类名@十六进制哈希值,一般看不懂,所以需要重写
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
| @Test public void testToString() {
Object obj = new Object(); System.out.println(obj);
String s = "hello"; System.out.println(s);
class Student { String name; int age; Student(String name, int age) { this.name = name; this.age = age; }
@Override public String toString() { return "Student{name='" + name + "', age=" + age + "}"; } }
Student stu = new Student("张三", 18); System.out.println(stu);
}
|
小技巧:实际开发中,IDE可以一键生成toString(),不用手写
clone():浅拷贝 vs 深拷贝
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 27 28 29 30 31 32 33 34 35
| @Test public void testClone() throws CloneNotSupportedException {
class Address implements Cloneable { String city; Address(String city) { this.city = city; } }
class Person implements Cloneable { String name; Address address;
Person(String name, Address address) { this.name = name; this.address = address; }
@Override protected Person clone() throws CloneNotSupportedException { return (Person) super.clone(); } }
Address addr = new Address("北京"); Person p1 = new Person("张三", addr); Person p2 = p1.clone();
System.out.println(p1 == p2); System.out.println(p1.name.equals(p2.name)); System.out.println(p1.address == p2.address);
p2.address.city = "上海"; System.out.println(p1.address.city); }
|
浅拷贝 vs 深拷贝
| 类型 |
基本类型字段 |
引用类型字段 |
比喻 |
| 浅拷贝 |
复制值 |
复制引用(共享同一个对象) |
抄了你的笔记,但图片是同一张 |
| 深拷贝 |
复制值 |
递归复制出新对象 |
抄了你的笔记,图片也重新画了一份 |
深拷贝的实现方式:手动clone引用字段,或者用序列化
getClass():获取运行时类信息
可以在运行时知道一个对象到底是什么类,这是反射的入口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| @Test public void testGetClass() { String s = "hello"; Integer n = 42;
System.out.println(s.getClass()); System.out.println(s.getClass().getName()); System.out.println(s.getClass().getSimpleName());
System.out.println(n.getClass());
String a = "hello"; String b = "world"; System.out.println(a.getClass() == b.getClass());
}
|
常见坑总结
| 坑 |
说明 |
怎么避免 |
| 只重写equals不重写hashCode |
HashMap/HashSet行为异常 |
两个必须一起重写 |
| equals传参类型写错 |
equals(Student s) 不是重写而是重载 |
参数类型必须是Object |
| clone默认是浅拷贝 |
引用字段共享同一个对象 |
需要深拷贝要手动处理 |
| toString忘记重写 |
打印出来看不懂的地址 |
养成重写toString的习惯 |
== 和 equals 搞混 |
参考 运算符 |
对象比较用equals |
练习题
题1:给下面的类重写equals、hashCode和toString
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 44 45 46 47
| class Book { String isbn; String title; double price;
Book(String isbn, String title, double price) { this.isbn = isbn; this.title = title; this.price = price; }
@Override public boolean equals(Object obj) { if (this == obj) return true; if (!(obj instanceof Book)) return false; Book other = (Book) obj; return Objects.equals(this.isbn, other.isbn); }
@Override public int hashCode() { return Objects.hash(isbn); }
@Override public String toString() { return "Book{isbn='" + isbn + "', title='" + title + "', price=" + price + "}"; } }
@Test public void testBook() { Book b1 = new Book("978-1", "Java入门", 59.9); Book b2 = new Book("978-1", "Java入门", 59.9); Book b3 = new Book("978-2", "Python入门", 49.9);
System.out.println(b1.equals(b2)); System.out.println(b1.equals(b3)); System.out.println(b1);
Set<Book> set = new HashSet<>(); set.add(b1); set.add(b2); System.out.println(set.size()); }
|