Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

"Illegal" implementation of generic method : Why don't I get any compilation error?

I've got an interface containing a method with this signature :

<P extends MergeProperty<T> & RestartApplicant> List<P> loadPropertiesFrom(T p1, T p2);

Basically, MergeProperty is a class which does NOT implement RestartApplicant, and RestartApplicant is a functional interface containing a method that does not really matter for the understanding of this problem.

Here is the catch. When I create a class implementing this interface, Java allows me to run without any compiling error the following code :

public class MyImplementation implements MyInterfacePreviouslyDescribed {

    @Override
    public List<MergeProperty<MathObject>> loadPropertiesFrom(MathObject p1, MathObject p2) {
        return Arrays.asList(
            // some random instances of MergeProperty that do not implement RestartApplicant
        );
    }
}

Obviously, I don't respect the restrictions of the implementation there. Given this signature, the list I return using Arrays.asList(...) does NOT need to contain elements that implement RestartApplicant. Remember, MergeProperty doesn't implement RestartApplicant. So this will most likely end up causing some casting error somewhere.

Still, I get a warning :

Type safety: The return type List<Main.MergeProperty<Main.MathObject>> for
loadPropertiesFrom(Main.MathObject, Main.MathObject) (...) needs unchecked
conversion to conform to List<Main.MergeProperty&Main.RestartApplicant> from the
type Main.Test<Main.MathObject>

My question is: why do I only get a warning? It seems to me that I should not be able to compile my code. Is there any particular reason for that?

Thanks in advance.

EDIT

After playing a little with my code, I figured that if I moved the "generics declaration" to the class level, which would lead to :

interface MyInterfacePreviouslyDescribed<T, P extends MergeProperty<T> & RestartApplicant>

instead of just

interface MyInterfacePreviouslyDescribed<T>

and obviously

List<P> loadPropertiesFrom(T p1, T p2);

instead of

<P extends MergeProperty<T> & RestartApplicant> List<P> loadPropertiesFrom(T p1, T p2);

then I actually get a compilation error if I try the same "illegal" implementation as before. It seems even more strange...

like image 451
Akami Avatar asked Oct 28 '19 22:10

Akami


1 Answers

You can even do:

    @Override
    public List<String> loadPropertiesFrom(MathObject p1, MathObject p2) {
        return Arrays.asList(
            // some random instances of MergeProperty that do not implement RestartApplicant
        );
    }

in the first case. This is because the overridden method is not generic and erasure for these is going to be List. Why is this allowed? I frankly don't know, may be something to do with backwards compatibility.

In your second example, it seems it should really be:

interface MyInterfacePreviouslyDescribed<T, P extends MergeProperty<T> & RestartApplicant> {

and in the implementation you are forcing the types to be correct. Actually, the second example is the intuitive one - as it does not compile; and that is expected. The first one, on the other hand relies on some compatibility rule for non-generic methods that allows to override to the same erasure.

like image 86
Eugene Avatar answered Nov 15 '22 00:11

Eugene