2011-07-06 61 views
10

我遇到了使ArrayList正確使用重寫equals的問題。問題是我試圖使用equals來僅測試單個鍵字段,並使用ArrayList.contains()來測試是否存在具有正確字段的對象。下面是一個例子ArrayList未使用重寫的等於

public class TestClass { 
    private static class InnerClass{  
    private final String testKey; 
    //data and such 

    InnerClass(String testKey, int dataStuff) { 
     this.testKey =testKey; 
     //etc 
    } 
    @Override 
    public boolean equals (Object in) { 
     System.out.println("reached here"); 
     if(in == null) { 
     return false; 
     }else if(in instanceof String) { 
     String inString = (String) in; 
     return testKey == null ? false : testKey.equals(inString); 
     }else { 
     return false; 
     }  
    }  
    } 

    public static void main(String[] args) {  
    ArrayList<InnerClass> objectList = new ArrayList<InnerClass>(); 
    //add some entries 
    objectList.add(new InnerClass("UNIQUE ID1", 42)); 
    System.out.println(objectList.contains("UNIQUE ID1")); 
    }  
} 

我擔心的是,我不僅在輸出越來越假,但我還沒有得到「到達這裏」輸出。

有沒有人有任何想法,爲什麼這個覆蓋被完全忽略?對於我不知道的重寫和內部類有一些微妙之處嗎?

編輯: 有問題的網站,所以我似乎無法標記答案。 感謝您的快速回應:是我的一個疏忽,它是String .equals thta被稱爲,而不是我自定義的一個。我猜這是現在的老式檢查

回答

15

如果您檢查ArrayList的來源,您會看到它調用equals其他對象。在你的情況下,它會調用String "UNIQUE ID1"equals將檢查其他對象是String類型的不和只是返回false

public boolean contains(Object o) { 
    return indexOf(o) >= 0; 
} 

public int indexOf(Object o) { 
    ...  
    for (int i = 0; i < size; i++) 
    if (o.equals(elementData[i])) 
     return i; 
    ... 
    return -1; 
} 

對於您的情況下調用containsInnerClass只包含id

objectList.contains(new InnerClass("UNIQUE ID1")) 

不要忘記實施equalsInnerClass,它只比較id

+0

謝謝。關於新的InnerClass()的建議也很有幫助。 :) – Sufian

+1

在我看來indexOf的實施應該是相反的方式:elementData [i] .equals(o)... – marcolopes

0

雖然不回答你的問題,許多收藏品使用hashcode()。您也應該覆蓋它以與equals()「同意」。

其實,你應該始終實施equalshashcode在一起,他們應該永遠是相互一致。至於的Javadoc Object.equals()狀態:

注意,它通常是必要的 覆蓋每當 這種方法被重寫hashCode方法,以 保持對 hashCode方法,其中指出, 總承包合同相同的對象必須具有相同的散列碼 代碼。

具體來說,許多集合依賴於這個合同被維護 - 行爲是未定的,否則。

+1

儘管確實應該實現兩者,但在這種情況下它並沒有幫助,因爲'ArrayList'不是基於散列的:它並不關心hashCode()'實現。 –

+0

'ArrayList'不使用'hashCode' –

+0

好。我「凝聚」了我的答案。好點你們倆 – Bohemian

3

你調用contains用,這是一個String而不是InnerClass參數:

System.out.println(objectList.contains("UNIQUE ID1")) 

在我的JDK:

public class ArrayList { 

    public boolean contains(Object o) { 
    return indexOf(o) >= 0; 
    } 

    public int indexOf(Object o) { 
    if (o == null) { 
     // omitted for brevity - aix 
    } else { 
     for (int i = 0; i < size; i++) 
     if (o.equals(elementData[i])) // <<<<<<<<<<<<<<<<<<<<<< 
      return i; 
    } 
    return -1; 
    } 
} 

注意如何indexOf電話o.equals()。在你的情況下,oString,所以你的objectList.contains將使用String.equals而不是InnerClass.equals

7

根據the JavaDoc of List.contains(o),它被定義爲返回true

當且僅當該列表中包含的至少一種元素e使得(o==null ? e==null : o.equals(e))

注意,這個定義呼籲oequals,這是參數那就是在List的元素。

因此將調用String.equals()而不是InnerClass.equals()

還要注意的是the contract for Object.equals()指出

這是對稱:對於任何非空引用值xyx.equals(y)應該返回true當且僅當y.equals(x)回報true

但你違反此約束,因爲new TestClass("foo", 1).equals("foo")回報true"foo".equals(new TestClass("foo", 1))總是返回false

不幸的是,這意味着您的用例(可以等於另一個標準類的自定義類)無法以完全符合的方式實現。

如果你仍然想要做這樣的事情,你必須閱讀你的所有集合類非常仔細的規範(有時是執行),並檢查是否有缺陷,如這一點。

+0

工作時間太長。當你開始忘記基本知識或誤讀api規範時,從來沒有一個好跡象。我發現有一個新的InnerClass(String testKey)構造函數來生成一個測試對象,但實際數據爲null/0的解決方法。 –

2

通常,您還需要覆蓋hashCode(),但這不是主要問題。你有一個不對稱的方法。文檔清楚地表明它應該是對稱的:

它是對稱的:對於任何非空引用值x和y,當且僅當y.equals(x)返回true時,x.equals )返回true。

而你觀察到的是由於違約而導致的意外行爲。

創建遍歷所有項目,並在字符串equals(..)驗證的實用方法:

