1. 程式人生 > >《Effective Java》第二章 對於所有物件都通用的方法

《Effective Java》第二章 對於所有物件都通用的方法

接下來繼續講第二章,第8-12條。

第8條:覆蓋equals時請遵守通用約定

equals 時Object類的一個非final方法,一般是表示類的例項物件是否相同,也就是物件的地址是否相等。

但是某些時候卻要重寫Object.equals方法。即類需要有“邏輯相等”,也就是值類,這都需要重寫equals方法。這樣這個類的例項可以用做Map的key中。有一種值類就不需要重寫equals,就是單例模式的類,至始至終也就一個物件。

在覆蓋equals時需要遵守的幾個約定:自反性、對稱性、傳遞性、一致性、非null。

第9條:覆蓋equals時總要覆蓋hashode

每個重寫類equals方法的類都必須要重寫hashCode方法,不然在改類將不能和基於雜湊的集合【HashMap、HashSet】一起正常使用。物件的根據equals方法判斷時相等的,其hashcode肯定也是相等的。即判斷2個物件是否相等,先比較hashcode是否相等,若相等則在比較equals是否相等。

看下面的一個例子。

package com.example.demo2;

import java.util.Objects;

public class Student {

    private int age;
    private String name;
    private int grade;

    public Student(int age, String name, int grade) {
        this.age = age;
        this.name = name;
        this.grade = grade;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Student)) return false;
        Student student = (Student) o;
        return age == student.age &&
                grade == student.grade &&
                Objects.equals(name, student.name);
    }

 
}

當把這個類和HashMap一起使用時

Map<Student,String> stu = new HashMap<>();
        stu.put(new Student(12,"jack",99),"Jack");

    期望的時stu.get(new Student(12,"jack",99)) 返回的時Jack,但是實際上返回的時null,主要的原因就是沒有重寫hashCode,從而導致2個物件例項有不同的hashcode值。一個好的雜湊函式就是為每個不同的物件產出不同的雜湊碼。

一般一個理想的雜湊函式設計方案:

1、把某個非零的常數值,比如說17,儲存在一個名為result的int型別的變數中

2、對於物件只能夠的每個關鍵字f,完成下面的步驟

a、為該域計算int型別的雜湊碼C

b、按下面的計算公式,result = 31 * result + c

3、返回result

 public int hashCode() {

        int result = 17;
        result = 31 * result + age;
        result = 31 * result + Integer.valueOf(name);
        result  = 31 * result + grade;
        return result;
    }

但是現在的IDE已經非常的強大了,可以利用快捷鍵自動重寫equals和hashcode方法了,但是其中的原理我們還是需要掌握的。

第10條:始終要覆蓋toString

java.lang.Object也提供了toString方法的一個實現,但是它格式可讀性不好,它包含類的名稱,一個一個“@”符號,接著是haahcode的無符號的十六進位制表示法。雖然遵守toString的約定不如遵守equals、hashcode的約定這麼重要,但是提供好的toString方法可以讓類的使用更加的舒適。

第11條:謹慎地覆蓋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方法,而不是自定義實現:

  1. publicclass Student implements Cloneable{  
  2.     private String name;  
  3.     privateint age;  
  4.     public Student(String name, int age) {  
  5.         this.name = name;  
  6.         this.age = age;  
  7.     }  
  8.     public String getName() {  
  9.         return name;  
  10.     }  
  11.     publicvoid setName(String name) {  
  12.         this.name = name;  
  13.     }  
  14.     publicint getAge() {  
  15.         return age;  
  16.     }  
  17.     publicvoid setAge(int age) {  
  18.         this.age = age;  
  19.     }  
  20.     @Override
  21.     protected Student clone()   
  22.                 throws CloneNotSupportedException {  
  23.         return (Student)super.clone();  
  24.     }  
  25. }  
  26. publicclass Main {  
  27.     publicstaticvoid main(String[] args) throws Exception{  
  28.         Student stu = new Student("kevin"23);  
  29.         Student stu2 = stu.clone();  
  30.         stu2.setAge(0);  
  31.         System.out.println(stu.getAge());  
  32.     }  
  33. }  
呼叫clone方法產生的物件例項並不是之前的例項,而是在堆上重新例項化了一個各個引數型別值都相同的例項,所以此時修改stu2的age欄位並不會影響到stu,看起來clone就是一個構造器的作用 -- 建立例項。



上面我們僅僅是說明了什麼是clone,接下來我們接著來講解什麼是“深拷貝”和“淺拷貝”。

  在上面的例子Student類中,我們新增一個引用型變數Test類:

  1. publicclass Student implements Cloneable{  
  2.     private String name;  
  3.     privateint age;  
  4.     private Test test;  
  5.     public Student(String name, int age) {  
  6.         this.name = name;  
  7.         this.age = age;  
  8.     }  
  9.     public String getName() {  
  10.         return name;  
  11.     }  
  12.     publicvoid setName(String name) {  
  13.         this.name = name;  
  14.     }  
  15.     publicint getAge() {  
  16.         return age;  
  17.     }  
  18.     publicvoid setAge(int age) {  
  19.         this.age = age;  
  20.     }  
  21.     public String getTest() {  
  22.         return test;  
  23.     }  
  24.     publicvoid setTest(Test test) {  
  25.         this.test= test;  
  26.     }  
  27.     @Override
  28.     protected Student clone()   
  29.                   throws CloneNotSupportedException {  
  30.         return (Student)super.clone();  
  31.     }  
  32. }  

  1. publicclass Main {  
  2.     publicstaticvoid main(String[] args) throws Exception{  
  3.         Student stu = new Student("kevin"23);  
  4.         Student stu2 = stu.clone();  
  5.         stu2.setAge(0);  
  6.         System.out.println(stu.getAge());  
  7.     }  
  8. }  
實際上測試這段程式碼可知,clone出來的stu2確實和stu是兩個物件例項,但它們的成員變數實際上確是指向的同一個引用(通過比較hashCode可知),這也就是所謂的“淺拷貝”。對應的“深拷貝”則是所有的成員變數都會真正的做一份拷貝。怎麼做到“深拷貝”,則是要求將類中的所有引用型變數都要clone。
  1. /** 
  2.  * 深拷貝 
  3.  *  
  4.  */
  5. publicclass Student implements Cloneable{  
  6.     private String name;  
  7.     privateint age;  
  8.     private Test test;  
  9.     public Student(String name, int age) {  
  10.         this.name = name;  
  11.         this.age = age;  
  12.     }  
  13.     public String getName() {  
  14.         return name;  
  15.     }  
  16.     publicvoid setName(String name) {  
  17.         this.name = name;  
  18.     }  
  19.     

    相關推薦

    Effective Java第二 對於所有物件通用方法

    接下來繼續講第二章,第8-12條。第8條:覆蓋equals時請遵守通用約定equals 時Object類的一個非final方法,一般是表示類的例項物件是否相同,也就是物件的地址是否相等。但是某些時候卻要重寫Object.equals方法。即類需要有“邏輯相等”,也就是值類,這

    Effective Java》:對於所有物件通用方法

    本系列皆為讀書筆記,“好記性不如爛筆頭”,勤看,也要勤記錄。此篇讀書筆記來自《Effective Java》。儘管Object是一個具體類,但設計它主要是為了擴充套件。它所有的非final方法都有明確的通用規定。任何一個類,在覆蓋這些方法的時候,都有責任去遵守這些通用規定,如

    effective java中文版第三 對於所有物件通用方法

    道一聲坑爹。。。。上週末剛把這章整理了。。。忘了儲存了。。。迫於強迫症。。。。不得不再寫一遍。。但是也一帶而過。。。。只是為了哥的強迫症 第8條 覆蓋equals時請遵守通用約定 1,自反性 2,對稱性 3,傳遞性 第9條 覆蓋equals時總要覆蓋hashCode(這條重點記住)

    effective java-讀書筆記-第三 對於所有物件通用方法

    第三章 對於所有物件都通用的方法 所有非final方法(equals、hashCode、toString、clone、finalize)都有明確的通用約定,因為它們被設計成是要被覆蓋的,如果不遵守,基於雜湊的集合(HashMap、HashSet、HashTable)可

    Effective Java》第3 對於所有物件通用方法

    Object類預設為所有類的基類,其雖然為一個具體的類,但是它的設計主要是為了擴充套件,而它的所有非final的方法(equals, hashCode, toString, clone和finalize)都有明確的通用約定, 如果在自定義類時要重寫這些方法,需要注意主動遵守

    Effective java 學習】第三對於所有物件通用方法

    第八條:覆蓋equals是請遵守通用約定 滿足下列四個條件之一,就不需要覆蓋equals方法: 類的每個例項本質上都已唯一的。不包括代表值的類,如:Integer,String等,Object提供的equals方法就夠用了 不關心是否提供了“邏輯相等”的測試功能。對

    Effective Java讀書筆記 -- 第三對於所有物件通用方法

    儘管Object是一個具體類,但是設計Object類主要是為了擴充套件。它的所有非final方法(equals、hashCode、toString、clone和finalize)都有明確的通用約定,因為它們就是被設計成要被覆蓋的。第八條:覆蓋equals時請遵守通用約定   

    Effective Java學習筆記(二)對於所有物件通用方法

    Object是一個具體類,但是設計他主要是為了擴充套件,他所有的非final方法(equals,toString,hashCode,clone,finalize)都是要被覆蓋的,並且任何一個類覆蓋非final方法時都要遵守通用原則,以便其他遵守這些預定的類能夠一同運作,

    Effective java筆記-對於所有物件通用方法

    對於所有物件都通用的方法 第8條 覆蓋equals時請遵守通用約定 滿足以下任何一個條件,可以不覆蓋equals: 類的每個例項本質上都是唯一的。(如Thread) 不關心是否提供了“邏輯相等”的測試功能(如Random) 超類已經覆蓋了equals,從

    【讀書筆記】《Effective Java》(2)--對於所有物件通用方法

    又讀了一章,之前一直覺得Java的體系很完善了,讀了這一章,發現原來Java平臺本身也有一些設計不周到的地方,而且有些地方因為已經成為公開API的一部分還不好改,相信繼續讀下去對Java的瞭解會更深一步的。 昨天下載了VS Code,嘗試了一下,感覺比subl

    Effective Java 總結(二) 對於所有物件通用方法

    在改寫equals的時候請遵守通用約定 不改寫equals方法時,每個例項只與自身相等,如果滿足一下任意一個條件,則不需要改寫equals方法: 一個類的例項本質上都是唯一的 不關心類是否支援“邏輯相等”功能 超類已經改寫了equals方法 類是私有的,並且可以確定

    對於所有物件通用方法(equals)

    1.在改寫equals的時候請遵守通用約定。 1)一個類的每個例項本質上都是惟一的。 2)不關心一個類是否提供了“邏輯相等”的測試功能。 3)超類改寫了equals,從超類繼承過來的行為對於子類也是合適的。public class CaseInsentiveString

    對於所有物件通用方法

    8.覆蓋equals時請遵守通用規定 需要滿足的條件:  類的每個例項本質上都是唯一的。 不關心類是否提供了“邏輯相等(logical equality)”的測試功能。 超類已經覆蓋了equals,從超類繼承過來的行為對於子類也是合適的。 類是私有的或是包級私有的,可以確

    9. 【對於所有物件通用方法】重寫equals方法時一定也要重寫hashCode方法

    本文是《Effective Java》讀書筆記第9條,其中內容可能會結合實際應用情況或參考其他資料進行補充或調整。 在每個覆蓋了equals方法的類中,一定也要覆蓋hasCode方法。否則會導致該類無法結合所有基於雜湊的集合(比如HashMap、HashS

    2、對於所有物件通用方法

    Object的設計主要是為了擴充套件。它的非final方法(equals、hashCode、toString、clone、finalize)都有明確的通用約定(general contract)。任何一個類覆蓋這些方法都有責任遵守這些約定。 覆蓋equal

    Java高階系列——使用所有物件通用方法

    一、介紹 通過如何建立和銷燬物件一文,我們已經知道Java是一個面向物件的程式語言,Java類層次結構的頂部是Object類,Java中每個單獨的類都隱式的繼承於該類。因此,所有的類都繼承了在Object類中定義的一系列方法,這其中的一些非常重要的方法如下:

    Effective Java對於所有物件通用方法

    Java中所有的類都預設繼承Object類,而Object的設計主要是為了擴充套件。它的所有的非final方法(equals、hashCode、toString、clone和finalize)都有明確的通用約定(general contract),因為它們被設計

    <Thinking in java 第二> 一切是對象

    運算 參數 區分 bigint 高精度 簽名 重載 對象 通過 P23——Java提供了兩個用於高精度計算的類:BIgInteger和BigDecimal,能作用於int和float的操作,也同樣能作用於BigInteger和BigDecimal。只不過必須以方法調用的方式

    Effective Java (3rd Editin) 讀書筆記:2 所有物件共有的方法

    2 所有物件共有的方法 Item 10:重寫 equals 方法時遵守通用協同 不需要重寫 equals() 方法的情況: 類的每一個例項都認為是不同。比如 Thread 這種代表活躍的實體而不是值 不需要“邏輯相等”的判斷。比如 Pattern 不需要檢查內嵌的正

    Effective Java 第二版 中文版 筆記(六)消除過期物件的引用

    這部分沒有怎麼理解,但總體的意思就是防止記憶體洩漏 Java的垃圾回收機制: Java中的物件是在堆中分配,物件的建立有2中方式:new或者反射。物件的回收是通過垃圾收集器,JVM的垃圾收集器簡化了程式設計師的工作,但是卻加重了JVM的工作,這是Java程式執行稍慢的