面試系列-創建對象別忘了這些操作

1. 覆蓋equals方法

  • 問題

    在面對equals方法時,會有這樣的疑問,什么時候該覆蓋equals方法,什么時候不應該覆蓋,也就是說覆蓋equals方法的時機是什么?如果覆蓋equals方法,那么應該寫?

  • 解決

    1. 覆蓋equals方法的時機

      覆蓋equals方法看起來似乎很簡單,但是有許多覆蓋方式會導致錯誤,并且后果非常嚴重,最容易避免這類問題的辦法就是不覆蓋equals方法,在這種情況下,類的每個實例都只與它自身相等。下面這幾種情況就不需要覆蓋equals()方法:

      • 類的每個實例本質上都是唯一的。對于代表實體而不是值(value)的類來說確實如此,例如Thread。Object提供的equals實現對于這些類來說是正確的行為;
      • 不關心類是否提供了“邏輯相等(logical equality)”的測試功能。如java.util.Random覆蓋了equals,以檢查兩個Random實例是否產生相同的隨機序列,但是調用者并不期望這樣的功能。在這樣的情況下,從Object繼承得到的equals實現已經足夠了;
      • 父類已經覆蓋了equals,從父類繼承過來的行為對于子類來說也是合適的。例如大多數Set實現都從AbstractSet繼承equals實現,類似的有List和Map等;
      • 類是私有的或是包級私有的,可以確定它的equals方法永遠不會被調用。
    2. 覆蓋equals方法的規范寫法

      在覆蓋equals方法時,需要遵守的約定有:

      • 自反性:對于任何非null的引用值x,x.equals(x)必須返回true;
      • 對稱性:對于任何非null的引用值x,y,當且僅當y.equals(x)返回true時,x.equals(y)也應該返回true;
      • 傳遞性:對于任何非null得引用值x、y和z,如果x.equals(y)返回true時,并且y.equals(z)也返回true,那么x.equals(z)也返回true;
      • 一致性:對于任何非null得引用值x和y,只要equals的比較操作在對象中所用的信息沒有被修改,那么多次調用x.equals(y)就會一致的返回true,或者一致的返回false;
      • 非空性:對于任何非null的引用值x,x.equals(null)必須返回false;

      編寫的技巧有:

      • 使用==操作符檢查“參數是否為這個對象的引用”;
      • 使用instanceof操作符檢查“參數是否為正確的類型”;
      • 經過instanceof類型檢查之后把參數轉換成正確的類型;
      • 對于該類中的每個“關鍵”域,檢查參數中的域是否與該對象中對應的域相匹配。對于不是double和float的基本類型,可以使用==進行比較,對于引用類型,可以遞歸調用equals方法,對于float域,可以使用Float.compare方法,對于double域,可以使用Double.compare方法;
      • 當編寫完equals方法時,應該問自己三個問題:它是否滿足對稱性、傳遞性、以及一致性;
      • 覆蓋equals方法總要覆蓋hashCode()方法;
      • 判斷各個域值是否相等的邏輯不要過于復雜;
      • 不要將所覆蓋的equals方法中的入參Object對象替換成其他對象,應該使用@Override。
  • 結論

    當面對equals方法時,應該根據覆蓋equals方法的時機去判斷是否需要覆蓋equals方法,如果需要覆蓋equals方法時,要嚴格遵守equals方法的規范。

