Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to detect comparison of incompatible types with the Java type system?

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.

like image 409
Philipp Claßen Avatar asked Oct 24 '14 15:10

Philipp Claßen


People also ask

How do you compare data types in Java?

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

What type of error is incompatible types in Java?

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.


2 Answers

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.

like image 129
supercat Avatar answered Sep 25 '22 01:09

supercat


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.

like image 40
Makoto Avatar answered Sep 22 '22 01:09

Makoto