<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                ## Chapter 3. Methods Common to All Objects(對象的通用方法) ### Item 10: Obey the general contract when overriding equals(覆蓋 equals 方法時應遵守的約定) Overriding the equals method seems simple, but there are many ways to get it wrong, and consequences can be dire. The easiest way to avoid problems is not to override the equals method, in which case each instance of the class is equal only to itself. This is the right thing to do if any of the following conditions apply: 覆蓋 equals 方法似乎很簡單,但是有很多覆蓋的方式會導致出錯,而且后果可能非常嚴重。避免問題的最簡單方法是不覆蓋 equals 方法,在這種情況下,類的每個實例都只等于它自己。如果符合下列任何條件,就是正確的做法: - **Each instance of the class is inherently unique.** This is true for classes such as Thread that represent active entities rather than values. The equals implementation provided by Object has exactly the right behavior for these classes. **類的每個實例本質上都是唯一的。** 對于像 Thread 這樣表示活動實體類而不是值類來說也是如此。Object 提供的 equals 實現對于這些類具有完全正確的行為。 - **There is no need for the class to provide a “logical equality” test.** For example, java.util.regex.Pattern could have overridden equals to check whether two Pattern instances represented exactly the same regular expression, but the designers didn’t think that clients would need or want this functionality. Under these circumstances, the equals implementation inherited from Object is ideal. **該類不需要提供「邏輯相等」測試。** 例如,`java.util.regex.Pattern` 可以覆蓋 equals 來檢查兩個 Pattern 實例是否表示完全相同的正則表達式,但設計人員認為客戶端不需要或不需要這個功能。在這種情況下,從 Object 繼承的 equals 實現是理想的。 - **A superclass has already overridden equals, and the superclass behavior is appropriate for this class.** For example, most Set implementations inherit their equals implementation from AbstractSet, List implementations from AbstractList, and Map implementations from AbstractMap. **超類已經覆蓋了 equals,超類行為適合于這個類。** 例如,大多數 Set 的實現從 AbstractSet 繼承其對等實現,List 從 AbstractList 繼承實現,Map 從 AbstractMap 繼承實現。 - **The class is private or package-private, and you are certain that its equals method will never be invoked.** If you are extremely risk-averse,you can override the equals method to ensure that it isn’t invoked accidentally: **類是私有的或包私有的,并且你確信它的 equals 方法永遠不會被調用。** 如果你非常厭惡風險,你可以覆蓋 equals 方法,以確保它不會意外調用: ``` @Override public boolean equals(Object o) { throw new AssertionError(); // Method is never called } ``` So when is it appropriate to override equals? It is when a class has a notion of logical equality that differs from mere object identity and a superclass has not already overridden equals. This is generally the case for value classes. A value class is simply a class that represents a value, such as Integer or String. A programmer who compares references to value objects using the equals method expects to find out whether they are logically equivalent, not whether they refer to the same object. Not only is overriding the equals method necessary to satisfy programmer expectations, it enables instances to serve as map keys or set elements with predictable, desirable behavior. 什么時候覆蓋 equals 方法是合適的?當一個類有一個邏輯相等的概念,而這個概念不同于僅判斷對象的同一性(相同對象的引用),并且超類還沒有覆蓋 equals。對于值類通常是這樣。值類只是表示值的類,例如 Integer 或 String。使用 equals 方法比較引用和值對象的程序員希望發現它們在邏輯上是否等價,而不是它們是否引用相同的對象。覆蓋 equals 方法不僅是為了滿足程序員的期望,它還使實例能夠作為 Map 的鍵或 Set 元素時,具有可預測的、理想的行為。 **譯注 1:有一個表示狀態的內部類。沒有覆蓋 equals 方法時,equals 的結果與 s1==s2 相同,為 false,即兩者并不是相同對象的引用。** ``` public static void main(String[] args) { class Status { public String status; } Status s1 = new Status(); Status s2 = new Status(); System.out.println(s1==s2); // false System.out.println(s1.equals(s2)); // false } ``` **譯注 2:覆蓋 equals 方法后,以業務邏輯來判斷是否相同,具備相同 status 字段即為相同。在使用去重功能時,也以此作為判斷依據。** ``` public static void main(String[] args) { class Status { public String status; @Override public boolean equals(Object o) { return Objects.equals(status, ((Status) o).status); } } Status s1 = new Status(); Status s2 = new Status(); System.out.println(s1==s2); // false System.out.println(s1.equals(s2)); // true } ``` One kind of value class that does not require the equals method to be overridden is a class that uses instance control (Item 1) to ensure that at most one object exists with each value. Enum types (Item 34) fall into this category. For these classes, logical equality is the same as object identity, so Object’s equals method functions as a logical equals method. 不需要覆蓋 equals 方法的一種值類是使用實例控件([Item-1](/Chapter-2/Chapter-2-Item-1-Consider-static-factory-methods-instead-of-constructors.md))來確保每個值最多只存在一個對象的類。枚舉類型([Item-34](/Chapter-6/Chapter-6-Item-34-Use-enums-instead-of-int-constants.md))屬于這一類。對于這些類,邏輯相等與對象標識相同,因此對象的 equals 方法函數與邏輯 equals 方法相同。 When you override the equals method, you must adhere to its general contract. Here is the contract, from the specification for Object : 當你覆蓋 equals 方法時,你必須遵守它的通用約定。以下是具體內容,來自 Object 規范: The equals method implements an equivalence relation. It has these properties: equals 方法實現了等價關系。它應有這些屬性: - Reflexive: For any non-null reference value x, x.equals(x) must return true. 反身性:對于任何非空的參考值 x,`x.equals(x)` 必須返回 true。 - Symmetric: For any non-null reference values x and y, x.equals(y) must return true if and only if y.equals(x) returns true. 對稱性:對于任何非空參考值 x 和 y,`x.equals(y)` 必須在且僅當 `y.equals(x)` 返回 true 時返回 true。 - Transitive: For any non-null reference values x, y, z, if x.equals(y) returns true and y.equals(z) returns true, then x.equals(z) must return true. 傳遞性:對于任何非空的引用值 x, y, z,如果 `x.equals(y)` 返回 true,`y.equals(z)` 返回 true,那么 `x.equals(z)` 必須返回 true。 - Consistent: For any non-null reference values x and y, multiple invocations of x.equals(y) must consistently return true or consistently return false, provided no information used in equals comparisons is modified. 一致性:對于任何非空的引用值 x 和 y, `x.equals(y)` 的多次調用必須一致地返回 true 或一致地返回 false,前提是不修改 equals 中使用的信息。 - For any non-null reference value x, x.equals(null) must return false. 對于任何非空引用值 x,`x.equals(null)` 必須返回 false。 Unless you are mathematically inclined, this might look a bit scary, but do not ignore it! If you violate it, you may well find that your program behaves erratically or crashes, and it can be very difficult to pin down the source of the failure. To paraphrase John Donne, no class is an island. Instances of one class are frequently passed to another. Many classes, including all collections classes,depend on the objects passed to them obeying the equals contract. 除非你有數學方面的傾向,否則這些起來有點可怕,但不要忽略它!如果你違反了它,你的程序很可能會出現行為異常或崩潰,并且很難確定失敗的根源。用 John Donne 的話來說,沒有一個類是孤立的。一個類的實例經常被傳遞給另一個類。許多類(包括所有集合類)依賴于傳遞給它們的對象遵守 equals 約定。 Now that you are aware of the dangers of violating the equals contract, let’s go over the contract in detail. The good news is that, appearances notwithstanding, it really isn’t very complicated. Once you understand it, it’s not hard to adhere to it. 既然你已經意識到了違反 equals 約定的危險,讓我們詳細討論一下。好消息是,盡管表面上看起來很復雜,但其實并不復雜。一旦你明白了,就不難堅持下去了。 So what is an equivalence relation? Loosely speaking, it’s an operator that partitions a set of elements into subsets whose elements are deemed equal to one another. These subsets are known as equivalence classes. For an equals method to be useful, all of the elements in each equivalence class must be interchangeable from the perspective of the user. Now let’s examine the five requirements in turn: 什么是等價關系?簡單地說,它是一個操作符,它將一組元素劃分為子集,子集的元素被認為是彼此相等的。這些子集被稱為等價類。為了使 equals 方法有用,從用戶的角度來看,每個等價類中的所有元素都必須是可互換的。現在讓我們依次檢查以下五個需求: **Reflexivity** —The first requirement says merely that an object must be equal to itself. It’s hard to imagine violating this one unintentionally. If you were to violate it and then add an instance of your class to a collection, the contains method might well say that the collection didn’t contain the instance that you just added. **反身性** ,第一個要求僅僅是說一個對象必須等于它自己。很難想象會無意中違反了這條規則。如果你違反了它,然后將類的一個實例添加到集合中,contains 方法很可能會說該集合不包含你剛才添加的實例。 **Symmetry** —The second requirement says that any two objects must agree on whether they are equal. Unlike the first requirement, it’s not hard to imagine violating this one unintentionally. For example, consider the following class,which implements a case-insensitive string. The case of the string is preserved by toString but ignored in equals comparisons: **對稱性** ,第二個要求是任何兩個對象必須在是否相等的問題上達成一致。與第一個要求不同,無意中違反了這個要求的情況不難想象。例如,考慮下面的類,它實現了不區分大小寫的字符串。字符串的情況是保留的 toString,但忽略在 equals 的比較: ``` // Broken - violates symmetry! public final class CaseInsensitiveString { private final String s; public CaseInsensitiveString(String s) { this.s = Objects.requireNonNull(s); } // Broken - violates symmetry! @Override public boolean equals(Object o) { if (o instanceof CaseInsensitiveString) return s.equalsIgnoreCase(((CaseInsensitiveString) o).s); if (o instanceof String) // One-way interoperability! return s.equalsIgnoreCase((String) o); return false; } ... // Remainder omitted } ``` The well-intentioned equals method in this class naively attempts to interoperate with ordinary strings. Let’s suppose that we have one caseinsensitive string and one ordinary one: 這個類中的 equals 方法天真地嘗試與普通字符串進行互操作。假設我們有一個不區分大小寫的字符串和一個普通字符串: ``` CaseInsensitiveString cis = new CaseInsensitiveString("Polish"); String s = "polish"; ``` As expected, cis.equals(s) returns true. The problem is that while the equals method in CaseInsensitiveString knows about ordinary strings, the equals method in String is oblivious to case-insensitive strings.Therefore, s.equals(cis) returns false, a clear violation of symmetry.Suppose you put a case-insensitive string into a collection: 正如預期的那樣,`cis.equals(s)` 返回 true。問題是,雖然 CaseInsensitiveString 中的 equals 方法知道普通字符串,但是 String 中的 equals 方法對不區分大小寫的字符串不知情。因此,`s.equals(cis)` 返回 false,這明顯違反了對稱性。假設你將不區分大小寫的字符串放入集合中: ``` List<CaseInsensitiveString> list = new ArrayList<>(); list.add(cis); ``` What does list.contains(s) return at this point? Who knows? In the current OpenJDK implementation, it happens to return false, but that’s just an implementation artifact. In another implementation, it could just as easily return true or throw a runtime exception. **Once you’ve violated the equals contract, you simply don’t know how other objects will behave when confronted with your object.** 此時 `list.contains(s)` 返回什么?誰知道呢?在當前的 OpenJDK 實現中,它碰巧返回 false,但這只是一個實現案例。在另一個實現中,它可以很容易地返回 true 或拋出運行時異常。一旦你違反了 equals 約定,就不知道當其他對象面對你的對象時,會如何表現。 **譯注:contains 方法在 ArrayList 中的實現源碼如下(省略了源碼中的多行注釋):** ``` // ArrayList 的大小 private int size; // 保存 ArrayList 元素的容器,一個 Object 數組 transient Object[] elementData; // non-private to simplify nested class access public boolean contains(Object o) { return indexOf(o) >= 0; } public int indexOf(Object o) { return indexOfRange(o, 0, size); } int indexOfRange(Object o, int start, int end) { Object[] es = elementData; if (o == null) { for (int i = start; i < end; i++) { if (es[i] == null) { return i; } } } else { for (int i = start; i < end; i++) { if (o.equals(es[i])) { return i; } } } return -1; } ``` To eliminate the problem, merely remove the ill-conceived attempt to interoperate with String from the equals method. Once you do this, you can refactor the method into a single return statement: 為了消除這個問題,只需從 equals 方法中刪除與 String 互操作的錯誤嘗試。一旦你這樣做了,你可以重構方法為一個單一的返回語句: ``` @Override public boolean equals(Object o) { return o instanceof CaseInsensitiveString && ((CaseInsensitiveString) o).s.equalsIgnoreCase(s); } ``` **Transitivity** —The third requirement of the equals contract says that if one object is equal to a second and the second object is equal to a third, then the first object must be equal to the third. Again, it’s not hard to imagine violating this requirement unintentionally. Consider the case of a subclass that adds a new value component to its superclass. In other words, the subclass adds a piece of information that affects equals comparisons. Let’s start with a simple immutable two-dimensional integer point class: **傳遞性** ,equals 約定的第三個要求是,如果一個對象等于第二個對象,而第二個對象等于第三個對象,那么第一個對象必須等于第三個對象。同樣,無意中違反了這個要求的情況不難想象。考慮向超類添加新的值組件時,子類的情況。換句話說,子類添加了一條影響 equals 比較的信息。讓我們從一個簡單的不可變二維整數點類開始: ``` public class Point { private final int x; private final int y; public Point(int x, int y) { this.x = x; this.y = y; } @Override public boolean equals(Object o) { if (!(o instanceof Point)) return false; Point p = (Point)o; return p.x == x && p.y == y; } ... // Remainder omitted } ``` Suppose you want to extend this class, adding the notion of color to a point: 假設你想繼承這個類,對一個點添加顏色的概念: ``` public class ColorPoint extends Point { private final Color color; public ColorPoint(int x, int y, Color color) { super(x, y); this.color = color; } ... // Remainder omitted } ``` How should the equals method look? If you leave it out entirely, the implementation is inherited from Point and color information is ignored in equals comparisons. While this does not violate the equals contract, it is clearly unacceptable. Suppose you write an equals method that returns true only if its argument is another color point with the same position and color: equals 方法應該是什么樣子?如果你完全忽略它,則實現將從 Point 類繼承而來,在 equals 比較中顏色信息將被忽略。雖然這并不違反 equals 約定,但顯然是不可接受的。假設你寫了一個 equals 方法,該方法只有當它的參數是另一個顏色點,且位置和顏色相同時才返回 true: ``` // Broken - violates symmetry! @Override public boolean equals(Object o) { if (!(o instanceof ColorPoint)) return false; return super.equals(o) && ((ColorPoint) o).color == color; } ``` The problem with this method is that you might get different results when comparing a point to a color point and vice versa. The former comparison ignores color, while the latter comparison always returns false because the type of the argument is incorrect. To make this concrete, let’s create one point and one color point: 這種方法的問題是,當你比較一個點和一個顏色點時,你可能會得到不同的結果,反之亦然。前者比較忽略顏色,而后者比較總是返回 false,因為參數的類型是不正確的。為了使問題更具體,讓我們創建一個點和一個顏色點: ``` Point p = new Point(1, 2); ColorPoint cp = new ColorPoint(1, 2, Color.RED); ``` Then p.equals(cp) returns true, while cp.equals(p) returns false. You might try to fix the problem by having ColorPoint.equals ignore color when doing “mixed comparisons”: 然后,`p.equals(cp)` 返回 true,而 `cp.equals(p)` 返回 false。當你做「混合比較」的時候,你可以通過讓 `ColorPoint.equals` 忽略顏色來解決這個問題: ``` // Broken - violates transitivity! @Override public boolean equals(Object o) { if (!(o instanceof Point)) return false; // If o is a normal Point, do a color-blind comparison if (!(o instanceof ColorPoint)) return o.equals(this); // o is a ColorPoint; do a full comparison return super.equals(o) && ((ColorPoint) o).color == color; } ``` This approach does provide symmetry, but at the expense of transitivity: 這種方法確實提供了對稱性,但犧牲了傳遞性: ``` ColorPoint p1 = new ColorPoint(1, 2, Color.RED); Point p2 = new Point(1, 2); ColorPoint p3 = new ColorPoint(1, 2, Color.BLUE); ``` Now p1.equals(p2) and p2.equals(p3) return true, while p1.equals(p3) returns false, a clear violation of transitivity. The first two comparisons are “color-blind,” while the third takes color into account. 現在,`p1.equals(p2)` 和 `p2.equals(p3)` 返回 true,而 `p1.equals(p3)` 返回 false,這明顯違反了傳遞性。前兩個比較是「色盲」,而第三個比較考慮了顏色。 Also, this approach can cause infinite recursion: Suppose there are two subclasses of Point, say ColorPoint and SmellPoint, each with this sort of equals method. Then a call to myColorPoint.equals(mySmellPoint) will throw a StackOverflowError. 同樣,這種方法會導致無限的遞歸:假設有兩個點的子類,比如 ColorPoint 和 SmellPoint,每個都使用這種 equals 方法。然后調用 `myColorPoint.equals(mySmellPoint)` 會拋出 StackOverflowError。 So what’s the solution? It turns out that this is a fundamental problem of equivalence relations in object-oriented languages. **There is no way to extend an instantiable class and add a value component while preserving the equals contract,** unless you’re willing to forgo the benefits of object-oriented abstraction. 那么解決方案是什么?這是面向對象語言中等價關系的一個基本問題。**除非你愿意放棄面向對象的抽象優點,否則無法繼承一個可實例化的類并添加一個值組件,同時保留 equals 約定。** You may hear it said that you can extend an instantiable class and add a value component while preserving the equals contract by using a getClass test in place of the instanceof test in the equals method: 你可能會聽到它說你可以繼承一個實例化的類并添加一個值組件,同時通過在 equals 方法中使用 getClass 測試來代替 instanceof 測試來保持 equals 約定: ``` // Broken - violates Liskov substitution principle (page 43) @Override public boolean equals(Object o) { if (o == null || o.getClass() != getClass()) return false; Point p = (Point) o; return p.x == x && p.y == y; } ``` This has the effect of equating objects only if they have the same implementation class. This may not seem so bad, but the consequences are unacceptable: An instance of a subclass of Point is still a Point, and it still needs to function as one, but it fails to do so if you take this approach! Let’s suppose we want to write a method to tell whether a point is on the unit circle. Here is one way we could do it: 只有當對象具有相同的實現類時,才會產生相等的效果。這可能看起來不是很糟糕,但其后果是不可接受的:Point 的子類的實例仍然是一個 Point,并且它仍然需要作為一個函數來工作,但是如果采用這種方法,它就不會這樣做!假設我們要寫一個方法來判斷一個點是否在單位圓上。我們可以這樣做: ``` // Initialize unitCircle to contain all Points on the unit circle private static final Set<Point> unitCircle = Set.of( new Point( 1, 0), new Point( 0, 1), new Point(-1, 0), new Point( 0, -1) ); public static boolean onUnitCircle(Point p) { return unitCircle.contains(p); } ``` While this may not be the fastest way to implement the functionality, it works fine. Suppose you extend Point in some trivial way that doesn’t add a value component, say, by having its constructor keep track of how many instances have been created: 雖然這可能不是實現功能的最快方法,但它工作得很好。假設你以一種不添加值組件的簡單方式繼承 Point,例如,讓它的構造函數跟蹤創建了多少實例: ``` public class CounterPoint extends Point { private static final AtomicInteger counter = new AtomicInteger(); public CounterPoint(int x, int y) { super(x, y); counter.incrementAndGet(); } public static int numberCreated() { return counter.get(); } } ``` The Liskov substitution principle says that any important property of a type should also hold for all its subtypes so that any method written for the type should work equally well on its subtypes [Liskov87]. This is the formal statement of our earlier claim that a subclass of Point (such as CounterPoint) is still a Point and must act as one. But suppose we pass a CounterPoint to the onUnitCircle method. If the Point class uses a getClass-based equals method, the onUnitCircle method will return false regardless of the CounterPoint instance’s x and y coordinates. This is so because most collections, including the HashSet used by the onUnitCircle method, use the equals method to test for containment, and no CounterPoint instance is equal to any Point. If, however, you use a proper instanceof-based equals method on Point, the same onUnitCircle method works fine when presented with a CounterPoint instance. Liskov 替換原則指出,類型的任何重要屬性都應該適用于所有子類型,因此為類型編寫的任何方法都應該在其子類型上同樣有效 [Liskov87]。這是我們先前做的正式聲明,即點的子類(如 CounterPoint)仍然是一個 Point,并且必須作為一個 Point。但假設我們傳遞了一個 CounterPoint 給 onUnitCircle 方法。如果 Point 類使用基于 getclass 的 equals 方法,那么不管 CounterPoint 實例的 x 和 y 坐標如何,onUnitCircle 方法都會返回 false。這是因為大多數集合,包括 onUnitCircle 方法使用的 HashSet,都使用 equals 方法來測試包含性,沒有一個 CounterPoint 實例等于任何一個點。但是,如果你在 Point 上使用了正確的基于實例的 equals 方法,那么在提供對位實例時,相同的 onUnitCircle 方法就可以很好地工作。 **譯注:里氏替換原則(Liskov Substitution Principle,LSP)面向對象設計的基本原則之一。里氏替換原則指出:任何父類可以出現的地方,子類一定可以出現。LSP 是繼承復用的基石,只有當衍生類可以替換掉父類,軟件單位的功能不受到影響時,父類才能真正被復用,而衍生類也能夠在父類的基礎上增加新的行為。** While there is no satisfactory way to extend an instantiable class and add a value component, there is a fine workaround: Follow the advice of Item 18,“Favor composition over inheritance.” Instead of having ColorPoint extend Point, give ColorPoint a private Point field and a public view method (Item 6) that returns the point at the same position as this color point: 雖然沒有令人滿意的方法來繼承一個可實例化的類并添加一個值組件,但是有一個很好的解決方案:遵循 [Item-18](/Chapter-4/Chapter-4-Item-18-Favor-composition-over-inheritance.md) 的建議,「Favor composition over inheritance.」。給 ColorPoint 一個私有的 Point 字段和一個 public 視圖方法([Item-6](/Chapter-2/Chapter-2-Item-6-Avoid-creating-unnecessary-objects.md)),而不是讓 ColorPoint 繼承 Point,該方法返回與這個顏色點相同位置的點: ``` // Adds a value component without violating the equals contract public class ColorPoint { private final Point point; private final Color color; public ColorPoint(int x, int y, Color color) { point = new Point(x, y); this.color = Objects.requireNonNull(color); } /** * Returns the point-view of this color point. */ public Point asPoint() { return point; } @Override public boolean equals(Object o) { if (!(o instanceof ColorPoint)) return false; ColorPoint cp = (ColorPoint) o; return cp.point.equals(point) && cp.color.equals(color); } ... // Remainder omitted } ``` There are some classes in the Java platform libraries that do extend an instantiable class and add a value component. For example,java.sql.Timestamp extends java.util.Date and adds a nanoseconds field. The equals implementation for Timestamp does violate symmetry and can cause erratic behavior if Timestamp and Date objects are used in the same collection or are otherwise intermixed. The Timestamp class has a disclaimer cautioning programmers against mixing dates and timestamps. While you won’t get into trouble as long as you keep them separate, there’s nothing to prevent you from mixing them, and the resulting errors can be hard to debug. This behavior of the Timestamp class was a mistake and should not be emulated. Java 庫中有一些類確實繼承了一個可實例化的類并添加了一個值組件。例如,`java.sql.Timestamp` 繼承 `java.util.Date` 并添加了納秒字段。如果在同一個集合中使用時間戳和日期對象,或者以其他方式混合使用時間戳和日期對象,那么時間戳的 equals 實現確實違反了對稱性,并且可能導致不穩定的行為。Timestamp 類有一個免責聲明,警告程序員不要混合使用日期和時間戳。雖然只要將它們分開,就不會遇到麻煩,但是沒有什么可以阻止你將它們混合在一起,因此產生的錯誤可能很難調試。時間戳類的這種行為是錯誤的,不應該效仿。 Note that you can add a value component to a subclass of an abstract class without violating the equals contract. This is important for the sort of class hierarchies that you get by following the advice in Item 23, “Prefer class hierarchies to tagged classes.” For example, you could have an abstract class Shape with no value components, a subclass Circle that adds a radius field, and a subclass Rectangle that adds length and width fields.Problems of the sort shown earlier won’t occur so long as it is impossible to create a superclass instance directly. 注意,你可以向抽象類的子類添加一個值組件,而不違反 equals 約定。這對于遵循 [Item-23](/Chapter-4/Chapter-4-Item-23-Prefer-class-hierarchies-to-tagged-classes.md) 中的建議而得到的類層次結構很重要,「Prefer class hierarchies to tagged classes.」。例如,可以有一個沒有值組件的抽象類形狀、一個添加半徑字段的子類圓和一個添加長度和寬度字段的子類矩形。只要不可能直接創建超類實例,前面顯示的那種問題就不會發生。 **Consistency—** The fourth requirement of the equals contract says that if two objects are equal, they must remain equal for all time unless one (or both) of them is modified. In other words, mutable objects can be equal to different objects at different times while immutable objects can’t. When you write a class,think hard about whether it should be immutable (Item 17). If you conclude that it should, make sure that your equals method enforces the restriction that equal objects remain equal and unequal objects remain unequal for all time. **一致性** ,對等約定的第四個要求是,如果兩個對象相等,它們必須一直保持相等,除非其中一個(或兩個)被修改。換句話說,可變對象可以等于不同時間的不同對象,而不可變對象不能。在編寫類時,仔細考慮它是否應該是不可變的([Item-17](/Chapter-4/Chapter-4-Item-17-Minimize-mutability.md))。如果你認為應該這樣做,那么請確保你的 equals 方法執行了這樣的限制,即相等的對象始終是相等的,而不等的對象始終是不等的。 Whether or not a class is immutable, **do not write an equals method that depends on unreliable resources.** It’s extremely difficult to satisfy the consistency requirement if you violate this prohibition. For example,java.net.URL’s equals method relies on comparison of the IP addresses of the hosts associated with the URLs. Translating a host name to an IP address can require network access, and it isn’t guaranteed to yield the same results over time. This can cause the URL equals method to violate the equals contract and has caused problems in practice. The behavior of URL’s equals method was a big mistake and should not be emulated. Unfortunately, it cannot be changed due to compatibility requirements. To avoid this sort of problem,equals methods should perform only deterministic computations on memoryresident objects. 無論一個類是否不可變,都不要編寫依賴于不可靠資源的 equals 方法。如果你違反了這個禁令,就很難滿足一致性要求。例如,`java.net.URL` 的 equals 方法依賴于與 url 相關聯的主機的 IP 地址的比較。將主機名轉換為 IP 地址可能需要網絡訪問,而且不能保證隨著時間的推移產生相同的結果。這可能會導致 URL 的 equals 方法違反約定,并在實踐中造成問題。URL 的 equals 方法的行為是一個很大的錯誤,不應該被模仿。不幸的是,由于兼容性需求,它不能更改。為了避免這種問題,equals 方法應該只對 memoryresident 對象執行確定性計算。 **Non-nullity—** The final requirement lacks an official name, so I have taken the liberty of calling it “non-nullity.” It says that all objects must be unequal to null. While it is hard to imagine accidentally returning true in response to the invocation o.equals(null), it isn’t hard to imagine accidentally throwing a NullPointerException. The general contract prohibits this.Many classes have equals methods that guard against it with an explicit test for null: **非無效性** ,最后的要求沒有一個正式的名稱,所以我冒昧地稱之為「非無效性」。它說所有對象都不等于 null。雖然很難想象在響應調用 `o.equals(null)` 時意外地返回 true,但不難想象意外地拋出 NullPointerException。一般約定中禁止這樣做。許多類都有相等的方法,通過顯式的 null 測試來防止它: ``` @Override public boolean equals(Object o) { if (o == null) return false; ... } ``` This test is unnecessary. To test its argument for equality, the equals method must first cast its argument to an appropriate type so its accessors can be invoked or its fields accessed. Before doing the cast, the method must use the instanceof operator to check that its argument is of the correct type: 這個測試是不必要的。要測試其參數是否相等,equals 方法必須首先將其參數轉換為適當的類型,以便能夠調用其訪問器或訪問其字段。在執行轉換之前,方法必須使用 instanceof 運算符來檢查其參數的類型是否正確: ``` @Override public boolean equals(Object o) { if (!(o instanceof MyType)) return false; MyType mt = (MyType) o; ... } ``` If this type check were missing and the equals method were passed an argument of the wrong type, the equals method would throw a ClassCastException, which violates the equals contract. But the instanceof operator is specified to return false if its first operand is null,regardless of what type appears in the second operand [JLS, 15.20.2]. Therefore,the type check will return false if null is passed in, so you don’t need an explicit null check. 如果缺少這個類型檢查,并且 equals 方法傳遞了一個錯誤類型的參數,equals 方法將拋出 ClassCastException,這違反了 equals 約定。但是,如果 instanceof 操作符的第一個操作數為空,則指定該操作符返回 false,而不管第二個操作數 [JLS, 15.20.2] 中出現的是什么類型。因此,如果傳入 null,類型檢查將返回 false,因此不需要顯式的 null 檢查。 Putting it all together, here’s a recipe for a high-quality equals method: 綜上所述,這里有一個高質量構建 equals 方法的秘訣: 1、**Use the == operator to check if the argument is a reference to this object.** If so, return true. This is just a performance optimization but one that is worth doing if the comparison is potentially expensive. **使用 == 運算符檢查參數是否是對該對象的引用。** 如果是,返回 true。這只是一種性能優化,但如果比較的代價可能很高,那么這種優化是值得的。 2、**Use the instanceof operator to check if the argument has the correct type.** If not, return false. Typically, the correct type is the class in which the method occurs. Occasionally, it is some interface implemented by this class. Use an interface if the class implements an interface that refines the equals contract to permit comparisons across classes that implement the interface. Collection interfaces such as Set, List, Map, and Map.Entry have this property. **使用 instanceof 運算符檢查參數是否具有正確的類型。** 如果不是,返回 false。通常,正確的類型是方法發生的類。有時候,它是由這個類實現的某個接口。如果類實現了一個接口,該接口對 equals 約定進行了改進,以允許跨實現該接口的類進行比較,則使用該接口。集合接口,如 Set、List、Map 和 Map.Entry 具有此屬性。 3、**Cast the argument to the correct type.** Because this cast was preceded by an instanceof test, it is guaranteed to succeed. **將參數轉換為正確的類型。** 因為在這個強制類型轉換之前有一個實例測試,所以它肯定會成功。 4、**For each “significant” field in the class, check if that field of the argument matches the corresponding field of this object.** If all these tests succeed, return true; otherwise, return false. If the type in Step 2 is an interface, you must access the argument’s fields via interface methods; if the type is a class, you may be able to access the fields directly, depending on their accessibility. **對于類中的每個「重要」字段,檢查參數的字段是否與該對象的相應字段匹配。** 如果所有這些測試都成功,返回 true;否則返回 false。如果第 2 步中的類型是接口,則必須通過接口方法訪問參數的字段;如果是類,你可以根據字段的可訪問性直接訪問它們。 For primitive fields whose type is not float or double, use the == operator for comparisons; for object reference fields, call the equals method recursively; for float fields, use the static Float.compare(float,float) method; and for double fields, use Double.compare(double, double). The special treatment of float and double fields is made necessary by the existence of Float.NaN, -0.0f and the analogous double values; see JLS 15.21.1 or the documentation of Float.equals for details. While you could compare float and double fields with the static methods Float.equals and Double.equals, this would entail autoboxing on every comparison, which would have poor performance. For array fields, apply these guidelines to each element. If every element in an array field is significant, use one of the Arrays.equals methods. 對于類型不是 float 或 double 的基本類型字段,使用 == 運算符進行比較;對于對象引用字段,遞歸調用 equals 方法;對于 float 字段,使用 `static Float.compare(float,float)` 方法;對于 double 字段,使用 `Double.compare(double, double)`。float 和 double 字段的特殊處理是由于 `Float.NaN`、-0.0f 和類似的雙重值的存在而必須的;請參閱 JLS 15.21.1 或 `Float.equals` 文檔。雖然你可以將 float 和 double 字段與靜態方法 Float.equals 和 Double.equals 進行比較,這將需要在每個比較上進行自動裝箱,這將有較差的性能。對于數組字段,將這些指導原則應用于每個元素。如果數組字段中的每個元素都很重要,那么使用 `Arrays.equals` 方法之一。 Some object reference fields may legitimately contain null. To avoid the possibility of a NullPointerException, check such fields for equality using the static method Objects.equals(Object, Object). 一些對象引用字段可能合法地包含 null。為了避免可能出現 NullPointerException,請使用靜態方法 `Objects.equals(Object, Object)` 檢查這些字段是否相等。 For some classes, such as CaseInsensitiveString above, field comparisons are more complex than simple equality tests. If this is the case,you may want to store a canonical form of the field so the equals method can do a cheap exact comparison on canonical forms rather than a more costly nonstandard comparison. This technique is most appropriate for immutable classes (Item 17); if the object can change, you must keep the canonical form up to date. 對于某些類,例如上面的 CaseInsensitiveString,字段比較比簡單的 equal 測試更復雜。如果是這樣,你可能希望存儲字段的規范形式,以便 equals 方法可以對規范形式進行廉價的精確比較,而不是更昂貴的非標準比較。這種技術最適合于不可變類([Item-17](/Chapter-4/Chapter-4-Item-17-Minimize-mutability.md));如果對象可以更改,則必須使規范形式保持最新。 The performance of the equals method may be affected by the order in which fields are compared. For best performance, you should first compare fields that are more likely to differ, less expensive to compare, or, ideally,both. You must not compare fields that are not part of an object’s logical state,such as lock fields used to synchronize operations. You need not compare derived fields, which can be calculated from “significant fields,” but doing so may improve the performance of the equals method. If a derived field amounts to a summary description of the entire object, comparing this field will save you the expense of comparing the actual data if the comparison fails.For example, suppose you have a Polygon class, and you cache the area. If two polygons have unequal areas, you needn’t bother comparing their edges and vertices. equals 方法的性能可能會受到字段比較順序的影響。為了獲得最佳性能,你應該首先比較那些更可能不同、比較成本更低的字段,或者理想情況下兩者都比較。不能比較不屬于對象邏輯狀態的字段,例如用于同步操作的鎖字段。你不需要比較派生字段(可以從「重要字段」計算),但是這樣做可能會提高 equals 方法的性能。如果派生字段相當于整個對象的摘要描述,那么如果比較失敗,比較該字段將節省比較實際數據的開銷。例如,假設你有一個多邊形類,你緩存這個區域。如果兩個多邊形的面積不相等,你不需要比較它們的邊和頂點。 **When you are finished writing your equals method, ask yourself three questions: Is it symmetric? Is it transitive? Is it consistent?** And don’t just ask yourself; write unit tests to check, unless you used AutoValue (page 49) to generate your equals method, in which case you can safely omit the tests. If the properties fail to hold, figure out why, and modify the equals method accordingly. Of course your equals method must also satisfy the other two properties (reflexivity and non-nullity), but these two usually take care of themselves. **寫完 equals 方法后,問自己三個問題:它具備對稱性嗎?具備傳遞性嗎?具備一致性嗎?** 不要只問自己,要編寫單元測試來檢查,除非使用 AutoValue(第 49 頁)來生成 equals 方法,在這種情況下,你可以安全地省略測試。如果屬性不能保持,請找出原因,并相應地修改 equals 方法。當然,equals 方法還必須滿足其他兩個屬性(反身性和非無效性),但這兩個通常會自己處理。 An equals method constructed according to the previous recipe is shown in this simplistic PhoneNumber class: 在這個簡單的 PhoneNumber 類中,根據前面的方法構造了一個 equals 方法: ``` // Class with a typical equals method public final class PhoneNumber { private final short areaCode, prefix, lineNum; public PhoneNumber(int areaCode, int prefix, int lineNum) { this.areaCode = rangeCheck(areaCode, 999, "area code"); this.prefix = rangeCheck(prefix, 999, "prefix"); this.lineNum = rangeCheck(lineNum, 9999, "line num"); } private static short rangeCheck(int val, int max, String arg) { if (val < 0 || val > max) throw new IllegalArgumentException(arg + ": " + val); return (short) val; } @Override public boolean equals(Object o) { if (o == this) return true; if (!(o instanceof PhoneNumber)) return false; PhoneNumber pn = (PhoneNumber)o; return pn.lineNum == lineNum && pn.prefix == prefix && pn.areaCode == areaCode; } ... // Remainder omitted } ``` Here are a few final caveats: 以下是一些最后的警告: - **Always override hashCode when you override equals (Item 11).** **當你覆蓋 equals 時,也覆蓋 hashCode。**([Item-11](/Chapter-3/Chapter-3-Item-11-Always-override-hashCode-when-you-override-equals.md)) - **Don’t try to be too clever.** If you simply test fields for equality, it’s not hard to adhere to the equals contract. If you are overly aggressive in searching for equivalence, it’s easy to get into trouble. It is generally a bad idea to take any form of aliasing into account. For example, the File class shouldn’t attempt to equate symbolic links referring to the same file. Thankfully, it doesn’t. **不要自作聰明。** 如果你只是為了判斷相等性而測試字段,那么遵循 equals 約定并不困難。如果你在尋求對等方面過于激進,很容易陷入麻煩。一般來說,考慮到任何形式的混疊都不是一個好主意。例如,File 類不應該嘗試將引用同一文件的符號鏈接等同起來。值得慶幸的是,它不是。 - **Don’t substitute another type for Object in the equals declaration.** It is not uncommon for a programmer to write an equals method that looks like this and then spend hours puzzling over why it doesn’t work properly: **不要用另一種類型替換 equals 聲明中的對象。** 對于程序員來說,編寫一個類似于這樣的 equals 方法,然后花上幾個小時思考為什么它不能正常工作是很常見的: ``` // Broken - parameter type must be Object! public boolean equals(MyClass o) { ... } ``` The problem is that this method does not override Object.equals,whose argument is of type Object, but overloads it instead (Item 52). It is unacceptable to provide such a “strongly typed” equals method even in addition to the normal one, because it can cause Override annotations in subclasses to generate false positives and provide a false sense of security. 這里的問題是,這個方法沒有覆蓋其參數類型為 Object 的 Object.equals,而是重載了它([Item-52](/Chapter-8/Chapter-8-Item-52-Use-overloading-judiciously.md))。即使是普通的方法,提供這樣一個「強類型的」equals 方法是不可接受的,因為它會導致子類中的重寫注釋產生誤報并提供錯誤的安全性。 Consistent use of the Override annotation, as illustrated throughout this item, will prevent you from making this mistake (Item 40). This equals method won’t compile, and the error message will tell you exactly what is wrong: 如本條目所示,一致使用 Override 注釋將防止你犯此錯誤([Item-40](/Chapter-6/Chapter-6-Item-40-Consistently-use-the-Override-annotation.md))。這個 equals 方法不會編譯,錯誤消息會告訴你什么是錯誤的: ``` // Still broken, but won’t compile @Override public boolean equals(MyClass o) { ... } ``` Writing and testing equals (and hashCode) methods is tedious, and the resulting code is mundane. An excellent alternative to writing and testing these methods manually is to use Google’s open source AutoValue framework, which automatically generates these methods for you, triggered by a single annotation on the class . In most cases, the methods generated by AutoValue are essentially identical to those you’d write yourself. 編寫和測試 equals (和 hashCode)方法很乏味,生成的代碼也很單調。手動編寫和測試這些方法的一個很好的替代方法是使用谷歌的開源 AutoValue 框架,它會自動為你生成這些方法,由類上的一個注釋觸發。在大多數情況下,AutoValue 生成的方法與你自己編寫的方法基本相同。 IDEs, too, have facilities to generate equals and hashCode methods, but the resulting source code is more verbose and less readable than code that uses AutoValue, does not track changes in the class automatically, and therefore requires testing. That said, having IDEs generate equals (and hashCode)methods is generally preferable to implementing them manually because IDEs do not make careless mistakes, and humans do. IDE 也有生成 equals 和 hashCode 方法的功能,但是生成的源代碼比使用 AutoValue 的代碼更冗長,可讀性更差,不會自動跟蹤類中的變化,因此需要進行測試。也就是說,讓 IDE 生成 equals(和 hashCode)方法通常比手動實現更可取,因為 IDE 不會出現粗心的錯誤,而人會。 In summary, don’t override the equals method unless you have to: in many cases, the implementation inherited from Object does exactly what you want.If you do override equals, make sure to compare all of the class’s significant fields and to compare them in a manner that preserves all five provisions of the equals contract. 總之,除非必須,否則不要覆蓋 equals 方法:在許多情況下,從 Object 繼承而來的實現正是你想要的。如果你確實覆蓋了 equals,那么一定要比較類的所有重要字段,并以保留 equals 約定的所有 5 項規定的方式進行比較。 --- **[Back to contents of the chapter(返回章節目錄)](/Chapter-3/Chapter-3-Introduction.md)** - **Next Item(下一條目):[Item 11: Always override hashCode when you override equals(當覆蓋 equals 方法時,總要覆蓋 hashCode 方法)](/Chapter-3/Chapter-3-Item-11-Always-override-hashCode-when-you-override-equals.md)**
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看