Inferred wildcard generics in return type

本小妞迷上赌 提交于 2019-11-27 02:39:29

问题


Java can often infer generics based on the arguments (and even on the return type, in contrast to e.g. C#).

Case in point: I've got a generic class Pair<T1, T2> which just stores a pair of values and can be used in the following way:

Pair<String, String> pair = Pair.of("Hello", "World");

The method of looks just like this:

public static <T1, T2> Pair<T1, T2> of(T1 first, T2 second) {
    return new Pair<T1, T2>(first, second);
}

Very nice. However, this no longer works for the following use-case, which requires wildcards:

Pair<Class<?>, String> pair = Pair.of((Class<?>) List.class, "hello");

(Notice the explicit cast to make List.class the correct type.)

The code fails with the following error (provided by Eclipse):

Type mismatch: cannot convert from TestClass.Pair<Class<capture#1-of ?>,String> to TestClass.Pair<Class<?>,String>

However, explicitly calling the constructor still works as expected:

Pair<Class<?>, String> pair =
    new Pair<Class<?>, String>((Class<?>) List.class, "hello");

Can someone explain this behaviour? Is it by design? Is it wanted? Am I doing something wrong or did I stumble upon a flaw in the design / bug in the compiler?

Wild guess: the “capture#1-of ?” somehow seems to imply that the wildcard is filled in by the compiler on the fly, making the type a Class<List>, and thus failing the conversion (from Pair<Class<?>, String> to Pair<Class<List>, String>). Is this right? Is there a way to work around this?


For completeness’ sake, here is a simplified version of the Pair class:

public final class Pair<T1, T2> {
    public final T1 first;
    public final T2 second;

    public Pair(T1 first, T2 second) {
        this.first = first;
        this.second = second;
    }

    public static <T1, T2> Pair<T1, T2> of(T1 first, T2 second) {
        return new Pair<T1, T2>(first, second);
    }
}

回答1:


The reason the constructor works is that you're explicitly specifying the type parameters. The static method also will work if you do that:

Pair<Class<?>, String> pair = Pair.<Class<?>, String>of(List.class, "hello");

Of course, the whole reason you have a static method in the first place is probably just to get the type inference (which doesn't work with constructors at all).

The problem here (as you suggested) is that the compiler is performing capture conversion. I believe this is as a result of [§15.12.2.6 of the JLS]:

  • The result type of the chosen method is determined as follows:
    • If the method being invoked is declared with a return type of void, then the result is void.
    • Otherwise, if unchecked conversion was necessary for the method to be applicable then the result type is the erasure (§4.6) of the method's declared return type.
    • Otherwise, if the method being invoked is generic, then for 1in, let Fi be the formal type parameters of the method, let Ai be the actual type arguments inferred for the method invocation, and let R be the declared return type of the method being invoked. The result type is obtained by applying capture conversion (§5.1.10) to R[F1 := A1, ..., Fn := An].
    • Otherwise, the result type is obtained by applying capture conversion (§5.1.10) to the type given in the method declaration.

If you really want the inference, one possible workaround is to do something like this:

Pair<? extends Class<?>, String> pair = Pair.of(List.class, "hello");

The variable pair will have a wider type, and it does mean a bit more typing in the variable's type name, but at least you don't need to cast in the method call anymore.



来源:https://stackoverflow.com/questions/1294227/inferred-wildcard-generics-in-return-type

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!