![](/img/trans.png)
[英]use java.util.function.Function to implement Factory Design Pattern
[英]Question about Java generics and design of java.util.function.Function
示例: Student extends Person
Person person = new Person();
Student student = new Student();
List<? super Student> list = new ArrayList<>();
list.add(student); // success
list.add(person); // compile error
List<? extends Person> list2 = new ArrayList<>();
list2.add(person); // compile error
list2.add(student);// compile error
我已阅读以下问题的答案“ capture#1-of ? extends Object is not 适用”
您正在使用通用通配符。 您不能执行添加操作,因为类类型不确定。 您不能添加/放置任何内容(空值除外)-- Aniket Thakur
官方文档:通配符永远不会用作泛型方法调用、泛型类实例创建或超类型的类型参数
但是为什么list.add(student)
编译成功呢?
java.util.function.Function
设计public interface Function<T, R>{
//...
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
}
为什么before
被设计为Function<? super V, ? extends T>
Function<? super V, ? extends T>
Function<? super V, ? extends T>
而不是Function<V,T>
当返回的类型是Function<V,R>
和输入的类型是V
? (它还可以通过编译灵活使用)
要理解这些问题,您必须了解泛型如何与subtyping
(在 Java 中使用extends
关键字明确表示)一起工作。 Andreas 提到了PECS规则,这是它们在 Java 中的表示。
首先,我想指出,上面的代码可以通过简单的转换来纠正
ArrayList<? super Student> list = new ArrayList<>();
list.add(new Student());
ArrayList<Person> a = (ArrayList<Person>) list; // a covariance
a.add(new Person());
并且编译和运行良好(而不是引发任何异常)
原因很简单,当我们有一个consumer
(它接受一些对象并消费它们,例如add
方法)时,我们期望它接受类型no more than
(超类)我们指定的类型T
,因为过程消费可能需要它想要的类型的任何成员(变量、方法等),我们希望确保类型T
满足消费者需要的所有成员。
与此相反,一个producer
,这对于我们产生对象(如get
方法),具有供应类型的对象no less than
指定类型T
使我们可以访问任何构件T
具有所产生的对象上。
这两个与称为covariance
和contravariance
子类型化形式密切相关
至于第二个问题,你也可以参考Consumer<T>
的实现(稍微简单一些):
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
我们需要这个的原因是? super T
? super T
是:当我们使用andThen
方法组合两个Consumer
,假设前一个Consumer
接受一个类型为T
的对象,我们期望后者接受一个类型no more than T
的对象,因此它不会尝试访问T
没有的任何成员。
因此,而不是简单地将Consumer<T> after
写Consumer<T> after
但Consumer<? super T> after
Consumer<? super T> after
,我们允许前一个消费者(类型T
)与一个消费者结合,该消费者接受的对象不完全是T
类型,但可能比T
小,这是为了方便covariance
。 这使得以下代码听起来:
Consumer<Student> stu = (student) -> {};
Consumer<Person> per = (person) -> {};
stu.andThen(per);
compose
同样的考虑, Function
类型的compose
方法也适用。
IMO 这可能是 vanilla Java 中最复杂的概念。 所以让我们把它分解一下。 我将从你的第二个问题开始。
Function<T, R>
接受类型为T
的实例t
并返回类型为R
的实例r
。 有了继承这意味着,你可以提供一个实例foo
类型的Foo
,如果Foo extends T
,同样返回bar
类型的Bar
,如果Bar extends R
作为一个想要编写灵活的泛型方法的库维护者,很难而且实际上不可能提前知道可能与此方法一起使用的所有类,这些类扩展了T
和R
。 那么我们将如何编写一个处理它们的方法呢? 此外,这些实例具有扩展基类的类型这一事实与我们无关。
这就是通配符的用武之地。在方法调用期间,我们说您可以使用满足所需类的信封的任何类。 对于所讨论的方法,我们有两个不同的通配符使用上限和下限泛型类型参数:
public interface Function<T, R>{
default <V> Function<V, R> compose(Function<? super V, ? extends T> before)
现在让我们说我们想利用这个方法......对于这个例子,让我们定义一些基本类:
class Animal{}
class Dog extends Animal{}
class Fruit{}
class Apple extends Fruit{}
class Fish{}
class Tuna extends Fish{}
想象一下我们的函数和转换定义如下:
Function<Animal, Apple> base = ...;
Function<Fish, Animal> transformation = ...;
我们可以使用compose
组合这些函数来创建一个新函数:
Function<Fish, Apple> composed = base.compose(transformation);
这一切都很好,但现在想象一下,在所需的输出函数中,我们实际上只想使用Tuna
作为输入。 如果我们不使用下界? super V
? super V
作为我们传递给compose
的Function
的输入类型参数,那么我们会得到一个编译器错误:
default <V> Function<V, R> compose(Function<V, ? extends T> before)
...
Function<Tuna, Apple> composed = base.compose(transformation);
> Incompatible types:
> Found: Function<Fish, Apple>, required: Function<Tuna, Apple>
发生这种情况是因为compose
调用的返回类型将V
指定为Tuna
而另一方面, transformation
将其“ V
”指定为Fish
。 所以现在当我们尝试通过transformation
来compose
,编译器需要transformation
来接受一个Tuna
作为它的V
,当然Tuna
并不完全匹配Fish
。
另一方面,代码的原始版本( ? super V
)允许我们将V
视为下限(即它允许V
“逆变”与“不变”)。 编译器能够成功应用下限检查,而不是遇到Tuna
和Fish
之间的不匹配? super V
计算为Fish super Tuna
? super V
,这是真的,因为Tuna extends Fish
。
对于另一种情况,假设我们的调用定义为:
Function<Animal, Apple> base = ...;
Function<Fish, Dog> transformation = ...;
Function<Fish, Apple> composed = base.compose(transformation);
如果我们没有通配符? extends T
? extends T
那么我们会得到另一个错误:
default <V> Function<V, R> compose(Function<? super V, T> before)
Function<Fish, Apple> composed = base.compose(transformation);
// error converting transformation from
// Function<Fish, Dog> to Function<Fish, Animal>
通配符? extends T
? extends T
允许这样做,因为T
解析为Animal
并且通配符解析为Dog
,这可以满足约束Dog extends Animal
。
对于你的第一个问题; 这些边界实际上只在方法调用的上下文中起作用。 在该方法的过程中,通配符将被解析为实际类型,就像? super V
? super V
被解析到Fish
和? extends T
? extends T
被解析为Dog
。 如果没有来自泛型签名的信息,我们将无法让编译器知道可以在类型的方法上使用什么类,因此不允许使用任何类。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.