gpt4 book ai didi

java - 为什么泛型方法接受具有不满足泛型要求的接口(interface)的方法引用

转载 作者:行者123 更新时间:2023-12-03 18:58:08 25 4
gpt4 key购买 nike

为什么没有编译错误,那个addListener方法是用参数调用的,它是一个带有接口(interface)的方法引用 NotAnEvent ,与 Event 没有任何共同之处类(class)?

public class TestClass {
public static void main(String[] args) {
addListener(TestClass::listener1);
addListener(TestClass::listener2);
}

public static <T extends Event> void addListener(Consumer<T> listener) {

}

public static void listener1(ActualEvent event) {

}

public static void listener2(NotAnEvent event) {

}

public static class Event {
}

public static class ActualEvent extends Event {
}

public interface NotAnEvent {
}
}
上面的代码可以成功编译,至少使用 Intellij Idea 2020.3 Ultimate 和 JDK 8(以及 OpenJDK 11 也是如此),但可以预见的是它在启动时会崩溃:
Exception in thread "main" java.lang.BootstrapMethodError: call site initialization exception
at java.lang.invoke.CallSite.makeSite(CallSite.java:341)
at java.lang.invoke.MethodHandleNatives.linkCallSiteImpl(MethodHandleNatives.java:307)
at java.lang.invoke.MethodHandleNatives.linkCallSite(MethodHandleNatives.java:297)
at ru.timeconqueror.TestClass.main(TestClass.java:8)
Caused by: java.lang.invoke.LambdaConversionException: Type mismatch for lambda argument 0: class ru.timeconqueror.TestClass$Event is not convertible to interface ru.timeconqueror.TestClass$NotAnEvent
at java.lang.invoke.AbstractValidatingLambdaMetafactory.validateMetafactoryArgs(AbstractValidatingLambdaMetafactory.java:267)
at java.lang.invoke.LambdaMetafactory.metafactory(LambdaMetafactory.java:303)
at java.lang.invoke.CallSite.makeSite(CallSite.java:302)
... 3 more

最佳答案

这段代码被编译器接受是正确的,因为它对于泛型类型系统是合理的。而界面NotAnEvent不是 Event 的子类型, 可能有一个扩展 Event 的类型并实现NotAnEvent并且将这种类型的使用者传递给您的方法是有效的 addListener .
另见 Generic return type upper bound - interface vs. class - surprisingly valid code
我们甚至可以修复您的示例以在运行时工作:

import java.util.function.Consumer;

public class TestClass {
public static <X extends Event&NotAnEvent> void main(String[] args) {
addListener(TestClass::listener1);
TestClass.<X>addListener(TestClass::listener2);
}

public static <T extends Event> void addListener(Consumer<T> listener) {}
public static void listener1(ActualEvent event) {}
public static void listener2(NotAnEvent event) {}
public static class Event {}
public static class ActualEvent extends Event {}
public interface NotAnEvent {}
}
此固定版本使用类型变量为假设类型(仍然不是实际类)分配名称,因此我们可以在调用 addListener 时引用它.由于我们可以为类型约束提供明确的解决方案,因此类型推断在假设可以满足约束时是正确的。
一个版本工作而另一个版本在运行时失败的原因与代码生成方式的细微差异有关。当我们查看字节码时,我们会看到在这两种情况下,都会生成一个合成辅助方法,而不是传递 listener2 的引用。直接到 LambdaMetafactory .
问题代码:
  private static void lambda$main$0(TestClass$NotAnEvent);
Code:
0: aload_0
1: invokestatic #73 // Method listener2:(LTestClass$NotAnEvent;)V
4: return
工作版本:
  private static void lambda$main$0(java.lang.Object);
Code:
0: aload_0
1: checkcast #73 // class TestClass$NotAnEvent
4: invokestatic #75 // Method listener2:(LTestClass$NotAnEvent;)V
7: return
类型删除发生后,具有多个边界的类型通常会将一个边界视为声明类型,而将类型转换为另一个边界。对于一个正确的通用程序,这些转换永远不会失败。在您的情况下,方法 addListener无法调用 accept除了 null 之外的任何方法, 因为它不知道 T是。
问题代码的有趣之处在于,辅助方法声明的参数类型与 listener2 相同。方法,这使得整个辅助方法毫无意义。该方法必须采用另一个界限( Event )或只是 Object ,作为第二种情况,使其工作。这似乎是编译器中的一个错误。

关于java - 为什么泛型方法接受具有不满足泛型要求的接口(interface)的方法引用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65395326/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com