繁体   English   中英

关于Java泛型和java.util.function.Function设计的问题

[英]Question about Java generics and design of java.util.function.Function

  1. 关于通配符的问题

示例: 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)编译成功呢?

  1. 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具有所产生的对象上。

这两个与称为covariancecontravariance子类型化形式密切相关

至于第二个问题,你也可以参考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> afterConsumer<T> afterConsumer<? 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

作为一个想要编写灵活的泛型方法的库维护者,很难而且实际上不可能提前知道可能与此方法一起使用的所有类,这些类扩展了TR 那么我们将如何编写一个处理它们的方法呢? 此外,这些实例具有扩展基类的类型这一事实与我们无关。

这就是通配符的用武之地。在方法调用期间,我们说您可以使用满足所需类的信封的任何类。 对于所讨论的方法,我们有两个不同的通配符使用上限和下限泛型类型参数:

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作为我们传递给composeFunction的输入类型参数,那么我们会得到一个编译器错误:

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 所以现在当我们尝试通过transformationcompose ,编译器需要transformation来接受一个Tuna作为它的V ,当然Tuna并不完全匹配Fish

另一方面,代码的原始版本( ? super V )允许我们将V视为下限(即它允许V “逆变”与“不变”)。 编译器能够成功应用下限检查,而不是遇到TunaFish之间的不匹配? 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM