78條高質量編碼建議《Effective Java》(9~11)閱讀筆記

接著上期繼續看本書高質量編碼建議9~11條的閱讀筆記

78條高質量編碼建議《Effective Java》(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,人工智能能智能。

78條高質量編碼建議《Effective Java》(9~11)閱讀筆記


分享到:


相關文章: