|
2020-06-12
简述
Object 方法包括 5 个非 final 类型的方法,分别是:clone、hashCode、equals、toString、finalize 方法;包括 4 个 final 方法,分别是 getClass、wait、notify、notifyAll 方法。其中 clone 方法是 protected 方法,finalize 方法自 Java 9 之后被废弃。
1. clone 方法 1.1 源码 1 @HotSpotIntrinsicCandidate protected native Object clone() throws CloneNotSupportedException;
说明:调用该方法实现一个对象的浅复制,创建并且返回此对象的副本(“副本”的准确含义可能依赖于对象的类)。 Object.clone() 方法是一个 protected 方法,类只有实现 java.lang.Cloneable 接口,并重写 Object.clone() 方法才能使用该 clone 方法,否则抛出 CloneNotSupportedException 。
1.2 clone 与 copy 的区别 假设我们有一个 Person 对象,并假设 Person 类实现了Cloneable 接口并重写了 clone 方法。
1 2 3 4 5 Person a = new Person(); //copy 的做法通常为: Person b = a; //clone 的做法通常为: Person c = a.clone();
说明: (1)copy 是将对象 a 的引用赋值给对象 b,赋值之后对象 a 和对象 b 都指向同一个引用 a。 (2)clone 是实现对象的浅拷贝,产生一个新的对象,对象 c 与 对象 a 不指向同一个引用。
clone 在内存中实际操作是:将对象 a 的内存,拷贝一个副本,并重新分配一块内存区域用于保存副本。
1.3 浅拷贝和深拷贝
前面我们提到,clone 在内存中的实际操作时将一个对象的内存拷贝出一个副本、并重新分配一个内存用于保存副本。由于原对象(被拷贝的对象)属性可能存在两种值传递类型,分别是值传递和引用传递,对副本的操作可能对原对象造成影响(改变副本的引用传递属性的值,由于引用传递导致原对象的响应值同样改变)。
如果一个对象包含引用传递类型的属性,直接拷贝对象,不做特殊处理,这种拷贝称为浅拷贝。若一个对象不存在引用传递类型的数据,那也就不区别什么浅拷贝和深拷贝,可以称为浅拷贝也可以称为深拷贝。
示例:
定义2个类:Person类 包含 Book属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class Person implements Cloneable { private String name; private Book book; //省略 constructor 、 getter 、 setter 方法 @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", book=" + book + '}'; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class Book { private String bookName; private String author; //省略 constructor 、 getter 、 setter 方法 @Override public String toString() { return "Book{" + "bookName='" + bookName + '\'' + ", author='" + author + '\'' + '}'; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class Main { //主方法: public static void main(String[] args) throws Exception { Book effectiveJava = new Book("Effective Java", "Joshua Bloch"); Person sungm = new Person("sungm", effectiveJava); Person sunhw = (Person) sungm.clone(); System.out.println("对象 sungm 的信息:" + sungm); System.out.println("对象 sunhw 的信息:" + sunhw); System.out.println("对象 sungm 与对象 sunhw 是否相等:" + (sungm == sunhw)); System.out.println("-----------------------------------------"); //改变对象 sunhw 的 book 属性 sunhw.getBook().setBookName("Vue.js"); sunhw.getBook().setAuthor("尤雨溪"); System.out.println("对象 sunhw 的信息:" + sunhw); System.out.println("对象 sungm 的信息:" + sungm); } }
1 2 3 4 5 6 7 /* 输出结果 */ 对象 sungm 的信息:Person{name='sungm', book=Book{bookName='Effective Java', author='Joshua Bloch'}} 对象 sunhw 的信息:Person{name='sungm', book=Book{bookName='Effective Java', author='Joshua Bloch'}} 对象 sungm 与对象 sunhw 是否相等:false ----------------------------------------- 对象 sunhw 的信息:Person{name='sungm', book=Book{bookName='Vue.js', author='尤雨溪'}} 对象 sungm 的信息:Person{name='sungm', book=Book{bookName='Vue.js', author='尤雨溪'}}
从输出结果可以看出:对象 sunhw 改变了的属性 book 的内容, 对对象 sungm 造成了影响。因为两个对象的 book 属性保存的是同一个引用,造成这种差异是由于对象进行了浅拷贝。
进行深拷贝示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 Person implements Cloneable { private String name; private Book book; //省略 constructor 、 getter 、 setter 方法 @Override protected Object clone() throws CloneNotSupportedException { Person person = (Person) super.clone(); person.book = (Book) book.clone(); return person; } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", book=" + book + '}'; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Book implements Cloneable{ private String bookName; private String author; //省略 constructor 、 getter 、 setter 方法 @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } @Override public String toString() { return "Book{" + "bookName='" + bookName + '\'' + ", author='" + author + '\'' + '}'; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class Main { public static void main(String[] args) throws Exception { Book effectiveJava = new Book("Effective Java", "Joshua Bloch"); Person sungm = new Person("sungm", effectiveJava); Person sunhw = (Person) sungm.clone(); System.out.println("对象 sungm 的信息:" + sungm); System.out.println("对象 sunhw 的信息:" + sunhw); System.out.println("对象 sungm 与对象 sunhw 是否相等:" + (sungm == sunhw)); System.out.println("-----------------------------------------"); //改变对象 sunhw 的 book 属性 sunhw.getBook().setBookName("Vue.js"); sunhw.getBook().setAuthor("尤雨溪"); System.out.println("对象 sunhw 的信息:" + sunhw); System.out.println("对象 sungm 的信息:" + sungm); } }
1 2 3 4 5 6 7 /*运行结果*/ 对象 sungm 的信息:Person{name='sungm', book=Book{bookName='Effective Java', author='Joshua Bloch'}} 对象 sunhw 的信息:Person{name='sungm', book=Book{bookName='Effective Java', author='Joshua Bloch'}} 对象 sungm 与对象 sunhw 是否相等:false ----------------------------------------- 对象 sunhw 的信息:Person{name='sungm', book=Book{bookName='Vue.js', author='尤雨溪'}} 对象 sungm 的信息:Person{name='sungm', book=Book{bookName='Effective Java', author='Joshua Bloch'}}
说明:进行深拷贝一般有2中方式 (1)将属性对象实现 java.lang.Cloneable 接口并重写 clone 方法,然后在原始类中修改 clone方法。 (2)实现 java.io.Serializable 接口,通过序列化和反序列号拷贝对象。
2. hashCode 方法
返回对象的 Hash 值 (也称散列码)。对象的散列码是为了更好的支持基于哈希机制的 Java 集合类,例如:HashMap、HashSet、HashTable。
2.1 通用约定
(1)在 Java 程序执行期间,多次调用该方法应该返回相同的值,前提是未修改在 equals 方法中使用的信息。 (2)如果 2 个对象通过 equals 方法判定为 2 个对象相等、那么他们返回的 Hash 值也应该相等。 (3)对于 2 个对象来说,如果使用 equals 方法返回 false,那么这两个对象的 hashCode 值不要求一定不同(可以相同,可以不同),但是如果不同则可以提高应用的性能。 (4)对于 Object 类来说,不同 Object 对象的 hash 值是不同的、其 hash 值返回的是内存地址。
说明:鉴于第 (2) 条约定,如果重写了 equals 方法,那就要求重写 hashCode 方法。
3. equals 方法
判断两个对象是否相等。仅当两个对象引用的是同一个内存地址,即同一个对象,该方法返回 true。若不满足指向同一个内存地址、即使两个对象的内容相同,也会返回 false。
源码:
1 public boolean equals(Object obj) { return (this == obj); }
规则: (1)自反性:对于任意非空对象, x.equals(x) 应该返回 true (2)对称性:对于任意非空对象,若 x.equals(y) 返回 true,则 y.equals(x) 也应该返回 true (3)传递性:对于任意非空对象,若 x.equals(y) 返回 true、y.equals(z) 也返回 true ,则 x.equals(z) 也应该返回 true (4)一致性:若 x.equals(y) 返回 true,那第二次、第三次调用也应该返回 true,前提是未修改两个对象。
4. toString 方法
返回对象的字符串表现形式(类全名及无符号十六进制的 Hash 值)。API 建议所有的子类都重写该方法。
源码:
1 public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()); }
5. finalize 方法
该方法自 Java 9 之后被废弃。
源码:
1 @Deprecated(since="9") protected void finalize() throws Throwable { }
说明:该方法并非一两句话能解释清楚,这里引入一篇博客、可供参考学习。https://www.jianshu.com/p/9d2788fffd5f
6. getClass 方法
返回运行时该对象的 class 对象,返回的 class 对象是被表示对象的类的 static synchronized 方法锁定的对象。
该方法一般常见于反射技术。
7. wait 方法
导致当前线程等待,可设置等待的毫秒数,知道其他线程调用 notify 方法或者调用该对象的 notifyAll 方法唤醒该线程。
源码:
1 2 3 4 5 public final void wait() throws InterruptedException { wait(0L); } public final native void wait(long timeoutMillis) throws InterruptedException;
8. notify 方法
唤醒正在此对象的监听器上等待的单个线程。如果该对象的监听器等待的线程存在多个、则唤醒其中一个线程,该线程的唤醒是随机的。
源码:
1 @HotSpotIntrinsicCandidate public final native void notify();
9. notifyAll 方法
唤醒正在此对象的监听器上等待的所有线程。
源码:
1 @HotSpotIntrinsicCandidate public final native void notifyAll();