与 lambda 和函数接口一起使用时理解下限的问题

2022-09-03 06:34:05

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

Predicate<? super String> predicate = s -> s.startsWith("g");

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

此外,当我调整谓词以取上限时:

<? extends String>,

它不会编译。

我以为我理解了上限和下限的含义,但显然,我错过了一些东西。任何人都可以帮助解释为什么下限适用于此lambda?


答案 1

Lambda 参数类型是精确的,它不能是 或 。JLS 15.27.3 涵盖了这一点。Lambda 表达式的类型。它引入了地面目标类型概念(基本上是lambda类型)。除此之外,它指出:? super? extends

如果 是通配符参数化的函数接口类型,并且 lambda 表达式是隐式类型的,则地面目标类型是 T 的非通配符参数化§9.9)。T

强调我的。所以基本上当你写作时

Predicate<? super String> predicate = s -> s.startsWith("g");

您的 lambda 类型是 。这与:Predicate<String>

Predicate<? super String> predicate = (Predicate<String>)(s -> s.startsWith("g"));

甚至

Predicate<String> pred = (Predicate<String>)(s -> s.startsWith("g"));
Predicate<? super String> predicate = pred;

鉴于 lambdas 类型参数是具体的,在此之后,将应用正常的类型转换规则:是 , 或 .所以两者都应该编译。两者实际上都在javac 8u25,8u45,8u71以及ecj 3.11.1上为我工作。Predicate<String>Predicate<? super String>Predicate<? extends String>Predicate<? super String>Predicate<? extends String>


答案 2

我刚刚测试了它,作业本身编译。变化的是你是否真的可以调用.predicate.test()

让我们退后一步,使用占位符进行解释。对于类型参数,扩展和扩展 。GenericClass<T>FooBarBarBaz

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

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

考虑一个具有参数的方法。如果它是 a ,则传递给 a 是一个错误 - 不是 的子类型。TGenericClass<Foo>BarBarFoo

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

Super:当你声明一个 时,你是在说“我不知道它的泛型类型参数实际上是什么,但它是一个超类。现在考虑一下这对方法调用意味着什么。GenericClass<? super Bar>Bar

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

考虑一个具有参数的方法。如果它是一个,那么通过它是合法的。如果它是 一个 ,那么传递它 a 仍然是合法的,因为它是 的子类型。TGenericClass<Bar>BarGenericClass<Baz>BarBarBaz

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

总而言之:意味着您可以使用通用返回值,但不能使用参数。 表示您可以使用泛型参数,但不能返回值。 有一个泛型参数,因此您需要 .<? extends T><? super T>Predicate.test()super

需要考虑的另一件事:通配符声明的边界是关于对象的实际类型参数。它们对可以与该对象一起使用的类型的影响正好相反。上限通配符 () 是可以为其分配返回值的变量类型的下限。下限通配符 () 是可以作为参数传入的类型的上限。 不会编译,因为下限为 ,它将只接受 的子类。extendssuperpredicate.test(new Object())StringString


推荐