public static boolean containsString(List<InnerClass> items, String str) { 
    for (InnerClass item : items) { 
     if (item.getTestKey().equals(str)) { 
      return true; 
     } 
    } 
    return false; 
} 

你可以做類似的事情,與番石榴的Iterables.any(..)方法:

final String str = "Foo"; 
boolean contains = Iterables.any(items, new Predicate<InnerClass>() { 
    @Override 
    public boolean apply(InnerClass input){ 
     return input.getTestKey().equals(str); 
    } 
} 
1

你平等的實現是錯誤的。你的參數不應該是String。它應該是一個InnerClass

public boolean equals(Object o) { 
    if (this == o) return true; 
    if (!(o instanceof InnerClass) return false; 
    InnerClass that = (InnerClass)o; 
    // check for null keys if you need to 
    return this.testKey.equals(that.testKey); 
} 

(注:instanceof null返回false,所以你不需要檢查空第一)。

你會再測試在您的列表中相當於對象存在使用:

objectList.contains(new InnerClass("UNIQUE ID1")); 

但是,如果你真的想通過String鍵檢查將InnerClass,爲什麼不使用Map<String,InnerClass>呢?

+0

直截了當地說,自從我上一次使用Java已經過去了大約6年之後,雖然我有編碼者的思維方式,但我缺乏經驗。自從我最初學習C以來,我還不完全熟悉Maps或Java哈希。現在使用Java是因爲對於小型應用程序/工具開發而言,速度要快得多,我需要體面的國際化和平臺獨立性。我添加了一個構造函數(以及用於實現抽象父類,姐妹類的其他一些類),以僅使用測試密鑰創建測試對象。 –

0

有你的代碼的幾個問題。 我的建議是爲了避免重寫完全平等的,如果你不熟悉它,並將其擴展到一個新的實現是這樣的...

class MyCustomArrayList extends ArrayList<InnerClass>{ 

    public boolean containsString(String value){ 
     for(InnerClass item : this){ 
      if (item.getString().equals(value){ 
       return true; 
      } 
     } 
     return false; 
    } 

} 

然後,你可以這樣做

List myList = new MyCustomArrayList() 
myList.containsString("some string"); 

我認爲這是因爲,如果你重載equals也應該覆蓋的hashCode,似乎你缺乏這方面的小知識 - 所以我只想避免。

此外,包含方法調用equals方法這就是爲什麼你看到的「到達這裏」。同樣,如果你不瞭解通話流程,我會避免它。

+0

我的確瞭解了調用流程,但錯過了它使用String.equals而不是InnerClass.equals。由於ArrayList不是基於散列的,我沒有必要在代碼片段中包含hashCode。當然,我包含一個hashCode覆蓋,即使它在這裏需要的所有東西都是返回testKey.hashCode() –

0

在其他的方式,您等於方法被,如果你改變你的代碼如下調用。希望這清除了這個概念。

package com.test; 

import java.util.ArrayList;  
import java.util.List; 

public class TestClass { 
    private static class InnerClass{  
     private final String testKey; 
     //data and such 

     InnerClass(String testKey, int dataStuff) { 
      this.testKey =testKey; 
      //etc 
     } 

     @Override 
     public boolean equals (Object in1) { 
      System.out.println("reached here"); 
      if(in1 == null) { 
       return false; 
      }else if(in1 instanceof InnerClass) { 
       return ((InnerClass) this).testKey == null ? false : ((InnerClass) this).testKey.equals(((InnerClass) in1).testKey); 
      }else { 
       return false; 
      }  
     }  
    } 

    public static void main(String[] args) {  
     ArrayList<InnerClass> objectList = new ArrayList<InnerClass>(); 
     InnerClass in1 = new InnerClass("UNIQUE ID1", 42); 
     InnerClass in2 = new InnerClass("UNIQUE ID1", 42); 

     //add some entries 
     objectList.add(in1); 
     System.out.println(objectList.contains(in2)); 
    }  
} 
0

很多帖子都說過,問題是list.indexOf(obj)函數調用obj的「equals」,而不是列表中的項目。

我有同樣的問題,「包括()」,沒有滿足我,因爲我需要知道哪裏是元素!我的意見是創建一個只有參數比較的空元素,然後調用indexOf。

實現這樣的功能,

public static InnerClass empty(String testKey) { 
    InnerClass in = new InnerClass(); 
    in.testKey =testKey; 
    return in; 
} 

,然後調用的indexOf像這樣:

ind position = list.indexOf(InnerClass.empty(key)); 
0

有在你的代碼的兩個錯誤。

第一: 「包含」要求「鏈表類」對象應該傳遞一個新的對象將InnerClass作爲參數方法。

第二個: equals方法(應該接受參數爲Object,並且是正確的)應根據接收到的對象正確處理代碼。 像這樣:

@Override 
    public boolean equals (Object in) { 
     System.out.println("reached here"); 
     if(in == null) { 
     return false; 
     }else if(in instanceof InnerClass) { 
     String inString = ((InnerClass)in).testKey; 
     return testKey == null ? false : testKey.equals(inString); 
     }else { 
     return false; 
     }  
    } 
0

這個職位是第一次編寫的Java 8是可用的,但現在,它是2017年而不是使用List.containts(...)方法,你可以這樣使用新的Java 8路前:

System.out.println(objectList.stream().filter(obj -> obj.getTestKey().equals("UNIQUE ID1")).findAny().isPresent()); 

,給你的TestClass一個getter你密押領域:

public String getTestKey() { 

    return testKey; 
} 

這種方法的好處是,你不必修改等號或哈希方法和you'l我看起來像你的同齡人的老闆!