我想要解决一个多年来一直影响我的代码某些部分的疑点--它是用简单的强制转换修复的,但现在我想了解它背后的基本原理,并可能在官方规范中引用它.

让我来介绍这段代码:

import java.util.function.Supplier;

class BaseClass<T, R> {
  R getSomething() { return null; }
  static <U> U method1 (final U arg1) { return arg1; }
  static <U> U method2 (final U arg1, final Class<? extends U> arg2) { return arg1; }
  static <U> U method3 (final U arg1, final Supplier<? extends U> arg2) { return arg1; }
}

现在,让我有一个带有部分泛型绑定的子类(T仍未绑定):

class DerivedClass<T> extends BaseClass<T, String> {
  private String s;

  void test (final DerivedClass<T> arg) {
    final var m1 = method1(arg);
    s = m1.getSomething();

    final DerivedClass<T> m3 = method1(arg);
    s = m3.getSomething();

    final var m2 = method2(arg, DerivedClass.class);
    // 1. ERROR: requires String, found Object
    s = m2.getSomething();

    // 2. WARNING: unchecked assignment DerivedClass to DerivedClass<T>
    final DerivedClass<T> m4 = method2(arg, DerivedClass.class);
    s = m4.getSomething();

    final var m5 = method3(arg, () -> new DerivedClass<>());
    s = m5.getSomething();
    } 
}

对于method1,一切都很好,返回的对象带有绑定R-&gt;String.换句话说,arg1U类型被正确地传播到结果.

如果使用method2,绑定就会丢失(并"降级"到Object):如果使用m4的显式类型声明强制执行操作(当然,显式强制转换也可以),则会发出警告;如果使用var,则会出现错误.

method3,尽管有arg2人以与method2人相似的方式宣布,但一切又好起来了.

因此,罪魁祸首似乎是存在一个Class<>类型的参数.为什么?一般来说,为什么即使有Class<>个参数,编译器也不使用arg1作为完全匹配呢?

据我所知,这种情况是从Java 5开始发生的(当然,var的部分指的是Java 17). 谢谢.

推荐答案

method3的情况与method2的情况完全不同.也就是说,拨打method3时不涉及raw types.DerivedClass.class中的DerivedClass是原始类型.

如果您在供应商中返回原始DerivedClass,则它与would类似:

final var m5 = method3(arg, () -> new DerivedClass());

// this doesn't compile, for the same reason as in the method2 case
s = m5.getSomething();

method2 cannot的类型参数可推断为DerivedClass<T>,因为这样第二个参数(arg2)的类型将为Class<? extends DerivedClass<T>>,但您向其传递的是Class<DerivedClass>.

这就像给一个Class<DerivedClass>Class<? extends DerivedClass<T>>的变量赋值:

// this does not compile either
Class<? extends DerivedClass<T>> x = DerivedClass.class;

这不能编译,因为原始DerivedClass不是DerivedClass<T>的子类型.事实上,如果你看一下JLS的section 4.10.2,DerivedClassDerivedClass<T>的直接supertype.

给定具有类型参数f1,...,fn(n>;0)的泛型类或接口C,参数化类型C<T1,...,Tn>的直接超类型全部如下所示,其中每个Ti(1≤i≤n)是一个类型:

  • [.]

  • 原始型C.

因此,类型参数method2被推断为原始DerivedClass.这也适用于arg1,因为泛型DerivedClass<T>是原始DerivedClass的子类型.

这就是说,it is not possible to get a Class<DerivedClass<T>>,除非你不判断施法.

现在您可能想知道,如果method2返回原始DerivedClass,而您将其赋给DerivedClass<T>,则此赋值是如何工作的:

final DerivedClass<T> m4 = BaseClass.method2(arg, DerivedClass.class);

这之所以有效,是因为在赋值上下文中,像这样工作的只有specified个.重要的一句话是:

如果在应用了上面列出的转换之后,结果类型是原始类型(§4.8),则可以应用未经判断的转换(§5.1.9).

这里有一个隐式的未经判断的转换,将DerivedClass转换为DerivedClass<T>.这也是你收到警告的原因.

Java相关问答推荐

Spring Jpa findById会导致StackOverFlow错误,但其他查询没有问题

如何在Inspaut中获取当前路径的 * 模式 *?

那么比较似乎不是词典学的,尽管doctor 这么说

Annotation @ Memphier无法正常工作,并表示:class需要一个bean,但找到了2个bean:

在Java 8之后,HashMap的最坏情况下时间复杂度仍然是O(n)而不是O(log n)?

多个Java线程和TreeMap.put()的非预期行为

现场观看Android Studio中的变化

对运行在GraalVM-21上的JavaFX应用程序使用分代ZGC会警告不支持JVMCI,为什么?

如何使用Maven和Spring Boot将构建时初始化、跟踪类初始化正确传递到本机编译

在Java Swing Paint应用程序中捕获快速鼠标移动时遇到困难

测试期间未执行开放重写方法

OpenGL ES 3.0-纹理黑色

try 判断可选参数是否为空时出现空类型安全警告

Spring-Boot Kafka应用程序到GraalVM本机映像-找不到org.apache.kafka.streams.processor.internals.DefaultKafkaClientSupplier

在Java 15应用程序中运行Java脚本和Python代码

从Spring6中的JPMS模块读取类时出现问题

嘲笑黄瓜中的对象

RestTemplate Bean提供OkHttp3ClientHttpRequestFactory不支持Spring Boot 3中的请求正文缓冲

Maven-Dependency-Plugin 3.6.+开始查找在依赖关系:分析目标期间找到的新的使用的未声明依赖关系

使用@ExceptionHandler的GlobalExceptionHandler还是来自服务器的REST应答的ResponseEntity?