亚洲免费在线-亚洲免费在线播放-亚洲免费在线观看-亚洲免费在线观看视频-亚洲免费在线看-亚洲免费在线视频

Effective Java (對(duì)象通用方法)

系統(tǒng) 1747 0

八、覆蓋equals時(shí)請(qǐng)遵守通用約定:

?? ?? 對(duì)于Object類中提供的equals方法在必要的時(shí)候是必要重載的,然而如果違背了一些通用的重載準(zhǔn)則,將會(huì)給程序帶來(lái)一些潛在的運(yùn)行時(shí)錯(cuò)誤。如果自定義的class沒(méi)有重載該方法,那么該類實(shí)例之間的相等性的比較將是基于兩個(gè)對(duì)象是否指向同一地址來(lái)判定的。因此對(duì)于以下幾種情況可以考慮不重載該方法:
? ? ? 1.?? ?類的每一個(gè)實(shí)例本質(zhì)上都是唯一的。
? ? ? 不同于值對(duì)象,需要根據(jù)其內(nèi)容作出一定的判定,然而該類型的類,其實(shí)例的自身便具備了一定的唯一性,如Thread、Timer等,他本身并不具備更多邏輯比較的必要性。
?? ?? 2.?? ?不關(guān)心類是否提供了“邏輯相等”的測(cè)試功能。
? ? ? 如Random類,開發(fā)者在使用過(guò)程中并不關(guān)心兩個(gè)Random對(duì)象是否可以生成同樣隨機(jī)數(shù)的值,對(duì)于一些工具類亦是如此,如NumberFormat和DateFormat等。
?? ?? 3.?? ?超類已經(jīng)覆蓋了equals,從超類繼承過(guò)來(lái)的行為對(duì)于子類也是合適的。
?? ?? 如Set實(shí)現(xiàn)都從AbstractSet中繼承了equals實(shí)現(xiàn),因此其子類將不在需要重新定義該方法,當(dāng)然這也是充分利用了繼承的一個(gè)優(yōu)勢(shì)。
?? ?? 4.?? ?類是私有的或是包級(jí)別私有的,可以確定它的equals方法永遠(yuǎn)不會(huì)被調(diào)用。
?? ?
?? ?? 那么什么時(shí)候應(yīng)該覆蓋Object.equals呢?如果類具有自己特有的“邏輯相等”概念,而且超類中沒(méi)有覆蓋equals以實(shí)現(xiàn)期望的行為,這是我們就需要覆蓋equals方法,如各種值對(duì)象,或者像Integer和Date這種表示某個(gè)值的對(duì)象。在重載之后,當(dāng)對(duì)象插入Map和Set等容器中時(shí),可以得到預(yù)期的行為。枚舉也可以被視為值對(duì)象,然而卻是這種情形的一個(gè)例外,對(duì)于枚舉是沒(méi)有必要重載equals方法,直接比較對(duì)象地址即可,而且效率也更高。
? ? ? 在覆蓋equals是,該條目給出了通用的重載原則:
?? ?? 1.?? ?自反性:對(duì)于非null的引用值x,x.equals(x)返回true。
? ? ? 如果違反了該原則,當(dāng)x對(duì)象實(shí)例被存入集合之后,下次希望從該集合中取出該對(duì)象時(shí),集合的contains方法將直接無(wú)法找到之前存入的對(duì)象實(shí)例。
? ? ? 2.?? ?對(duì)稱性:對(duì)于任何非null的引用值x和y,如果y.equals(x)為true,那么x.equals(y)也為true。

1 ???? public final class CaseInsensitiveString {
2 ???????? private final String s;
3 ???????? public CaseInsensitiveString(String s) {
4 ???????????? this .s = s;
5 ???????? }
6 ???????? @Override public boolean equals(Object o) {
7 ???????????? if (o instanceof CaseInsensitiveString)
8 ???????????????? return s.equalsIgnoreCase((CaseInsensitiveString)o).s);
9 ???????????? if (o instanceof String) // One-way interoperability
10 ???????????????? return s.equalsIgnoreCase((String)o);
11 ???????????? return false ;
12 ???????? }
13 ???? }
14 ???? public static void main(String[] args) {
15 ???????? CaseInsensitiveString cis = new CaseInsensitiveString("Polish");
16 ???????? String s = "polish";
17 ???????? List<CaseInsensitiveString> l = new ArrayList<CaseInsensitiveString>();
18 ???????? l.add(cis);
19 ???????? if (l.contains(s))
20 ???????????? System.out.println("s can be found in the List");
21 ???? }
?

