2009-10-27 3 views
19

Ich versuche, equals Methode für eine parametrisierte Klasse zu überschreiben.Überschreiben "gleich" -Methode: Wie man den Typ des Parameters herausfinden?

@Override 
public boolean equals(Object obj) { 
    if (this == obj) 
     return true; 
    if (obj == null) 
     return false; 
    if (!(obj instanceof Tuple)) 
     return false; 

    Tuple<E> other = (Tuple<E>) obj; //unchecked cast 
    if (!a0.equals(other.a0) && !a0.equals(other.a1)) { 
     return false; 
    } 
    if (!a1.equals(other.a1) && !a1.equals(other.a0)) { 
     return false; 
    } 

    return true; 
} 

Wie kann ich sicherstellen, dass <E> des other Objekt als this das gleiche ist?

+2

BTW bedeutet diese Logik nicht, dass "a", "a" und "a", "b" gleich sind? Wäre es nicht einfacher zu schreiben "wenn a0 = a0 und a1 = a1 oder a0 = a1 und a1 = a0 ..." –

+0

Wenn Sie zulassen, dass Ihre a0 und a1 null sind, stellen Sie sicher, dass Sie Prüfungen hinzufügen dafür. –

Antwort

15

Sie können dies tun, indem Sie einen Verweis auf Class<E> Typ beibehalten. Meiner Meinung nach sollten jedoch Gleichheitsprüfungen über die Werte sein, die die Objekte darstellen, anstatt über die konkreten Typen, die die Werte erhalten.

Ein klassisches Beispiel hierfür ist das Collections API. new ArrayList<String>().equals(new LinkedList<Object>()) gibt true zurück. Während diese völlig unterschiedliche Typen haben, stellen sie den gleichen Wert dar, nämlich "eine leere Sammlung".

persönlich sollten zwei Tuple s, die die gleichen Daten (z.B. ("a", "b")) stellen nicht gleich sein, weil ein Typ ist, während der andere Tuple<String>Tuple<Object> ist?

+0

Genau ... das Speichern von Klassen-Token ist in diesem Fall sinnlos. –

+5

Wie wäre es mit Id und Id

, auch wenn der tatsächliche Wert innerhalb des Objekts (die Datenbank-ID) gleich ist, denke ich nicht, dass sie als gleich betrachtet werden sollten, da sie IDs für verschiedene DB-Spalten darstellen. – herman

+1

+1 für eine klare Erklärung, aber ich würde auch hier, was in der equals-Methode zu schreiben, wenn der Parameter-Typ nicht wichtig ist, um die Compiler-Warnungen zu entfernen. Für andere, die dies lesen, gibt es eine andere Antwort auf dieser Seite, die den Platzhalter erwähnt und es ist hier zu verwenden. – C0M37

4

Wegen der Löschung können Sie nicht. Das Beste, was Sie tun können, ist, in der Tupel-Klasse den Typ zu speichern, den Sie für das Tuple planen, um es in einem "java.lang.Class" -Feldelement zu speichern. Dann könnten Sie diese Felder vergleichen, um sicherzustellen, dass die Tupelklasse dieselben Typen enthält.

Siehe auch diesen Thread: What is the equivalent of the C++ Pair<L,R> in Java?

Es würde helfen, wenn Sie mehr über Ihre Klasse schreiben. Ich denke, die ungeprüfte Besetzung und Ihre Anzahl von Feldern, die Sie gleichsetzen, bedeutet, dass es Tuple sein sollte < E, F > nein?

EDIT: hier ist eine nützliche Pair-Klasse, die ich regelmäßig verwende (Sie können Ihre Tuple-Klasse bei Bedarf anpassen). Beachten Sie, dass diese Klasse, ähnlich den Vorschlägen anderer, die befragten Mitglieder dazu bringt, die Frage der Gleichheit zu entscheiden. Ihr Use Case sollte bestimmen, ob die Gleichheit tatsächlich auf dem Typ der enthaltenen Elemente basiert.