2. 覆蓋equals方法同時覆蓋hashCode方法

  • 問題

    在每個覆蓋了equals方法的類中,也必須覆蓋hashCode方法,如果不這樣的話,就會違反了Object.hashCode的通用約定,從而導致該類無法結合所有基于散列的集合一起正常動作,比如說HashMap,HashSet,Hashtable。那么,Object.hashCode規范是什么?以及一個性能良好的hashCode應該怎樣寫?

  • 解決

    1. Object.hashCode規范

      • 在應用程序的執行期間,只要對象的equals方法的比較操作所用到的信息沒有被修改,那么對這同一個對象調用多次,hashCode方法必須都始終如一地返回同一個整數
      • 如果兩個對象根據equals(Object)方法比較是相等的。那么調用這兩個對象中任意一個對象的hashCode方法都必須產生同樣的整數結果。
      • 如果兩個對象根據equals(Object)方法比較是不相等的,那么調用這兩個對象中的任意一個對象的hashCode方法,則不一定要產生不同的整數結果。
    2. hashCode的寫法

      一個好的散列函數通常傾向于“為不相等的對象產生不相等的hashCode”,編寫好的hashCode也如下這種簡單的方式:

      1. 把某個非零的常數值,比如說17保存在一個名為result的int類型的變量中。
      2. 對于對象中的每個關鍵域f(指equals方法中涉及的每個域),完成以下步驟:

    a. 為該域計算int類型的散列碼c:

    1. 如果該域是boolean類型,則計算(f ? 1 : 0)

    2). 如果該域是byte、char、short或者int類型,則計算(int)f

    1. 如果該域是long類型,則計算(int)(f^(f>>>32))。
    2. 如果該域是float類型,則計算Float.floatToIntBits(f)。
    3. 如果該域是double類型,則計算Double.doubleToLongBits(f),然后按照步 驟2.a.3),為得到的long類型值計算散列值。
    4. 如果該域是一個對象引用,并且該類的equals方法通過遞歸地調用equals方式來比較這個域,則同樣為這個域遞歸地調用hashCode。如果需要更復雜的比較,則為這個域計算一個范式,然后針對這個范式調用hashCode。如果這個域的值為null,則返回0(不絕對,但通常是0)。
    5. 如果該域是一個數組,則要把每個元素當做單獨的域來處理。也就是說,遞歸地應用上面的規則,對每個重要的元素計算一個散列碼。然后再用2中的方法組合起來。如果數組中的每個元素都很重要,則可以用Arrays.hashCode方法。

    b. 按照下面的公式,把步驟2.a計算得到的散列碼c合并到result中。

    result = 31 * result + c;

    1. 返回result。

    2. 示例

      public final class PhoneNumber {
          private final short areaCode;
          private final short prefix;
          private final short lineNumber;
      
          @Override
          public int hashCode() {
              int result = 17;
              result = 31 * result + areaCode;
              result = 31 * result + prefix;
              result = 31 * result + lineNumber;
              return result;
          }
      }
      
  • 結論

    1. 如果覆蓋了equals方法一定要覆蓋hashCode方法,否則會造成基于散列值得集合使用出現問題,如HashMap或者HashSet等;
    2. 不要試圖從散列碼計算中排除一個對象的關鍵部分來提高性能。雖然這樣可能使計算的速度得到提升,但是效果并不見得會好,可以會導致散列表慢到根本無法使用,如果因此大量的實例映射到極少的散列碼上,那基于散列的集合將會顯示出平方級的性能。Java平臺類庫中的許多類如 String、Integer、Date,都可以把它們的hashCode方法返回確切值規定為該實例的一個函數,一般來說,這并不是一個好主意,因為這樣做嚴格地限制了在將來的版本中改進散列函數的能力。

3. 覆蓋toString方法

  • 問題

    Object中默認的toString方法,它返回的字符串只類類名加上一個“@符號”,后面是十六進制形式的hashCode,這些信息對我們來說用處不大,所以為了提供更好的關于類和對象的說明,我們應該總是覆蓋toString()方法來提供更加清晰的說明,覆蓋toString方法的好處以及覆蓋toString的注意事項?

  • 解決

    1. 覆蓋toString方法的好處

      toString方法雖然不會像equals這樣的方法對類造成那么大的影響,但是一個好的toString可以使類用起來更加的舒服。當對象被傳給println、printf、字符串聯操作符(+)以及assert或者被調試器打印出來時,toString方法會被自動調用。這是一種重要的調用手段,如果不重寫toString提供更明確的信息,這將很難讓人理解。toString的輸出,也可以方便我們debug

    2. 覆蓋toString的注意事項

      • 在實際應用中,toString方法應該返回對象中包含的所有值得關注的信息,如果對象太大或者對象中包含的狀態信息難以用字符來表達,這樣做就有點不切實際了,在這種情況下toString方法應該返回類的關鍵域信息;
      • 在覆蓋toString時可以指定輸出格式,這樣就可以編寫相應的代碼來解析這種字符串表示法,產生字符串表示法,以及把字符串表示嵌入到持久的數據中。但是,將來一旦輸出格式變化了,會造成更大的問題。是否指定輸出格式應該權衡。
  • 總結

    在實際開發過程中最好要覆蓋toString方法,將類的有用信息使用toString方法進行輸出,這樣就可以方便調試或者打印的時候輸出

