Discrimination and inheritance

I have the following code and it does not behave the way I think it does. I have put comments on the line 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 someone please help me understand this behavior.

+3


source to share


4 answers


I am trying to explain what is going on, step by step:

The D

extends class C<Class<String>>

, which introduces a method with a signature void m(Class<String> arg)

at compile time by class definition C

. But due to type erasure, the type parameter information String

will be cleared after compilation. Hence the bytecode will create the method void m(Class arg)

defined by the class C

.

In addition, a class D

implements an interface I

that requires D

a method to be implemented m(Class arg)

that differs at compile time from a method m(Class<String> arg)

because of the type parameter. Hence, you need to provide an implementation for the legacy method or declare a tag D

.

If you provide an implementation of a method m(Class arg)

declared by an interface I

, you are effectively overriding the method defined by the class C

, because both will have the same signature after the type erasure is applied.

Everything changes if you create the D

extend class C<Collection<String>>

. In this case, the class C

will inject a method with a signature void m(Collection<String> arg)

at compile time, which changes to void m(Collection args)

as bytecode after type erasure. But now the method void m(Class arg)

declared by the interface I

has a different signature and therefore won't override but the overload method m

defined by the class C

.

Here's 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's the call sequence and exit:

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.

      

+1


source


D

doesn't inherit anything from I

because it doesn't have a default method, it's just a method signature for which you don't really implement an implementation. As @RC points out, your type m

is so different that it is not considered a valid implementation. To get the conflict error you really need to add the conflicting implementation.



In E

you mark your class as abstract so that it does not execute the method as required in the interface. Once you execute in any class that extends E

, you will raise a conflicting implementation error because after type erasure the types will be identical. Basically you just moved the problem off D

to anything that will expand E

.

0


source


I look forward to the next discussion at jdk8

.

In 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 the method should be implemented in the interface. I

Search 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 mC

void m(Class<String> arg)

in the class D

, mI

there isvoid m(Class arg)

And look at the spec 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 the above specification, void m(Class<String> arg

it is not a sub-signal void m(Class arg)

, but void m(Class arg)

a sub-signal void m(Class<String> arg

. Therefore, the class D

must explicitly implement the interface I

.

To explain this, see the following code:

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

Second, why there is no method conflicting due to JLS8.4.8

explain class inheritance:

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 the above specification, a method void m(T arg)

with a parameter Class<String>

T

will be translated to void m(Class<String> arg)

. And the converted method will be removed before void m(Class arg)

this method will exist in the class D

, so the class D

does not inherit the method void m(T arg)

in the class C

.

0


source


The problem can be boiled down to this basic fact about Java generics that it is safe to assign an instance of a generated instance of type raw, but the reverse is not true. When overriding a method, invalid conversions are not allowed. Therefore, Class<String>

it cannot override the type of the original class.

In this case:

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

      

0


source







All Articles