/** 
* Adapted from http://forums.sun.com/thread.jspa?threadID=5132045 
* 
* 
* @author Tim Harsch 
* 
* @param <L> 
* @param <R> 
*/ 
public class Pair<L, R> { 

    private final L left; 
    private final R right; 

    public R getRight() { 
     return right; 
    } // end getter 

    public L getLeft() { 
     return left; 
    } // end getter 

    public Pair(final L left, final R right) { 
     this.left = left; 
     this.right = right; 
    } // end constructor 

    public static <A, B> Pair<A, B> create(A left, B right) { 
     return new Pair<A, B>(left, right); 
    } // end factory method 

    @Override 
    public final boolean equals(Object o) { 
     if (!(o instanceof Pair<?,?>)) 
      return false; 

     final Pair<?, ?> other = (Pair<?, ?>) o; 
     return equal(getLeft(), other.getLeft()) && equal(getRight(), other.getRight()); 
    } // end method 

    public static final boolean equal(Object o1, Object o2) { 
     if (o1 == null) { 
      return o2 == null; 
     } 
     return o1.equals(o2); 
    } // end method 

    @Override 
    public int hashCode() { 
     int hLeft = getLeft() == null ? 0 : getLeft().hashCode(); 
     int hRight = getRight() == null ? 0 : getRight().hashCode(); 

     return hLeft + (37 * hRight); 
    } // end method 

    @Override 
    public String toString() { 
     StringBuilder sb = new StringBuilder(); 
     sb.append('<'); 
     if(left == null) { 
      sb.append("null"); 
     } else { 
      sb.append(left.toString()); 
     } // end if 
     sb.append(','); 
     if(right == null) { 
      sb.append("null"); 
     } else { 
      sb.append(right.toString()); 
     } // end if 
     sb.append('>'); 
     return sb.toString(); 
    } // end method 
} // end class 
3

Seit generics are erased at compile time können Sie grundsätzlich nicht. Zur Laufzeit ist jeder Typparameter weg, und soweit es die JVM betrifft, sind sie in jeder Hinsicht genau gleich.

Die Möglichkeit, das zu umgehen, ist ein Class Feld zu speichern, das den Typ darstellt, und das Objekt mit diesem Typ im Konstruktor erstellen.

Eine Probe Konstruktor:

public class Tuple <E> { 

    public Tuple(Class<E> c) { 
     //store the class 
    } 

} 

Oder Sie eine Fabrik verwenden:

public static <E> Tuple <E> getTuple(Class<E> type) { 
    // Create and return the tuple, 
    // just store that type variable in it 
    // for future use in equals. 
} 
3

Leider können Sie diese bei der Kompilierung nicht tun; Die Information ist weg. Dies sind die Folgen von type erasure. Eine Alternative besteht darin, den Parameter als Class-Instanz zu speichern und später nachzuschlagen.

2

Die Vorschläge einen Verweis auf E ‚s-Typen mit einem Class Objekt scheinen ineffizient (das ist eine Menge von sinnlosen Verweisen auf einen Class Aufnahme-Speicher) und sinnlos für Ihr Problem zu behalten.

Es ist nicht allgemein wahr, dass Foo<Bar> und Foo<Baz> ungleich sein sollten. Sie brauchen also nicht E. Und in dem Code, den Sie geschrieben haben, brauchen Sie ihn nicht einmal zu kompilieren. Einfach auf Tuple<?> umwandeln, denn das ist wirklich alles, was Sie über Tuple an diesem Punkt wissen. Es kompiliert immer noch und alles.

Wenn Sie Tuple s mit Daten von zwei wild verschiedenen Typen übergeben werden, werden diese Elemente nicht equals, und Ihre Methode wird false zurückgeben, wie gewünscht. (Man hofft - hängt von diesen Typen equals vernünftig.)

+0

Ich sehe nicht, wie das Speichern eines Verweises auf den E-Typ Speicher belegt; Jedes "Class" -Objekt muss vom Classloader/JVM trotzdem beibehalten werden, sodass der einzige zusätzliche Speicher, den Sie verwenden, vier zusätzliche Byte für einen Zeiger (8 für 64-Bit-Systeme) ist. –

