与lambda和Functional Interface一起使用时理解下限的问题

pip*_*970 15 java generics lambda predicate java-8

在研究Java8 Streams时,我遇到了以下代码片段:

Predicate<? super String> predicate = s -> s.startsWith("g");
Run Code Online (Sandbox Code Playgroud)

由于泛型参数是下限,我认为这不会编译.我看到它的方式,如果一个Object是String的超类型,那么传入一个Object类型应该会破坏它,因为Object没有startsWith()函数.但是,我很惊讶地看到它没有任何问题.

更进一步,当我调整谓词采取上限:

<? extends String>,
Run Code Online (Sandbox Code Playgroud)

它不会编译.

我以为我理解了上限和下限的含义,但显然,我错过了一些东西.任何人都可以帮助解释为什么下限与这个lambda一起工作?

Tag*_*eev 9

Lambda参数类型是精确的,它不能是? super? extends.这由JLS 15.27.3涵盖.Lambda表达式的类型.它介绍了地面目标类型概念(基本上是lambda类型).除其他外,它表明:

如果T是通配符参数化的功能接口类型并且隐式键入lambda表达式,则地面目标类型是T的非通配符参数化(第9.9节).

强调我的.所以基本上你写的时候

Predicate<? super String> predicate = s -> s.startsWith("g");
Run Code Online (Sandbox Code Playgroud)

你的lambda类型是Predicate<String>.它与以下相同:

Predicate<? super String> predicate = (Predicate<String>)(s -> s.startsWith("g"));
Run Code Online (Sandbox Code Playgroud)

甚至

Predicate<String> pred = (Predicate<String>)(s -> s.startsWith("g"));
Predicate<? super String> predicate = pred;
Run Code Online (Sandbox Code Playgroud)

鉴于lambdas类型参数是具体的,在适用普通类型转换规则之后:Predicate<String>是a Predicate<? super String>,或Predicate<? extends String>.因此,无论Predicate<? super String>Predicate<? extends String>应编译.这两个实际上都适用于javac 8u25,8u45,8u71以及ecj 3.11.1.


Dou*_*las 8

我只是测试了它,赋值本身编译.是否可以实际呼叫有什么变化predicate.test().

让我们退一步,使用占位符GenericClass<T>进行解释.对于类型参数,Foo扩展BarBar扩展Baz.

扩展:当你声明一个时GenericClass<? extends Bar>,你会说"我不知道它的泛型类型参数究竟是什么,但它是它的子类Bar." 实际实例将始终具有非通配符类型参数,但在此部分代码中您不知道它的值是什么.现在考虑这对方法调用意味着什么.

你知道你实际得到的是a GenericClass<Foo>或a GenericClass<Bar>.考虑一个返回的方法T.在前一种情况下,它的返回类型是Foo.在后者,Bar.无论哪种方式,它都是一个子类型,Bar并且可以安全地分配给Bar变量.

考虑一个具有T参数的方法.如果它是a GenericClass<Foo>,那么传递它Bar是一个错误 - Bar不是一个子类型Foo.

因此,使用上限可以使用泛型返回值,但不能使用泛型方法参数.

超级:当你声明一个时GenericClass<? super Bar>,你说的是"我不知道它的泛型类型参数究竟是什么,但它是一个超类Bar." 现在考虑这对方法调用意味着什么.

你知道你实际得到的是a GenericClass<Bar>或a GenericClass<Baz>.考虑一个返回的方法T.在前一种情况下,它返回Bar.在后者,Baz.如果它返回a Baz,则将该值Bar赋给变量是一个错误.你不知道它是哪一个,所以你不能在这里安全地做任何事情.

考虑一个具有T参数的方法.如果它是a GenericClass<Bar>,那么传递它Bar是合法的.如果它是a GenericClass<Baz>,那么传递它Bar仍然是合法的,因为它Bar是一个子类型Baz.

因此,使用下限可以使​​用泛型方法参数,但不能使用泛型返回值.

总结:<? extends T>意味着您可以使用泛型返回值而不是参数.<? super T>意味着您可以使用通用参数但不能返回值.Predicate.test()有一个通用参数,所以你需要super.

另一件需要考虑的事情:通配符所声明的边界是关于对象的实际类型参数.它们对可以该对象一起使用的类型的后果是相反的.上限通配符(extends)是可以为其指定返回值的变量类型的下限.下限通配符(super)是您可以作为参数传入的类型的上限.predicate.test(new Object())不会编译,因为在下限中String,它只接受子类String.