Joker
Joker

Reputation: 11150

Generics and Inheritance corner case

I have following code and it is not behaving as i am thinking. I have put the comments inline , that what is happening and what is expected.

class C<T> {
    void m(T arg) {
    }
}

interface I {
    void m(Class arg);
}

class D extends C<Class<String>> implements I {
}
// expected : error -- conflicting inherited methods
// actual: error -- abstract method not overridden

abstract class E extends C<Class<String>> implements I {
}
// expected : error -- conflicting inherited methods
// actual: no error, but no bridge method

Can some one please help me understand this behavior.

Upvotes: 3

Views: 158

Answers (4)

dabaicai
dabaicai

Reputation: 999

I expect following discussion in jdk8.

In the your code:

class D extends C<Class<String>> implements I {

}

should implement the interface I like this:

class D extends C<Class<String>> implements I {

    @Override
    public void m(Class arg) {

    }
}

Let's see why should implement the method in the interface I.According the JLS8.4.8.1:

An instance method mC declared in or inherited by class C, overrides from C another
method mI declared in an interface I, iff all of the following are true:
• I is a superinterface of C.
• mI is an abstract or default method.
• The signature of mC is a subsignature (§8.4.2) of the signature of mI.

In the above code,the mC is void m(Class<String> arg) in Class D,the mI is void m(Class arg)

And see the specification of subsignature:

And the `JLS8.4.2` explain the subsignature between two method:

The signature of a method m1 is a subsignature of the signature of a method m2 if
either:
• m2 has the same signature as m1, or
• the signature of m1 is the same as the erasure (§4.6) of the signature of m2.

According to above specification ,the void m(Class<String> arg is not subsignature of void m(Class arg),but the void m(Class arg) is subsignature of void m(Class<String> arg.So the Class D should implement the interface I explicitly.

See following code to explain it:

class C {
    public void m(Class arg) {
    }
}

interface I<T> {
    void m(T arg);
}

class D extends C implements I<Class<String>> {

}

it will compile ok.

The second,Why there is not method conflicting,Because of the JLS8.4.8 explain the inheritance of class:

A class C inherits from its direct superclass all concrete methods m (both static and instance) of the superclass for which all of the following are true:
• m is a member of the direct superclass of C.
• m is public, protected, or declared with package access in the same package as C.
• No method declared in C has a signature that is a subsignature (§8.4.2) of the signature of m.

According to above specification,the method void m(T arg) with Class<String> parameter of T be translated to void m(Class<String> arg).And the converted method will be erasured to void m(Class arg),this method will be existed in class D,so the Class D don't inherit the method void m(T arg) in class C.

Upvotes: 0

deepend0
deepend0

Reputation: 329

The problem can be reduced to this basic fact about Java generics that it is safe to assign an instantiated generic type to raw type but the reverse is not true. When overriding a method, unsafe conversions are not permitted. Therefore Class<String> cannot override raw Class type.

In this case:

    Class a = String.class; //type safe
    Class<String> b = a; //not type safe

Upvotes: 0

Harmlezz
Harmlezz

Reputation: 8078

I try to explain what's going on, step by step:

Class D extends C<Class<String>> which introduces the method with signature void m(Class<String> arg) at compile-time as of the definition of class C. But due to type erasure the information about the type parameter String will be eliminated after compilation. Hence the byte code will create a method void m(Class arg) defined by class C.

In addition, class D implements interface I which requires D to implement a method m(Class arg) which at compile-time is different from the method m(Class<String> arg) due to the type parameter. Hence you have to provide an implementation for former method or declare D abstract.

If you provide an implementation for the method m(Class arg) declared by interface I you in fact override the method defined by class C, because both will have the same signature after type erasure is applied.

Things change if you make class D extend C<Collection<String>>. In this case class C will introduce a method with signature void m(Collection<String> arg) at compile-time which changes to void m(Collection args) as byte code after type erasure happened. But now the method void m(Class arg) declared by interface I does have a different signature and hence will not override but overload method m defined by class C.

Here a simple Java example:

class C<T> {
    void m(T arg) {
        System.out.println("Method [m] of class [C] called");
    }
}

class D extends C<Class<String>> implements I {
    @Override
    public void m(Class arg) {
        System.out.println("Method [m] of class [D] called");
    }
}

class DD extends C<Collection<String>> implements I {

    @Override
    public void m(Class arg) {
        System.out.println("Method [m] of class [DD] called");
    }
}

and here a sequence of calls and the output:

public static void main(String[] args) {
    new D().m(D.class);
    new D().m((Class<String>) null);

    new DD().m(DD.class);
    new DD().m(new ArrayList<String>());
}

Output

Method [m] of class [D] called
Method [m] of class [D] called
Method [m] of class [DD] called
Method [m] of class [C] called.

Upvotes: 1

Jilles van Gurp
Jilles van Gurp

Reputation: 8324

D is not inheriting anything from I because it has no default method, just a method signature for which you are indeed not providing an implementation. As @RC points out the type of your m is different enough that it is not considered a valid implementation. To get the error about a conflict, you would actually need to add a conflicting implementation.

In E you are marking your class as abstract so it's OK to not have the method implemented as required in the interface. As soon as you do in any class that extends E, you will trigger the error about conflicting implementations because after type erasure the types would be identical. Basically you just moved the problem from D to whatever is going to extend E.

Upvotes: 0

Related Questions