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.
source to share
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.
source to share
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
.
source to share
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
.
source to share
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
source to share