返回类型中推断的通配符泛型

发布于 2024-08-01 17:39:45 字数 1767 浏览 6 评论 0 原文

Java 通常可以根据参数(甚至根据返回类型,与 C# 等不同)推断泛型。

举个例子:我有一个泛型类 Pair 它只存储一对值,可以按以下方式使用:

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

方法 of看起来就像这样:

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

非常好。 但是,这不再适用于以下需要通配符的用例:(

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

请注意显式转换以使 List.class 成为正确的类型。)

代码失败并出现以下错误(由 Eclipse 提供) ):

类型不匹配:无法从 TestClass.Pair,String> 转换为 TestClass.Pair,String>< /代码>

但是,显式调用构造函数仍然可以按预期工作:

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

有人可以解释这种行为吗? 是设计使然吗? 这是想要的吗? 我是否做错了什么,或者我是否偶然发现了设计中的缺陷/编译器中的错误?

疯狂猜测:“捕获#1-of?” 不知何故似乎暗示通配符是由编译器动态填充的,使类型成为 Class,从而导致转换失败(来自 Pair ,字符串>Pair,字符串>)。 这是正确的吗? 有办法解决这个问题吗?


为了完整起见,下面是 Pair 类的简化版本:

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);
    }
}

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);
    }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

将军与妓 2024-08-08 17:39:45

构造函数起作用的原因是您显式指定类型参数。 如果您这样做,静态方法也将起作用:

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

当然,您首先拥有静态方法的全部原因可能只是为了获得类型推断(这根本不适用于构造函数)。

这里的问题(正如您所建议的)是编译器正在执行 捕获转化。 我相信这是由于 [JLS §15.12.2.6]

  • 所选方法的结果类型确定如下:
    • 如果所调用的方法声明为返回类型 void,
      那么结果无效。
    • 否则,如果需要未经检查的转换
      方法适用,然后
      结果类型是擦除(§4.6)
      该方法声明的返回类型。
    • 否则,如果被调用的方法是通用的,那么对于 1in,让
      Fi 是以下形式的类型参数
      方法,让 Ai 为实际类型
      为该方法推断的参数
      调用,并令 R 为声明的
      该方法的返回类型是
      调用。 获取结果类型
      通过应用捕获转换
      (§5.1.10) 至 R[F1 := A1, ..., Fn :=
      安]。
    • 否则,通过应用捕获来获取结果类型
      转换(§5.1.10)到给定的类型
      在方法声明中。

如果您确实想要推断,一种可能的解决方法是执行以下操作:

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

变量 pair 将具有更宽的类型,这确实意味着需要更多地输入变量的类型名称,但至少您不再需要在方法调用中进行强制转换。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文