+0

Das stimmt, es sind die Referenzen, die den Raum einnehmen. 4/8 Bytes pro Objekt können oder müssen nicht trivial sein, abhängig davon, wie viele Instanzen sich dort befinden. Da es nicht hilft, das Problem zu lösen, scheint es, ich würde diese Verweise in jeder Größe als verschwenderisch bezeichnen. –

0

Off-Topic - erkennen Sie, dass entsprechend Ihrer Implementierung, Tuple (a0, a1) ist gleich Tuple (a1, a1)? Ich vermute, das ist nicht, was Sie wollen ...

Zum Thema, wie andere gesagt haben, macht die Löschung das unmöglich. Aber Sie sollten darüber nachdenken, warum Sie dies wollen - die Überprüfung der Gleichheit geschieht nur zur Laufzeit und Generics sind nur Kompilierzeit. Konzeptionell hat eine Variable generische Parameter, aber ein Objekt nicht. Wenn Sie also die Gleichheit von Objekten vergleichen, spielen die generischen Parameter keine Rolle. Sie können zur Laufzeit trotzdem keine geeigneten Maßnahmen auf ihnen basierend ergreifen.

Objektgleichheit und generischer Parameter Co-/Kontravarianz sind zwei orthogonale Probleme.

0

Ich stimme den obigen Kommentaren zu, warum muss die Klasse E gleich sein? und wie wollen Sie Unterklassen von E behandeln?

Wie dem auch sei, da hier ein Codebeispiel, das Ihnen helfen kann:

public class Example<T> { 

    T t; 

    public Example(T t) { 
    this.t = t; 
    } 

    public static void main(String[] args) { 
    final String s = "string"; 
    final Integer i = 1; 
    final Number n = 1; 

    final Example<String> exampleString = new Example<String>(s); 
    final Example<Integer> exampleInteger = new Example<Integer>(i); 
    final Example<Number> exampleNumber = new Example<Number>(n); 

    System.out.println("exampleString subclass " + exampleString.t.getClass()); 
    System.out.println("exmapleIntger subclass " + exampleInteger.t.getClass()); 
    System.out.println("exmapleNumber subclass " + exampleNumber.t.getClass()); 
    System.out.println("Integer equals Number = " + 
     exampleInteger.t.equals(exampleNumber.t)); 
    } 
} 

Sie können t.getClass() aufrufen Klasse Informationen über die Art von T zu erhalten (vorausgesetzt, es ist nicht null, von Natürlich.

Ich hoffe, das hilft.

4

Ich lief in dieses Problem selbst, und in meinem -particular- Fall, ich brauche nicht den Typ E.

Zum Beispiel wissen:

public class Example<E> { 
    E value; 

    public boolean equals(Object obj) { 
     if (this == obj) 
      return true; 
     if (obj == null) 
      return false; 
     if (getClass() != obj.getClass()) 
      return false; 
     Example<?> other = (Example<?>) obj; 
     if (value == null) { 
      if (other.value != null) 
       return false; 
     } else if (!value.equals(other.value)) 
      return false; 
     return true; 
    } 
} 

In dem obigen Code, Es gibt keine ungeprüfte Besetzung wegen der Verwendung von Example<?>. Der Typparameter-Platzhalter '?' rettet den Tag.

+1

Nie gedacht, so zu verwenden. Dies ist eine fantastische Antwort, da es während einer Gleichheitsüberprüfung in einer parametrisierten Klasse die meiste Zeit nicht interessiert, welche Typen die Generika sind. +1 – C0M37

+0

1. Die Frage war, wie man überprüft, dass die Typparameter gleich sind, und diese Antwort vermeidet nur eine "unsafe cast" Warnung, während die Typparameter nicht überprüft werden -> downvoted 2. es gibt wirklich Fälle, wo Sie wollen Instanzen für verschiedene Arten als ungleich anzusehen – herman