views:

661

answers:

8

I'm trying to override equals() for a parametrized class. How can I make sure that this parameter is the same?

/* (non-Javadoc)
 * @see java.lang.Object#equals(java.lang.Object)
 * Because this is commutative, `Tuple(a0, a1)` is the same as `Tuple(a1, a0)`
 */
@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;
}

How can I make sure that <E> of the other object is the same as this?

A: 

Use reflection. http://tutorials.jenkov.com/java-reflection/generics.html

Maas
Good thing too, because I would have downvoted you. http://meta.stackoverflow.com/search?q=lmgtfy
Robert Harvey
+1  A: 

Because of erasure you can't. About the best you could do is store in the tuple class the type you plan for the Tuple to hold in a "java.lang.Class" field member. Then you could compare those fields to make sure the tuple class is holding the same types.

Also see this thread: http://stackoverflow.com/questions/156275/what-is-the-equivalent-of-the-c-pairl-r-in-java

It would help if you post more about your class. I'm thinking the unchecked cast and your number of fields you equate means it should be Tuple<E,F> no?

EDIT: here is a useful Pair class I use regularly (you can adapt your Tuple class if needed). Note, similiar to suggestions by others this class just lets the contained members decide the question of equality. Your use case is what should determine whether equality is really based on the type of the contained members.

/**
 * 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
harschware
A: 

Since generics are erased at compile time, you basically can't. At runtime, any generic parameter is gone, and as far as the jvm is concerned, they are exactly the same in all respects.

The way to work around that is to store a Class field that represents the type, and create the object with that type in the constructor.

A sample constructor:

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

Or you could use a factory:

 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.
 }
Yishai
+1  A: 

Sadly, you can't do this at compile time; the information is gone. Such are the consequences of type erasure. An alternative is to store the parameter as a Class instance, and then look it up later.

John Feminella
+5  A: 

You can do it by retaining a reference to Class<E> type as @Yishai suggested. However, in my opinion, equality tests should be about the values the objects represent rather than the concrete types the values get expressed.

A classic example of this is the Collections API for example. new ArrayList<String>().equals(new LinkedList<Object>()) returns true. While these have completely different types, they represent the same value, namely "an empty collection".

Personally, should two Tuples that represent the same data (e.g. ("a", "b")) be not equal, because one is of type Tuple<String> while the other is Tuple<Object>?

notnoop
Exactly ... storing class token in this case is pointless.
Peter Štibraný
+1  A: 

The suggestions to retain a reference to E's type with a Class object seem inefficient (that's a lot of pointless references to a Class taking up memory) and pointless for your problem.

As notnoop points out, it's not generally true that Foo<Bar> and Foo<Baz> should be unequal. So you don't need E. And, in the code you wrote, you don't even need it to compile. Simply cast to Tuple<?>, since that is truly all you know about Tuple at that point. It still compiles and all.

If you are passed Tuples with data of two wildly different types, those elements will not be equals() and your method will return false, as desired. (One hopes -- depends on those types implementing equals() sanely.)

Sean Owen
A: 

Off-topic - do you realise that according to your implementation, Tuple(a0, a1) is equal to Tuple(a1, a1)? I suspect that's not what you want...

On-topic, as others have said, erasure makes this impossible. But you should reconsider why you want this - equality checking only happens at runtime, and generics are only compile-time. Conceptually, a variable has generic parameters, but an object does not. Thus when you're comparing the equality of objects, the generic parameters do not matter at all; you can't take any appropriate action based on them at runtime anyway.

Object equality, and generic parameter co-/contra-variance, are two orthogonal concerns.

Andrzej Doyle
A: 

I agree with the comments above, why does the class E need to be equal? and how do you want to treat subclasses of E?

Anyway, given that here is a code sample that may help you:

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));
  }
}

You can call t.getClass() to get class information about the type of T (assuming it is not null, of course.)

I hope this helps.

Mr Jacques