Java Comparable接口的陷阱
Java Comparable接口提供了一種對實現該接口的類進行自然排序的方法。 自然順序對標量和其他非常簡單的對象有意義,但是當我們使用面向業務的領域對象時,自然順序就變得更加復雜。 從業務經理的角度來看,交易對象的自然順序可以是交易的價值,但是從系統管理員的角度來看,自然順序可以是交易的速度。 在大多數情況下,業務域對象沒有明確的自然順序。
假設我們已經為諸如Company這樣的類找到了良好的自然排序。 我們將使用公司的正式名稱作為主要訂單字段,并使用公司ID作為次要字段。 公司類的實現可以如下。
public class Company implements Comparable<Company> {private final String id;private final String officialName;public Company(final String id, final String officialName) {this.id = id;this.officialName = officialName;}public String getId() {return id;}public String getOfficialName() {return officialName;}@Overridepublic int hashCode() {HashCodeBuilder builder = new HashCodeBuilder(17, 29);builder.append(this.getId());builder.append(this.getOfficialName());return builder.toHashCode();}@Overridepublic boolean equals(final Object obj) {if (obj == this) {return true;}if (!(obj instanceof Company)) {return false;}Company other = (Company) obj;EqualsBuilder builder = new EqualsBuilder();builder.append(this.getId(), other.getId());builder.append(this.getOfficialName(), other.getOfficialName());return builder.isEquals();}@Overridepublic int compareTo(final Company obj) {CompareToBuilder builder = new CompareToBuilder();builder.append(this.getOfficialName(), obj.getOfficialName());builder.append(this.getId(), obj.getId());return builder.toComparison();} }該實現看起來不錯并且可以正常工作。 對于某些用例,Company類是不夠的,因此我們將其擴展到CompanyDetails類,該類提供有關公司的更多信息。 例如,可以在顯示公司詳細信息的數據表中使用這些類的實例。
public class CompanyDetails extends Company {private final String marketingName;private final Double marketValue;public CompanyDetails(final String id, final String officialName, final String marketingName, final Double marketValue) {super(id, officialName);this.marketingName = marketingName;this.marketValue = marketValue;}public String getMarketingName() {return marketingName;}public Double getMarketValue() {return marketValue;}@Overridepublic int hashCode() {HashCodeBuilder builder = new HashCodeBuilder(19, 31);builder.appendSuper(super.hashCode());builder.append(this.getMarketingName());return builder.toHashCode();}@Overridepublic boolean equals(final Object obj) {if (obj == this) {return true;}if (!(obj instanceof CompanyDetails)) {return false;}CompanyDetails other = (CompanyDetails) obj;EqualsBuilder builder = new EqualsBuilder();builder.appendSuper(super.equals(obj));builder.append(this.getMarketingName(), other.getMarketingName());builder.append(this.getMarketValue(), other.getMarketValue());return builder.isEquals();} }乍一看,該實現看起來還不錯,但實際上并非如此。 我們可以創建一個小的測試用例來指示實現問題。 當我們不知道類的實際接口在做什么,并且我們對擴展的超類的所有細節沒有給予足夠的關注時,就會出現問題。
CompanyDetails c1 = new CompanyDetails("231412", "McDonalds Ltd", "McDonalds food factory", 120000.00); CompanyDetails c2 = new CompanyDetails("231412", "McDonalds Ltd", "McDonalds restaurants", 60000.00);Set<CompanyDetails> set1 = CompaniesFactory.createCompanies1(); set1.add(c1); set1.add(c2);Set<CompanyDetails> set2 = CompaniesFactory.createCompanies2(); set2.add(c1); set2.add(c2);Assert.assertEquals(set1.size(), set2.size());我們使用兩個集合,但要意識到它們的行為有所不同。 這是為什么? 另一個集是HashSet,它依賴于對象的hashCode()和equals()方法,而另一個是TreeSet,并且僅依賴于Comparable接口,而我們并未為子類實現該接口。 擴展域對象時,這是一個很常見的錯誤,但更重要的是,這與錯誤的編碼約定有關。 我們使用Apache Commons的構建器來實現hashCode() , equals()和compareTo()方法。 建設者提供了appendSuper()方法,該方法指示應將其用于該方法的超類的實現。 如果您讀過Joshua Bloch撰寫的精彩著作《 Effective Java》 ,您將意識到這是不對的。 如果在子類中添加字段,則在不違反對稱規則的情況下,我們無法正確實現equals()或compareTo()方法。 我們應該使用組合而不是繼承。 如果我們使用組合來創建CompanyDetails,那么Comparable接口就不會有問題,因為我們不會自動實現它,并且默認情況下允許行為異常。 而且我們也可以適當地滿足equals()和hashCode()的要求。
這篇文章中提到的問題很普遍,但通常被忽略。 可比接口的問題實際上是由不正確的約定引起的,并且不了解所用接口的要求。 作為Java開發人員或架構師,您應該注意諸如此類的事情,并遵守良好的編碼約定和實踐。 項目越大,避免人為因素造成的錯誤就越重要。 我試圖為可比接口總結一個良好的最佳實踐列表,以便可以避免錯誤。
Java可比接口設計和用法的最佳實踐:
- 了解您正在創建的域對象,如果該對象沒有明確的自然順序,則不要實現Comparable接口。
- 比Comparable更喜歡Comparator實現。 比較器可以根據用例以更面向業務的方式使用。
- 如果您需要創建依賴于比較對象的接口或庫,請盡可能提供自己的Comparator實現,否則創建良好的文檔,說明如何為您的接口實現Comparator。
- 遵守良好的編碼約定和慣例。 有效的Java是入門的好書。
翻譯自: https://www.javacodegeeks.com/2013/08/pitfalls-of-java-comparable-interface.html
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的Java Comparable接口的陷阱的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 360系统重装大师重装系统怎么样?(36
- 下一篇: 什么是复数域 复数域的名词解释