?? ?? 對(duì)于上例,如果執(zhí)行cis.equals(s)將會(huì)返回true,因?yàn)樵谠揷lass的equals方法中對(duì)參數(shù)o的類型針對(duì)String作了特殊的判斷和特殊的處理,因此如果equals中傳入的參數(shù)類型為String時(shí),可以進(jìn)一步完成大小寫不敏感的比較。然而在String的equals中,并沒(méi)有針對(duì)CaseInsensitiveString類型做任何處理,因此s.equals(cis)將一定返回false。針對(duì)該示例代碼,由于無(wú)法確定List.contains的實(shí)現(xiàn)是基于cis.equals(s)還是基于s.equals(cis),對(duì)于實(shí)現(xiàn)邏輯兩者都是可以接受的,既然如此,外部的使用者在調(diào)用該方法時(shí)也應(yīng)該同樣保證并不依賴于底層的具體實(shí)現(xiàn)邏輯。由此可見,equals方法的對(duì)稱性是非常必要的。以上的equals實(shí)現(xiàn)可以做如下修改:

        
          1
        
             @Override 
        
          public
        
        
          boolean
        
         equals(Object o) {


        
          2
        
        
          if
        
         (o 
        
          instanceof
        
         CaseInsensitiveString) 


        
          3
        
        
          return
        
         s.equalsIgnoreCase((CaseInsensitiveString)o).s);

        
          
4
        
        
          return
        
        
          false
        
        ;

        
          
5
        
             }
      

? ? ? 這樣修改之后,cis.equals(s)和s.equals(cis)都將返回false。?? ?
? ? ? 3.?? ?傳遞性:對(duì)于任何非null的引用值x、y和z,如果x.equals(y)返回true,同時(shí)y.equals(z)也返回true,那么x.equals(z)也必須返回true。

1 ???? public class Point {
2 private final int x;
3 ???????? private final int y;
4 ???????? public Point( int x, int y) {
5 ???????????? this .x = x;
6 ???????????? this .y = y;
7 ???????? }
8 ???????? @Override public boolean equals(Object o) {
9 ???????????? if (!(o instanceof Point))
10 ???????????????? return false ;
11 ???????????? Point p = (Point)o;
12 ???????????? return p.x == x && p.y == y;
13 ???????? }
14 ???? }
?

? ? ? 對(duì)于該類的equals重載是沒(méi)有任何問(wèn)題了,該邏輯可以保證傳遞性,然而在我們?cè)噲D給Point類添加新的子類時(shí),會(huì)是什么樣呢?

1 ???? public class ColorPoint extends Point {
2 ???????? private final Color c;
3 ???????? public ColorPoint( int x, int y,Color c) {
4 ???????????? super (x,y);
5 ???????????? this .c = c;
6 ???????? }
7 ???????? @Override public boolean equals(Object o) {
8 ???????????? if (!(o instanceof ColorPoint))
9 ???????????????? return false ;
10 ???????????? return super .equals(o) && ((ColorPoint)o).c == c;
11 ???????? }
12 ???? }
?

? ? ? 如果在ColorPoint中沒(méi)有重載自己的equals方法而是直接繼承自超類,這樣的相等性比較邏輯將會(huì)給使用者帶來(lái)極大的迷惑,畢竟Color域字段對(duì)于ColorPoint而言確實(shí)是非常有意義的比較性字段,因此該類重載了自己的equals方法。然而這樣的重載方式確實(shí)帶來(lái)了一些潛在的問(wèn)題,見如下代碼:

1 ???? public void test() {
2 ???????? Point p = new Point(1,2);
3 ???????? ColorPoint cp = new ColorPoint(1,2,Color.RED);
4 ???????? if (p.equals(cp))
5 ???????????? System.out.println("p.equals(cp) is true");
6 ???????? if (!cp.equals(p))
7 System.out.println("cp.equals(p) is false");
8 ???? }
?

? ? ? 從輸出結(jié)果來(lái)看,ColorPoint.equals方法破壞了相等性規(guī)則中的對(duì)稱性,因此需要做如下修改:

1 ???? @Override public boolean equals(Object o) {
2 ???????? if (!(o instanceof Point))
3 ???????????? return false ;
4 ???????? if (!(o instanceof ColorPoint))
5 ???????????? return o.equals( this );
6 ???????? return super .equals(o) && ((ColorPoint)o).c == c;
7 ???? }
?

? ? ? 經(jīng)過(guò)這樣的修改,對(duì)稱性確實(shí)得到了保證,但是卻犧牲了傳遞性,見如下代碼:

