接著上期繼續看本書高質量編碼建議9~11條的閱讀筆記
9.覆蓋equals時總要覆蓋hashcode方法
如果這個類僅僅是重寫了equals方法而沒有重寫hashCode,那麼這個類和基於散列的集合類一起工作時就會出現問題。
首先明確一個概念,兩個對象使用equals返回true,則它們的hashCode也一定相等;如果兩個對象的hashCode相等,則它們的equals則不一定相等。這個概念和散列函數相關,在《哈希》這篇博客(請參考本期第五篇)裡我曾談到過有關散列(哈希)相關的知識。
如何實現hashCode,當然你可以使hashCode返回一個固定的數值,任何對象的hashCode都是一個固定的數值,這沒有問題。但當它與基於散列的集合類一起工作時,這些元素將具有相同的散列碼,進而使得所有對象都被映射到統一散列桶中,使得散列表退化為鏈表。散列函數應該如何編寫在《哈希》 一文中有提到常用的散列算法,這裡不再敘述。
9.始終要覆蓋toString
這條建議我在實際當中遇到過,因為當時幾乎並沒有人去重寫toString方法,使得我不得不在後來去將幾乎所有的POJO類的toString方法都重寫了。原因在於在有的場景下會打印一條日誌,日誌的內容就是POJO類的屬性字段值,這個時候toString的意義很明顯的就體現出來了,好在eclipse能按照一定的格式自動生成toString方法。有的類是自己已經重新實現了toString方法例如集合類。
9.謹慎的覆蓋clone
按照書中的話來講,能不重寫clone就不要去重寫,因為它帶來的問題太多了。我們暫且不討論這裡面的陷阱有多少,只從對Java基礎知識的掌握程度來說明什麼是clone,以及什麼是“深拷貝”和“淺拷貝”。
首先觀察以下代碼,並思考對象在內存中的分配以及引用的變化:
public class Student {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
public class Main {
public static void main(String[] args) throws Exception{
Student stu = new Student("kevin", 23);
Student stu2 = stu;
stu2.setAge(0);
System.out.println(stu.getAge());
}
}
這是一段很簡單的代碼,Student對象實例stu、stu2在內存中的分配及引用分別如下圖所示:
所以代碼中出現修改stu2實例的age字段時,stu中的age字段也被修改了,原因很簡單因為它們的引用指向的都是同一個對象實例。
那如果我們想在實例化一個name=”kevin”,age=23的Student實例怎麼辦呢?當然可以再寫一段Student stu2 = new Student(“kevin”, 23);如果再重新構造一個對象實例很複雜,能不能直接複製呢?顯然,使Student實現Cloneable接口並重寫clone方法即可,注意此時的重寫clone方法在裡面僅有一句代碼即是即調用父類的clone方法,而不是自定義實現:
public class Student implements Cloneable{
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
protected Student clone()
throws CloneNotSupportedExcepti on {
return (Student)super.clone();
}
}
public class Main {
public static void main(String[] args) throws Exception{
Student stu = new Student("kevin", 23);
Student stu2 = stu.clone();
stu2.setAge(0);
System.out.println(stu.getAge());
}
}
調用clone方法產生的對象實例並不是之前的實例,而是在堆上重新實例化了一個各個參數類型值都相同的實例,所以此時修改stu2的age字段並不會影響到stu,看起來clone就是一個構造器的作用——創建實例。
上面我們僅僅是說明了什麼是clone,接下來我們接著來講解什麼是“深拷貝”和“淺拷貝”。
在上面的例子Student類中,我們新增一個引用型變量Test類:
public class Student implements Cloneable{
private String name;
private int age;
private Test test;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getTest() {
return test;
}
public void setTest(Test test) {
this.test= test;
}
@Override
protected Student clone()
throws CloneNotSupportedException {
return (Student)super.clone();
}
}
public class Main {
public static void main(String[] args) throws Exception{
Student stu = new Student("kevin", 23);
Student stu2 = stu.clone();
stu2.setAge(0);
System.out.println(stu.getAge());
}
}
實際上測試這段代碼可知,clone出來的stu2確實和stu是兩個對象實例,但它們的成員變量實際上確是指向的同一個引用(通過比較hashCode可知),這也就是所謂的“淺拷貝”。對應的“深拷貝”則是所有的成員變量都會真正的做一份拷貝。怎麼做到“深拷貝”,則是要求將類中的所有引用型變量都要clone。
**
* 深拷貝
* Created by zhaozhiyong on 2017/09/20.
*/
public class Student implements Cloneable{
private String name;
private int age;
private Test test;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Test getTest() {
return test;
}
public void setTest(Test test) {
this.test = test;
}
@Override
protected Object clone()
throws CloneNotSupportedException {
Student stu = (Student)super.clone();
stu.test = test.clone(); //Test類也要繼承Cloneable
return stu;
}
}
書中是不建議自定義重寫clone方法的,如果非要重寫書中總結為一句話:clone方法就是一個構造器,你必須確保它不會傷害到原始的對象,並確保正確地創建被克隆對象中的約束條件。
再說一個與本條目無關的點,查看Cloneable接口實際上可以發現裡面什麼方法都沒有,clone方法卻來自Object類,繼承了Cloneable接口為什麼就能重寫clone方法了呢?原因在於clone方法在Object類中的修飾符是protected,而Cloneable接口和Object處於同一個包下,熟悉修飾符的都知道protected的權限限定在同一個包下或者其子類。Cloneable和Object同屬於一個包,Cloneable自然能繼承clone方法,繼承了Cloneable接口的成為了它的子類同樣也就繼承了clone方法。
今天就這麼多了,明天持續更新,下期內容:
12.考慮實現comparable接口
13.使類和成員的可訪問性最小化
14.在公有類中使用訪問方法而非公有域
15.使可變性最小化
16.複合優先於集成
看看我們的資料:4000G java架構師資料
主要內容:21套Java精品高級課架構課包含:java8新特性,P2P金融項目,程序設計,功能設計,數據庫設計,架構設計,web安全,高併發,高性能、高可用、高可擴展,分佈式,集群,電商,緩存,性能調優,設計模式,項目實戰,工作流,程序調優,負載均衡,Solr集群與應用,主從複製,中間件,全文檢索,Spring boot,Spring cloud,Dubbo,Elasticsearch,Redis,ActiveMQ,Nginx,Mycat,Spring,MongoDB,ZeroMQ,Git,Nosql,Jvm,Mecached,Netty,Nio,Mina,Nutch,Webservice,Activiti,Shiro,Tomcat,大型分佈式電商實戰等高端視頻課程......,Android、ios、微信小程序移動app應用,以及2017年最火的大數據(hadoop、hbase、hive、spark、storm等),python,人工智能能智能。