The fact is that your method signature becomes
<? extends Object> boolean equals(? extends Object a, ? extends Object b)
which does not give you any options. Even if you call
equals(new Date(), "hello world");
the compiler does not even need to break the sweat and determine the lowest common ancestor for your parameter types.
Edit
Interesting fact. I knew that what I wrote above was true, but it still looked a little strange. So I tested
<T> boolean equals(T a, T b) { return true; } <T,E> boolean equals(T a, E b) { return true; }
Which compiler screamed. The reason is that the compiler really doesn't matter and just rewrites both methods as
boolean equals(? extends Object a, ? extends Object b)
which after erasing styles becomes
boolean equals(Object a, Object b)
which is the same signature. Indeed, if I save your equals(T,T) method and I add another method with the equals(Object, Object) signature, the compiler continues to say that I have the same method that was declared elsewhere.
In short , your equals(T,T) method matches equals(Object, Object) due to type erasure, and therefore you cannot force the same parameter type, at least at compile time, unless you specifically implement equals methods for everyone.