1 ???? public void test() {
2 ???????? ColorPoint p1 = new ColorPoint(1,2,Color.RED);
3 ???????? Point p2 = new Point(1,2);
4 ???????? ColorPoint p1 = new ColorPoint(1,2,Color.BLUE);
5 ???????? if (p1.equals(p2) && p2.equals(p3))
6 ???????????? System.out.println("p1.equals(p2) && p2.equals(p3) is true");
7 ???????? if (!(p1.equals(p3))
8 ???????????? System.out.println("p1.equals(p3) is false");
9 ???? }
?

?? ?? 再次看輸出結(jié)果,傳遞性確實(shí)被打破了。如果我們?cè)赑oint.equals中不使用instanceof而是直接使用getClass呢?

        
          1
        
             @Override 
        
          public
        
        
          boolean
        
         equals(Object o) {


        
          2
        
        
          if
        
         (o == 
        
          null
        
         || o.getClass() == getClass()) 

        
          
3
        
        
          return
        
        
          false
        
        ;


        
          4
        
                 Point p = (Point)o;


        
          5
        
        
          return
        
         p.x == x && p.y == y;

        
          
6
        
             }
      

?? ?? 這樣的Point.equals確實(shí)保證了對(duì)象相等性的這幾條規(guī)則,然而在實(shí)際應(yīng)用中又是什么樣子呢?

1 ???? class MyTest {
2 ???????? private static final Set<Point> unitCircle;
3 ???????? static {
4 ???????????? unitCircle = new HashSet<Point>();
5 ???????????? unitCircle.add( new Point(1,0));
6 ???????????? unitCircle.add( new Point(0,1));
7 ???????????? unitCircle.add( new Point(-1,0));
8 ???????????? unitCircle.add( new Point(0,-1));
9 ???????? }
10 ???????? public static boolean onUnitCircle(Point p) {
11 ???????????? return unitCircle.contains(p);
12 ???????? }
13 ???? }
?

? ? ? 如果此時(shí)我們測(cè)試的不是Point類本身,而是ColorPoint,那么按照目前Point.equals(getClass方式)的實(shí)現(xiàn)邏輯,ColorPoint對(duì)象在被傳入onUnitCircle方法后,將永遠(yuǎn)不會(huì)返回true,這樣的行為違反了"里氏替換原則"(敏捷軟件開發(fā)一書中給出了很多的解釋),既一個(gè)類型的任何重要屬性也將適用于它的子類型。因此該類型編寫的任何方法,在它的子類型上也應(yīng)該同樣運(yùn)行的很好。
?? ?? 如何解決這個(gè)問(wèn)題,該條目給出了一個(gè)折中的方案,既復(fù)合優(yōu)先于繼承,見如下代碼:

1 ???? public class ColorPoint {
2 ???????? // 包含了Point的代理類
3 ???????? private final Point p;
4 ???????? private final Color c;
5 ???????? public ColorPoint( int x, int y,Color c) {
6 ???????????? if (c == null )
7 ???????????????? throw new NullPointerException();
8 ???????????? p = new Point(x,y);
9 ???????????? this .c = c;
10 ???????? }
11 ???????? // 提供一個(gè)視圖方法返回內(nèi)部的Point對(duì)象實(shí)例。這里Point實(shí)例為final對(duì)象非常重要,
12 // 可以避免使用者的誤改動(dòng)。視圖方法在Java的集合框架中有著大量的應(yīng)用。
13 ???????? public Point asPoint() {
14 ???????????? return p;
15 ???????? }
16 ???????? @Override public boolean equals(Object o) {
17 ???????????? if (!(o instanceof ColorPoint))
18 ???????????????? return false ;
19 ???????????? ColorPoint cp = (ColorPoint)o;
20 ???????????? return cp.p.equals(p) && cp.c.equals(c);
21 ???????? }
22 ???? }
?

