I'm trying to write a helper function to compare two types in a typesafe way:
typesafeEquals("abc", new Integer(42)); // should not compile
My first straightforward attempt failed:
<T> boolean typesafeEquals(T x, T y) { // does not work!
return x.equals(y);
}
Well, the problem is that T
can be deduced to be an Object
. Now, I wonder if it is impossible to implement typesafeEquals
in the Java type system.
I know that there are tools like FindBugs find can warn about comparison of incompatible types. Anyway it would be interesting to either see a solution without external tools or an explanation why it is impossible.
Update: I think the answer is that it is impossible. I have no proof to support that claim, though. Only that it seems to be difficult to come up with a solution that works for all cases.
Some answers come close, but I believe the type system of Java does not support to solve the problem in all generality.
In Java, the == operator compares that two references are identical or not. Whereas the equals() method compares two objects. Objects are equal when they have the same state (usually comparing variables).
The incompatible types error most often occurs when manual or explicit conversion between types is required, but it can also happen by accident when using an incorrect API, usually involving the use of an incorrect reference type or the invocation of an incorrect method with an identical or similar name.
Asking an instance of Apple
whether it is equal to a particular instance of Orange
should not cause it any trouble. It should simply observe that because it does not consider itself equal to any objects which are not of type Apple
, and because it was given a reference to something that isn't of type Apple
, it does not consider itself equal to the passed in object.
Further, certain interfaces have contracts which require implementations them to consider themselves equal to any other implementations meeting certain criteria. It would thus be possible for two objects which are of unrelated types to both implement an interface which requires that the two objects report each other as equal. Even if the types of the references have no interface in common, unless at least one is sealed it would be possible for instances of types derived from them to share an interface which would compel a reciprocal equality relationship.
Consequently, while there are certainly cases where static analysis could reveal that the objects identified by references of two different classes cannot possibly consider themselves equal, the level of static analysis required would be far beyond anything the generic type system could possibly encapsulate [among other things, it would require examination of the code in the different types' equals
methods].
I would suggest that while it might in theory be nice if the compiler could identify cases where code tried to compare things that couldn't possibly be equal, the way that rules about equals
are defined (including the fact that it's permissible--and in some cases required--for instances of unrelated classes to compare themselves equal to each other) means that in general what you seek isn't possible. If it's possible to ask an instance of Apple
whether it's equal to any particular Fruit
, then it must be possible to ask it whether it's equal to any particular instance of any type derived from Fruit
. The Apple
shouldn't care if the question is silly; it should simply answer it.
Adding a third parameter - the Class<T>
type to your method - will help with this sort of issue. As you say, you can get an Object
from T
; unless you add bounds to it, it won't ever be able to identify anything other than Object
.
While adding the class type will force you to put it in every time you want to use the method, it enforces the compiler and implied contract that you're comparing them equal as if they are a certain class.
<T> boolean typesafeEquals(T x, T y, Class<T> clazz) {
return x.equals(y);
}
Do note that this kind of checking is a bit of an overreaction from the perspective of the developer. It should be the case that two objects which are not equivalent should simply return false
.
Given the requirement of it being a drop-in replacement for Object.equals
, that is simply impossible given the well-established loose type safety around the method as it is. While generics would be an ideal way to solve this, generics existed in Java 5, whereas equals
was there from the beginning.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With