20

I'm trying to override equals method for a parameterized class.

@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?

nbro
  • 15,395
  • 32
  • 113
  • 196
Nick Heiner
  • 119,074
  • 188
  • 476
  • 699

9 Answers9

16

You can do it by retaining a reference to Class<E> type. 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>?

nbro
  • 15,395
  • 32
  • 113
  • 196
notnoop
  • 58,763
  • 21
  • 123
  • 144
  • 5
    How about Id and Id
    , even if the actual value inside the object (the database id) is the same, I don't think they should be considered equal since they represent IDs for different DB columns.
    – herman Aug 11 '12 at 00:56
  • 1
    +1 for a clear explanation, but I would also include in here what to write in the equals method when the parameter type is not important in order to remove the compiler warnings. For others reading this, There is another answer on this page that mentions the > wildcard and it's use here. – C0M37 Jan 30 '13 at 05:10
5

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: What is the equivalent of the C++ Pair<L,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
Community
  • 1
  • 1
harschware
  • 13,006
  • 17
  • 55
  • 87
5

I just ran into this problem myself, and in my -particular- case, I didn't need to know the type E.

For example:

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 the above code, there is no unchecked cast because of using Example<?>. The type parameter wildcard '?' saves the day.

jfritz42
  • 5,913
  • 5
  • 50
  • 66
  • 1
    Never thought to use > like that. This is a fantastic answer, since most of the time, during an equals check in a Parameterized class, one really shouldn't care what types the generics are. +1 – C0M37 Jan 30 '13 at 05:15
  • 1. the question was how to check that the type parameters are equal, and this answer only avoids an "unsafe cast" warning while the type parameters aren't checked --> downvoted 2. there really are cases where you want instances for different types to be regarded as unequal – herman Jan 30 '13 at 11:33
3

Since generics are erased at compile time, you basically can't. At runtime, any type 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> c) {
        //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.
}
nbro
  • 15,395
  • 32
  • 113
  • 196
Yishai
  • 90,445
  • 31
  • 189
  • 263
3

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
  • 303,634
  • 46
  • 339
  • 357
2

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.

It's not generally true that Foo<Bar> andFoo<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.)

nbro
  • 15,395
  • 32
  • 113
  • 196
Sean Owen
  • 66,182
  • 23
  • 141
  • 173
  • I don't see how retaining a reference to E's type take up memory; each `Class` object has to be retained by the classloader/JVM anyway, so the only extra memory you'd use is about an extra four bytes for a pointer (8 for 64-bit systems). – The Alchemist Mar 15 '11 at 12:50
  • That's right, it's the references that take up the space. 4/8 bytes per object may or may not be trivial depending on how many instances are out there. As it doesn't help solve the problem at hand it seems, I'd call those references wasteful at any size. – Sean Owen Mar 15 '11 at 20:27
0

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

Bobby
  • 11,419
  • 5
  • 44
  • 69
Maas
  • 330
  • 1
  • 7
0

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
  • 102,507
  • 33
  • 189
  • 228
0

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.

MrJacqes
  • 373
  • 1
  • 4