????? 4.?? ?一致性:對(duì)于任何非null的引用值x和y,只要equals的比較操作在對(duì)象中所用的信息沒(méi)有被改變,多次調(diào)用x.equals(y)就會(huì)一致的返回true,或者一致返回false。
? ? ? 在實(shí)際的編碼中,盡量不要讓類的equals方法依賴一些不確定性較強(qiáng)的域字段,如path。由于path有多種表示方式可以指向相同的目錄,特別是當(dāng)path中包含主機(jī)名稱或ip地址等信息時(shí),更增加了它的不確定性。再有就是path還存在一定的平臺(tái)依賴性。
?? ?? 5.?? ?非空性:很難想象會(huì)存在o.equals(null)返回true的正常邏輯。作為JDK框架中極為重要的方法之一,equals方法被JDK中的基礎(chǔ)類廣泛的使用,因此作為一種通用的約定,像equals、toString、hashCode和compareTo等重要的通用方法,開發(fā)者在重載時(shí)不應(yīng)該讓自己的實(shí)現(xiàn)拋出異常,否則會(huì)引起很多潛在的Bug。如在Map集合中查找指定的鍵,由于查找過(guò)程中的鍵相等性的比較就是利用鍵對(duì)象的equals方法,如果此時(shí)重載后的equals方法拋出NullPointerException異常,而Map的get方法并未捕獲該異常,從而導(dǎo)致系統(tǒng)的運(yùn)行時(shí)崩潰錯(cuò)誤,然而事實(shí)上,這樣的問(wèn)題是完全可以通過(guò)正常的校驗(yàn)手段來(lái)避免的。綜上所述,很多對(duì)象在重載equals方法時(shí)都會(huì)首先對(duì)輸入的參數(shù)進(jìn)行是否為null的判斷,見如下代碼:

1 ???? @Override public boolean equals(Object o) {
2 ???????? if (o == null )
3 ???????????? return false ;
4 ???????? if (!(o instanceof MyType))
5 ???????????? return false ;
6 ???????? ...
7 ???? }
?

????? 注意以上代碼中的instanceof判斷,由于在后面的實(shí)現(xiàn)中需要將參數(shù)o進(jìn)行類型強(qiáng)轉(zhuǎn),如果類型不匹配則會(huì)拋出ClassCastException,導(dǎo)致equals方法提前退出。在此需要指出的是instanceof還有一個(gè)潛在的規(guī)則,如果其左值為null,instanceof操作符將始終返回false,因此上面的代碼可以優(yōu)化為:

        
          1
        
             @Override 
        
          public
        
        
          boolean
        
         equals(Object o) {


        
          2
        
        
          if
        
         (!(o 
        
          instanceof
        
         MyType)) 


        
          3
        
        
          return
        
        
          false
        
        ;

        
          
4
        
                 ...

        
          
5
        
             }
      

? ? ? 鑒于之上所述,該條目中給出了重載equals方法的最佳邏輯:
? ? ? 1.?? ?使用==操作符檢查"參數(shù)是否為這個(gè)對(duì)象的引用",如果是則返回true。由于==操作符是基于對(duì)象地址的比較,因此特別針對(duì)擁有復(fù)雜比較邏輯的對(duì)象而言,這是一種性能優(yōu)化的方式。
? ? ? 2.?? ?使用instanceof操作符檢查"參數(shù)是否為正確的類型",如果不是則返回false。
? ? ? 3.?? ?把參數(shù)轉(zhuǎn)換成為正確的類型。由于已經(jīng)通過(guò)instanceof的測(cè)試,因此不會(huì)拋出ClassCastException異常。
? ? ? 4.?? ?對(duì)于該類中的每個(gè)"關(guān)鍵"域字段,檢查參數(shù)中的域是否與該對(duì)象中對(duì)應(yīng)的域相匹配。
? ? ? 如果以上測(cè)試均全部成功返回true,否則false。見如下示例代碼:

1 ???? @Override public boolean equals(Object o) {
2 ???????? if (o == this )
3 ???????????? return true ;
4 ????????
5 ???????? if (!(o instanceof MyType))
6 ???????????? return false ;
7 ????????????
8 ???????? MyType myType = (MyType)o;
9 ???????? return objField.equals(o.objField) && intField == o.intField
10 ???????????? && Double.compare(doubleField,o.doubleField) == 0
11 ???????????? && Arrays.equals(arrayField,o.arrayField);
12 ???? }
?

? ? ? 從上面的示例中可以看出,如果域字段為Object對(duì)象,則使用equals方法進(jìn)行兩者之間的相等性比較,如果為int等整型基本類型,可以直接比較,如果為浮點(diǎn)型基本類型,考慮到精度和Double.NaN和Float.NaN等問(wèn)題,推薦使用其對(duì)應(yīng)包裝類的compare方法,如果是數(shù)組,可以使用JDK 1.5中新增的Arrays.equals方法。眾所周知,&&操作符是有短路原則的,因此應(yīng)該將最有可能不相同和比較開銷更低的域比較放在最前面。
? ? ? 最后需要提起注意的是Object.equals的參數(shù)類型為Object,如果要重載該方法,必須保持參數(shù)列表的一致性,如果我們將子類的equals方法寫成:public boolean equals(MyType o);Java的編譯器將會(huì)視其為Object.equals的過(guò)載(Overload)方法,因此推薦在聲明該重載方法時(shí),在方法名的前面加上@Override注釋標(biāo)簽,一旦當(dāng)前聲明的方法因?yàn)楦鞣N原因并沒(méi)有重載超類中的方法,該標(biāo)簽的存在將會(huì)導(dǎo)致編譯錯(cuò)誤,從而提醒開發(fā)者此方法的聲明存在語(yǔ)法問(wèn)題。
?? ?
九、覆蓋equals時(shí)總要覆蓋hashCode:

