开发者

Overriding "equals" method: how to figure out the type of the parameter?

I'm trying to override equals method for a param开发者_运维百科eterized 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?


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


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


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.


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.


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.
}


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.)


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.


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.


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

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