4.實現comparable

  • 問題

    compareTo方法是Comparable接口中唯一的方法,不但允許進行簡單的等同性比較,而且允許執行順序比較。一旦實現了Comparable接口,就可以跟許多泛型方法以及依賴于該接口的集合實現類進行協作。實現CompareTo方法有哪些規范?

  • 解決

    1. 使用compareTo方法有一個重要的約定,就是通常情況下compareTo方法施加的等同性測試和equals方法一致。如果不一致的話,集合接口一般是使用equals方法來進行等同性測試,而有序集合是采用compareTo方法進行等同性測試,如果兩者不一致的話,容易造成災難性的后果;

    2. 將對象與指定的對象進行比較。當該對象小于、等于或者大于指定對象的時候,分別返回一個負整數,零或者正整數,如果由于指定對象的類型而無法與該對象進行比較,則拋出ClassCastException。在下面的說明中,符號sgn(表達式)表示數學中的signum函數,它根據表達式(expression)的值為負值、零和正值,分別返回-1、0、1。

      • 必須確保所有的x和y都滿足sgn(x.compareTo(y)) == -sgn(y.compareTo(x))。這也暗示著當且僅當y.compareTo(x)拋出異常時,x.compareTo(y)才拋出異常。
      • 必須確保這個比較關系是可傳遞的:(x.compareTo(y) > 0 && y.compareTo(z) > 0)暗示著x.compareTo(z) > 0也成立。對應著equals使用規范里面的傳遞性
      • 必須確保x.compareTo(y) == 0暗示著所有的z都滿足sgn(x.compareTo(z)) == sgn(y.compareTo(z))。
      • 強烈建議(x.compareTo(y) == 0) == (x.equals(y)),但是這個并非絕對必要。一般來說,任何實現了Comparable接口的類,若違反了這個條件,都應該明確予以說明。推薦使用這樣的說法:“注意,該類具有內在的排序功能,但是與equals不一致”。
    3. 示例

      如果一個類有多個關鍵域,那么比較這些關鍵域的順序非常關鍵。必須從最關鍵的域開始,逐步進行到所有的重要域。如果某個域的比較產生了非零的結果(0代表著相等),則整個比較操作結束,并返回該結果。如果最關鍵的域是相等的,則再比較下一個關鍵域,以此類推,如果所有域都是相等的,那么才返回0。例如下面的例子:

      public final class PhoneNumber implements Comparable {
      
          private final short areaCode;
          private final short prefix;
          private final short lineNumber;
      
          public PhoneNumber(int areaCode, int prefix,
                              int lineNumber) {
              this.areaCode = (short) areaCode;
              this.prefix = (short) prefix;
              this.lineNumber = (short) lineNumber;
          }
      
          @Override
          public int compareTo(PhoneNumber pn) {
              if (areaCode < pn.areaCode) 
                  return -1;
              if(areaCode > pn.areaCode)
                  return 1;
      
              if (prefix < pn.prefix)
                  return -1;
              if (prefix > pn.prefix)
                  return 1;
      
              if (lineNumber < pn.lineNumber)
                  return -1;
              if (lineNumber > pn.lineNumber)
                  return 1;
      
              return 0;
          }
      }
      

      可以改進如下:

      public int compareTo(PhoneNumber pn) {
          int areaCodeDiff = areaCode - pn.areaCode;
          if (areaCodeDiff != 0)
              return areaCodeDiff;
      
          int prefixDiff = prefix - pn.prefix;
          if (0 != prefixDiff)
              return prefixDiff;
      
          return lineNumber - pn.lineNumber;
      }
      

      使用這種方法的時候需要注意,有符號的32位整數還不足以大到能夠表達任意兩個32位整數的差值,如果i是一個很大的正整數,j是一個很小的負整數,i-j有可能會溢出,并且返回一個負值。

  • 結論

    在實現Comparable接口時,應該遵守這些規范,特別是在做等同性測試的時候,要和equals等同性測試結果保持一致。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,401評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,011評論 3 413
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 175,263評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,543評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,323評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,874評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,968評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,095評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,605評論 1 331
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,551評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,720評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,242評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,961評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,358評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,612評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,330評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,690評論 2 370

推薦閱讀更多精彩內容