?? ?? 一個(gè)通用的約定,如果類覆蓋了equals方法,那么hashCode方法也需要被覆蓋。如果將會(huì)導(dǎo)致該類無(wú)法和基于散列的集合一起正常的工作,如HashMap、HashSet。來(lái)自JavaSE6的約定如下:
?? ?? 1.?? ?在應(yīng)用程序執(zhí)行期間,只要對(duì)象的equals方法的比較操作所用到的信息沒(méi)有被修改,那么對(duì)這同一個(gè)對(duì)象多次調(diào)用,hashCode方法都必須始終如一地返回同一個(gè)整數(shù)。在同一個(gè)應(yīng)用程序的多次執(zhí)行過(guò)程中,每次執(zhí)行所返回的整數(shù)可以不一致。
? ? ? 2.?? ?如果兩個(gè)對(duì)象根據(jù)equals(Object)方法比較是相等的,那么調(diào)用這兩個(gè)對(duì)象中任意一個(gè)對(duì)象的hashCode方法都必須產(chǎn)生同樣的整數(shù)結(jié)果。
? ? ? 3.?? ?如果兩個(gè)對(duì)象根據(jù)equals(Object)方法比較是不相等的,那么調(diào)用這兩個(gè)對(duì)象中任意一個(gè)對(duì)象的hashCode方法,則不一定要產(chǎn)生不同的整數(shù)結(jié)果。但是程序員應(yīng)該知道,給不相等的對(duì)象產(chǎn)生截然不同的整數(shù)結(jié)果,有可能提高散列表的性能。
? ? ? 如果類沒(méi)有覆蓋hashCode方法,那么Object中缺省的hashCode實(shí)現(xiàn)是基于對(duì)象地址的,就像equals在Object中的缺省實(shí)現(xiàn)一樣。如果我們覆蓋了equals方法,那么對(duì)象之間的相等性比較將會(huì)產(chǎn)生新的邏輯,而此邏輯也應(yīng)該同樣適用于hashCode中散列碼的計(jì)算,既參與equals比較的域字段也同樣要參與hashCode散列碼的計(jì)算。見下面的示例代碼:

1 ???? public final class PhoneNumber {
2 ???????? private final short areaCode;
3 ???????? private final short prefix;
4 ???????? private final short lineNumber;
5 ???????? public PhoneNumber( int areaCode, int prefix, int lineNumber) {
6 ???????????? // 做一些基于參數(shù)范圍的檢驗(yàn)。
7 ???????????? this .areaCode = areaCode;
8 ???????????? this .prefix = prefix;
9 ???????????? this .lineNumber = lineNumber;
10 ???????? }
11 ???????? @Override public boolean equals(Object o) {
12 ???????????? if (o == this )
13 ???????????????? return true ;
14 ???????????? if (!(o instanceof PhoneNumber))
15 ???????????????? return false ;
16 ???????????? PhoneNumber pn = (PhoneNumber)o;
17 ???????????? return pn.lineNumber = lineNumber && pn.prefix == prefix && pn.areaCode = areaCode;
18 ???????? }
19 ???? }
20 ???? public static void main(String[] args) {
21 ???????? Map<PhoneNumber,String> m = new HashMap<PhoneNumber,String>();
22 ???????? PhoneNumber pn1 = new PhoneNumber(707,867,5309);
23 ???????? m.put(pn1,"Jenny");
24 ???????? PhoneNumber pn2 = new PhoneNumber(707,867,5309);
25 ???????? if (m.get(pn) == null )
26 ???????????? System.out.println("Object can't be found in the Map");
27 ???? }
?

? ? ? 從以上示例的輸出結(jié)果可以看出,新new出來(lái)的pn2對(duì)象并沒(méi)有在Map中找到,盡管pn2和pn1的相等性比較將返回true。這樣的結(jié)果很顯然是有悖我們的初衷的。如果想從Map中基于pn2找到pn1,那么我們就需要在PhoneNumber類中覆蓋缺省的hashCode方法,見如下代碼:

1 ???? @Override public int hashCode() {
2 ???????? int result = 17;
3 ???????? result = 31 * result + areaCode;
4 ???????? result = 31 * result + prefix;
5 ???????? result = 31 * result + lineNumber;
6 ???????? return result;
7 ???? }
?

