Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/338.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java 为什么泛型方法接受不带';t满足一般';s要求_Java_Generics_Method Reference - Fatal编程技术网

Java 为什么泛型方法接受不带';t满足一般';s要求

Java 为什么泛型方法接受不带';t满足一般';s要求,java,generics,method-reference,Java,Generics,Method Reference,为什么没有编译错误,调用了带有参数的addListener方法,这是一个带有接口notavent的方法引用,它与事件类没有任何共同之处 public class TestClass { public static void main(String[] args) { addListener(TestClass::listener1); addListener(TestClass::listener2); } public static &

为什么没有编译错误,调用了带有参数的
addListener
方法,这是一个带有接口
notavent
的方法引用,它与
事件
类没有任何共同之处

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 {
    }
}

这在某种程度上是有道理的,尽管人们可以肯定地说这是不可取的

问题在于PECS规则(生产者延伸,消费者延伸)。想象一下,我们将其翻转过来:

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

    public static <T extends Event> void addListener(Supplier<T> listener) {}
    public static NotAnEvent listener2() {return null;}

    public static class Event {}
    public static class ActualEvent extends Event {}
    public interface NotAnEvent {}
}
但是,这里两个addListener调用都是编译器错误。我们可以做到这一点,但如何做到这一点有点奇怪:

public class Weird extends Event implements NotAnEvent {}

...

addListener(TestClass::listener2, new Weird());
现在它可以编译并工作了——最重要的是,没有运行时异常发生,因为您可以将
Weird
的一个实例传递给NotAnEvent的消费者,并且工作正常

这部分解释了一些行为:
notavent
必须是一个接口:如果
listener2
的参数类型是
Object
或某个接口,它会编译,但如果它是某个类(final或note),它不会编译。这大概是因为编译器在想:好吧,这可能会在以后解决,并且不会发生堆损坏,因为如果不传入T,就无法安全地获取T,然后编译器错误就会接踵而至,除非您有类似上面的
奇怪的

这就引出了一个明显的后续问题:

您确实会得到一个运行时异常,它似乎是基于键入问题。你在你的问题中说它“可预测”崩溃,但我认为这不是特别可预测的。您的
addListener
代码没有任何作用,通常使用泛型擦除就可以了。某些链接过程正在失败


因此,尽管如此,在某些规范中还是有一个bug,大概值得在bugs.openjdk上归档。

编译器接受这段代码是正确的,因为它与泛型类型系统有关。虽然接口
NotAnEvent
不是
Event
的子类型,但可能存在扩展
Event
并实现
NotAnEvent
的类型,将该类型的使用者传递给方法
addListener
是有效的

另见

我们甚至可以在运行时修复您的示例:

import java.util.function.Consumer;
公共类TestClass{
公共静态void main(字符串[]args){
addListener(TestClass::listener1);
addListener(TestClass::listener2);
}
公共静态void addListener(使用者侦听器){}
公共静态无效listener1(ActualEvent事件){}
公共静态无效侦听器2(NotAnvent事件){}
公共静态类事件{}
公共静态类ActualEvent扩展事件{}
公共接口NotAnEvent{}
}
此固定版本使用类型变量为假设的类型(仍然不是实际的类)指定名称,因此我们可以在调用
addListener
时引用它。因为我们可以为类型约束提供显式的解决方案,所以类型推断在假设约束可以满足时是正确的

一个版本工作而另一个在运行时失败的原因与代码生成方式的细微差异有关。当我们查看字节码时,我们将看到在这两种情况下,都会生成一个合成助手方法,而不是将
listener2
的引用直接传递给
LambdaMetafactory

问题代码:
private static void lambda$main$0(TestClass$NotAnEvent);
代码:
0:aload_0
1:invokestatic#73//方法监听器2:(LTestClass$NotAnEvent;)V
4:返回
工作版本:
private static void lambda$main$0(java.lang.Object);
代码:
0:aload_0
1:checkcast#73//class TestClass$notavent
4:invokestatic#75//方法监听器2:(LTestClass$NotAnEvent;)V
7:返回
在类型擦除发生后,具有多个边界的类型通常会将一个边界视为声明的类型,并将一个类型强制转换为另一个边界。对于正确的泛型程序,这些强制转换永远不会失败。在您的情况下,方法
addListener
不能调用
accept
方法,只能调用
null
,因为它不知道
t
是什么


问题代码的有趣之处在于,helper方法声明的参数类型与
listener2
方法相同,这使得整个helper方法毫无意义。作为第二种情况,该方法必须使用另一个绑定(
事件
)或仅使用
对象
,以使其工作。这似乎是编译器中的一个错误。

这就是您要查找的内容。如果您查看字节码,那么您应该会看到两者之间的差异calls@BryanAcu尼安乌涅斯:不,不是;您可以在JDK14的javac上重现这个问题中所述的问题;您链接到的答案是“在JDK9中修复”。这可能是javac中的一个bug,也可能是java语言规范中的一个bug,因为它遵循规范,但规范允许这样做是错误的。有趣的是,甚至ecj(eclipse的编译器,完全独立)也允许这样做。通常,对于这样的规范/错误,ecj做对了,javac做错了。我还没有在JLS中找到正确的位置,但这表明它符合规范(因此,规范需要修改)。
public class TestClass {
    public static void main(String[] args) {
        addListener(TestClass::listener2, new Event());
        addListener(TestClass::listener2, new NotAnEvent() {});
    }

    public static <T extends Event> void addListener(Consumer<T> listener, T elem) {}
    public static void listener2(Consumer<NotAnEvent> c) {}

    public static class Event {}
    public static class ActualEvent extends Event {}
    public interface NotAnEvent {}
}
public class Weird extends Event implements NotAnEvent {}

...

addListener(TestClass::listener2, new Weird());