Some java generics questions

98 Views Asked by At

Q1. In the below code i get the error "Name clash: The method equals(T) of type Node has the same erasure as equals(Object) of type Object but does not override it"

public class Node<T> {
    public boolean equals(T b) {
        return false;   
    }
    //public boolean equals(Object b) {
    //  return false;   
    //} 
}

I tried to read explanations online but still have a simple question. If after type erasure, the equivalent of public boolean equals(T b) is public boolean equals(Object b) which is perfectly valid way to override, then why the fuss?

Q2. Lets say i add the @Override annotation to public boolean equals. The error thrown this time is "The method equals(T) of type Node must override a superclass method". Again, the same argument above, since after type erasure public boolean equals(T b) is public boolean equals(Object b) why does the compiler complain?

One possible explanation i can think of is "Before type erasure the compiler tries to find a method with the signature public boolean equals(T b) in the base class, doesn't find it, and complains".

Q3. In the below code we get the error "Cannot perform instanceof check against type parameter T".

if ( b instanceof T ) {         
}

Again, i get the explaination that T would be stripped out and become Object and we have no info of T at runtime. Lets say hypothetically is the compiler allowed it, wouldn’t this be a valid check i.e. if ( b instanceof Object )?

So, is the compiler complaining mainly because this is misleading (as this will always be true!) and wants you to change it? Or is there some other reason's too why this isnt a good idea.

2

There are 2 best solutions below

0
On BEST ANSWER

In general, erasure is not just a simple textual replacement. These different kinds of types have specific semantic meaning. Of course a language could do anything, but would the vast consequences be actually good? In general, things are disallowed because the consequences are counterproductive.

Q1. In the below code i get the error "Name clash: The method equals(T) of type Node has the same erasure as equals(Object) of type Object but does not override it"

I think the error message is just confusing you, although it is actually telling you something highly-specific. It appears to be trying to cite JLS at you.

What it means is that you've declared a method with an effectively identical signature to one in a supertype such that it would be an override, but that the override is not allowed. The message is telling you "but does not override it", because this is a fact that is mandated by the spec.

The 2nd bullet-point list in 8.4.8.3 is the stipulation that prevents your code from compiling (paraphrased):

It is a compile-time error if a type T has a method m1 and there exists a method m2 declared in a supertype of T such that:

  • m1 and m2 have the same name.

  • The signature of m1 is not a subsignature (§8.4.2) of the signature of m2.

  • The signature of m1 has the same erasure as the signature of m2.

Note that the complex rules in place here do allow the following override:

class A<T> {
    void m(T t) {}
}
class B<T> extends A<T> {
    void m(Object o) {}
}

(B.m is a subsignature of A.m, but not the other way around.)

Herein lies one problem. Say you were allowed to override the other way:

abstract class A {
    abstract void   put(Object o);
    abstract Object get();
}
class B<T> extends A {
    T t;
    void put(T t) { this.t = t; }
    T    get()    { return t;   }
}

This is not really good because if we do A a = new B<String>();, we can put anything in the B<String>. It's just weird and bad. Generics are about stronger typing.

There are also more problems, for example:

class Ambiguous implements Comparable<Number>, Comparable<String> {
    public int compareTo(Number n) { return 0; }
    public int compareTo(String s) { return 0; }
}

Can't be done because their erasures are the same.

The whole enigma must be disallowed.

Q2. Lets say i add the @Override annotation to public boolean equals. ...

@Override does not mean you are overriding, it just means you are asserting that you think it should be an override. You just have two errors now, because it is an error to annotate a method that is not an override with @Override.

Q3. In the below code we get the error "Cannot perform instanceof check against type parameter T".

T and Object are different kinds of types. T is a type variable and Object refers to a class declaration. Specifically the rule here is that the right-hand side must be reifiable.

If it did not, if b instanceof T were simply equivalent to b instanceof |erasure of T|, would that be useful? No, it would not. In fact, it would be almost completely useless. Since we always know the bound of T we can already always make that check.

So, is the compiler complaining mainly because this is misleading ... ?

It would be extremely misleading. But let's be clear. The compiler is complaining because the language specification mandates it so. When the specification mandates something, we do not always know what its reasoning is. Just that it is so.

1
On

Some definitions:

  • "overriding" is when a subclass implements a method with the same signature as a method in a superclass
  • "overloading" is when a class implements two methods with the same name but different parameter types

With that in mind...

Q1: it's not a case of overriding, it's a case of overloading, but erasure results in a signature collision.

Q2: Adding the Override doesn't change the fact that it's a case of overloading.

Q3: "erasure" means it's gone - there is no "T" at runtime to compare anything with.