? ? ? 在上面的代碼中,可以看到參與hashCode計(jì)算的域字段也同樣參與了PhoneNumber的相等性(equals)比較。對(duì)于生成的散列碼,推薦不同的對(duì)象能夠盡可能生成不同的散列,這樣可以保證在存入HashMap或HashSet中時(shí),這些對(duì)象被分散到不同的散列桶中,從而提高容器的存取效率。對(duì)于有些不可變對(duì)象,如果需要被頻繁的存取于哈希集合,為了提高效率,可以在對(duì)象構(gòu)造的時(shí)候就已經(jīng)計(jì)算出其hashCode值,hashCode()方法直接返回該值即可,如:

1 ???? public final class PhoneNumber {
2 ???????? private final short areaCode;
3 ???????? private final short prefix;
4 ???????? private final short lineNumber;
5 ???????? private final int myHashCode;
6 ???????? public PhoneNumber( int areaCode, int prefix, int lineNumber) {
7 ???????????? // 做一些基于參數(shù)范圍的檢驗(yàn)。
8 ???????????? this .areaCode = areaCode;
9 ???????????? this .prefix = prefix;
10 ???????????? this .lineNumber = lineNumber;
11 ???????????? myHashCode = 17;
12 ???????????? myHashCode = 31 * myHashCode + areaCode;
13 ???????????? myHashCode = 31 * myHashCode + prefix;
14 ???????????? myHashCode = 31 * myHashCode + lineNumber;
15 ???????? }
16 ???????? @Override public boolean equals(Object o) {
17 ???????????? if (o == this )
18 ???????????????? return true ;
19 ???????????? if (!(o instanceof PhoneNumber))
20 ???????????????? return false ;
21 ???????????? PhoneNumber pn = (PhoneNumber)o;
22 ???????????? return pn.lineNumber = lineNumber && pn.prefix == prefix && pn.areaCode = areaCode;
23 ???????? }
24 ???????? @Override public int hashCode() {
25 ???????????? return myHashCode;
26 ???????? }
27 ???? }
?

? ? ? 另外,該條目還建議不要僅僅利用某一域字段的部分信息來(lái)計(jì)算hashCode,如早期版本的String,為了提高計(jì)算哈希值的效率,只是挑選其中16個(gè)字符參與hashCode的計(jì)算,這樣將會(huì)導(dǎo)致大量的String對(duì)象具有重復(fù)的hashCode,從而極大的降低了哈希集合的存取效率。
?? ?
十、始終要覆蓋toString:

? ? ? 與equals和hashCode不同的是,該條目推薦應(yīng)該始終覆蓋該方法,以便在輸出時(shí)可以得到更明確、更有意義的文字信息和表達(dá)格式。這樣在我們輸出調(diào)試信息和日志信息時(shí),能夠更快速的定位出現(xiàn)的異常或錯(cuò)誤。如上一個(gè)條目中PhoneNumber的例子,如果不覆蓋該方法,就會(huì)輸出PhoneNumber@163b91 這樣的不可讀信息,因此也不會(huì)給我們?cè)\斷問(wèn)題帶來(lái)更多的幫助。以下代碼重載了該方法,那么在我們調(diào)用toString或者println時(shí),將會(huì)得到"(408)867-5309"。

        
          1
        
             @Override String toString() {


        
          2
        
        
          return
        
         String.format("(%03d) %03d-%04d",areaCode,prefix,lineNumber);


        
          3
        
             }
      

? ? ? 對(duì)于toString返回字符串中包含的域字段,如本例中的areaCode、prefix和lineNumber,應(yīng)該在該類(PhoneNumber)的聲明中提供這些字段的getter方法,以避免toString的使用者為了獲取其中的信息而不得不手工解析該字符串。這樣不僅帶來(lái)不必要的效率損失,而且在今后修改toString的格式時(shí),也會(huì)給使用者的代碼帶來(lái)負(fù)面影響。提到toString返回字符串的格式,有兩個(gè)建議,其一是盡量不要固定格式,這樣會(huì)給今后添加新的字段信息帶來(lái)一定的束縛,因?yàn)楸仨氁紤]到格式的兼容性問(wèn)題,再者就是推薦可以利用toString返回的字符串作為該類的構(gòu)造函數(shù)參數(shù)來(lái)實(shí)例化該類的對(duì)象,如BigDecimal和BigInteger等裝箱類。
? ? ? 這里還有一點(diǎn)建議是和hashCode、equals相關(guān)的,如果類的實(shí)現(xiàn)者已經(jīng)覆蓋了toString的方法,那么完全可以利用toString返回的字符串來(lái)生成hashCode,以及作為equals比較對(duì)象相等性的基礎(chǔ)。這樣的好處是可以充分的保證toString、hashCode和equals的一致性,也降低了在對(duì)類進(jìn)行修訂時(shí)造成的一些潛在問(wèn)題。盡管這不是剛性要求的,卻也不失為一個(gè)好的實(shí)現(xiàn)方式。該建議并不是源于該條目,而是去年在看effective C#中了解到的。
?? ?
十二、考慮實(shí)現(xiàn)Comparable接口:

?? ?? 和之前提到的通用方法equals、hashCode和toString不同的是compareTo方法屬于Comparable接口,該接口為其實(shí)現(xiàn)類提供了排序比較的規(guī)則,實(shí)現(xiàn)類僅需基于內(nèi)部的邏輯,為compareTo返回不同的值,既A.compareTo(B) > 0可視為A > B,反之則A < B,如果A.compareTo(B) == 0,可視為A == B。在C++中由于提供了操作符重載的功能,因此可以直接通過(guò)重載操作符的方式進(jìn)行對(duì)象間的比較,事實(shí)上C++的標(biāo)準(zhǔn)庫(kù)中提供的缺省規(guī)則即為此,如bool operator>(OneObject o)。在Java中,如果對(duì)象實(shí)現(xiàn)了Comparable接口,即可充分利用JDK集合框架中提供的各種泛型算法,如:Arrays.sort(a); 即可完成a對(duì)象數(shù)組的排序。事實(shí)上,JDK中的所有值類均實(shí)現(xiàn)了該接口,如Integer、String等。
?? ?? Object.equals方法的通用實(shí)現(xiàn)準(zhǔn)則也同樣適用于Comparable.compareTo方法,如對(duì)稱性、傳遞性和一致性等,這里就不做過(guò)多的贅述了。然而兩個(gè)方法之間有一點(diǎn)重要的差異還是需要在這里提及的,既equals方法不應(yīng)該拋出異常,而compareTo方法則不同,由于在該方法中不推薦跨類比較,如果當(dāng)前類和參數(shù)對(duì)象的類型不同,可以拋出ClassCastException異常。在JDK 1.5 之后我們實(shí)現(xiàn)的Comparable<T>接口多為該泛型接口,不在推薦直接繼承1.5 之前的非泛型接口Comparable了,新的compareTo方法的參數(shù)也由Object替換為接口的類型參數(shù),因此在正常調(diào)用的情況下,如果參數(shù)類型不正確,將會(huì)直接導(dǎo)致編譯錯(cuò)誤,這樣有助于開發(fā)者在coding期間修正這種由類型不匹配而引發(fā)的異常。
? ? ? 在該條目中針對(duì)compareTo的相等性比較給出了一個(gè)強(qiáng)烈的建議,而不是真正的規(guī)則。推薦compareTo方法施加的等同性測(cè)試,在通常情況下應(yīng)該返回和equals方法同樣的結(jié)果,考慮如下情況:

1 ???? public static void main(String[] args) {
2 ???????? HashSet<BigDecimal> hs = new HashSet<BigDecimal>();
3 ???????? BigDecimal bd1 = new BigDecimal("1.0");
4 ???????? BigDecimal bd2 = new BigDecimal("1.00");
5 ???????? hs.add(bd1);
6 ???????? hs.add(bd2);
7 ???????? System.out.println("The count of the HashSet is " + hs.size());
8 ????????
9 ???????? TreeSet<BigDecimal> ts = new TreeSet<BigDecimal>();
10 ???????? ts.add(bd1);
11 ???????? ts.add(bd2);
12 ???????? System.out.println("The count of the TreeSet is " + ts.size());
13 ???? }
14 ???? /* ??? 輸出結(jié)果如下:
15 ??????? The count of the HashSet is 2
16 ??????? The count of the TreeSet is 1
17 */ ???
?

?? ?? 由以上代碼的輸出結(jié)果可以看出,TreeSet和HashSet中包含元素的數(shù)量是不同的,這其中的主要原因是TreeSet是基于BigDecimal的compareTo方法是否返回0來(lái)判斷對(duì)象的相等性,而在該例中compareTo方法將這兩個(gè)對(duì)象視為相同的對(duì)象,因此第二個(gè)對(duì)象并未實(shí)際添加到TreeSet中。和TreeSet不同的是HashSet是通過(guò)equals方法來(lái)判斷對(duì)象的相同性,而恰恰巧合的是BigDecimal的equals方法并不將這個(gè)兩個(gè)對(duì)象視為相同的對(duì)象,這也是為什么第二個(gè)對(duì)象可以正常添加到HashSet的原因。這樣的差異確實(shí)給我們的編程帶來(lái)了一定的負(fù)面影響,由于HashSet和TreeSet均實(shí)現(xiàn)了Set<E>接口,倘若我們的集合是以Set<E>的參數(shù)形式傳遞到當(dāng)前添加BigDecimal的函數(shù)中,函數(shù)的實(shí)現(xiàn)者并不清楚參數(shù)Set的具體實(shí)現(xiàn)類,在這種情況下不同的實(shí)現(xiàn)類將會(huì)導(dǎo)致不同的結(jié)果發(fā)生,這種現(xiàn)象極大的破壞了面向?qū)ο笾械?里氏替換原則"。
? ? ? 在重載compareTo方法時(shí),應(yīng)該將最重要的域字段比較方法比較的最前端,如果重要性相同,則將比較效率更高的域字段放在前面,以提高效率,如以下代碼:

1 ???? public int compareTo(PhoneNumer pn) {
2 ???????? if (areaCode < pn.areaCode)
3 ???????????? return -1;
4 ???????? if (areaCode > pn.areaCode)
5 ???????????? return 1;
6 ????????????
7 ???????? if (prefix < pn.prefix)
8 ???????????? return -1;
9 ???????? if (prefix > pn.prefix)
10 ???????????? return 1;
11 ????????????
12 ???????? if (lineNumber < pn.lineNumer)
13 ???????????? return -1;
14 ???????? if (lineNumber > pn.lineNumber)
15 ???????????? return 1;
16 ???????? return 0;
17 ???? }
?

? ? ? 上例給出了一個(gè)標(biāo)準(zhǔn)的compareTo方法實(shí)現(xiàn)方式,由于使用compareTo方法排序的對(duì)象并不關(guān)心返回的具體值,只是判斷其值是否大于0,小于0或是等于0,因此以上方法可做進(jìn)一步優(yōu)化,然而需要注意的是,下面的優(yōu)化方式會(huì)導(dǎo)致數(shù)值類型的作用域溢出問(wèn)題。

1 ???? public int compareTo(PhoneNumer pn) {
2 ???????? int areaCodeDiff = areaCode - pn.areaCode;
3 ???????? if (areaCodeDiff != 0)
4 ???????????? return areaCodeDiff;
5 ???????? int prefixDiff = prefix - pn.prefix;
6 ???????? if (prefixDiff != 0)
7 ???????????? return prefixDiff;
8 ????
9 ???????? int lineNumberDiff = lineNumber - pn.lineNumber;
10 ???????? if (lineNumberDiff != 0)
11 ???????????? return lineNumberDiff;
12 ???????? return 0;
13 ???? }?

Effective Java (對(duì)象通用方法)


更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號(hào)聯(lián)系: 360901061

您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對(duì)您有幫助就好】

您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺我的文章對(duì)您有幫助,請(qǐng)用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長(zhǎng)會(huì)非常 感謝您的哦!!!

發(fā)表我的評(píng)論
最新評(píng)論 總共0條評(píng)論
主站蜘蛛池模板: 日韩欧美一区二区三区四区 | 国产三级黄色录像 | 能免费看黄的网站 | 奇米影视777第四色 奇米影视777狠狠狠888不卡 | 福利免费视频 | 国产乱人免费视频 | 久久99热久久精品在线6 | 国产成人在线视频播放 | 99精品热线在线观看免费视频 | 一区二区国产在线播放 | 成人精品视频在线 | 在线观看 一区二区 麻豆 | 这里只有精品22 | 亚洲精品国产第一区第二区国 | 99免费在线观看 | 国内精品免费一区二区观看 | 奇米影视奇奇米色狠狠色777 | 国产一极毛片 | 亚洲国产精品激情在线观看 | 成人黄18免费视频 | 日本深夜影院 | 日韩dv | 久久国产精品夜色 | 狠狠色噜噜狠狠狠狠97影音先锋 | 午夜宅男在线观看 | 福利视频在线观看午夜 | 日韩www | 国产品精人成福利视频 | 夜夜躁狠狠躁日日躁视频 | 久久98| 极品女神西比尔久久精品 | a级毛片免费 | 国产精品96久久久久久久 | 尹人香蕉99久久综合网站 | 成人日韩欧美 | 欧美做爰xxxⅹ在线视频hd | 国产福利一区二区在线观看 | 国内精品视频一区二区三区八戒 | 五月天婷婷在线免费观看 | 顶级欧美色妇xxxxbbbb | 久久99精品一级毛片 |