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

关于通配符的问题

例子: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

您正在使用通用通配符。您无法执行添加操作,因为类类型不确定。你不能添加/放置任何东西(null 除外)&mdash;&mdash;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<V,T>当返回类型是Function<V,R>并且输入类型是V?(还可以通过编译灵活使用)


达令说
浏览 122回答 2
2回答

HUX布斯

要理解这些问题,您必须了解泛型是如何工作的subtyping(在 Java 中使用关键字明确表示extends)。Andreas 提到了PECS规则,这是它们在 Java 中的表示。首先,我想指出上面的代码可以通过简单的强制转换来纠正ArrayList<? super Student> list = new ArrayList<>();list.add(new Student());ArrayList<Person> a = (ArrayList<Person>) list; // a covariancea.add(new Person());并且编译和运行良好(而不是引发任何异常)原因很简单,当我们有一个consumer(接受一些对象并使用它们,例如方法add)时,我们希望它接受我们指定类型no more than(超类)的对象T,因为使用过程可能需要任何成员(变量,方法等)它想要的类型,我们希望确保该类型T满足消费者所需的所有成员。相反,producer为我们生成对象(如get方法)的 a 必须提供no less than指定类型的对象T,以便我们可以访问T生成的对象上的任何成员。covariance这两个与称为和的子类型形式密切相关contravariance至于第二个问题,你也可以参考一下的实现Consumer<T>(比较简单):default Consumer<T> andThen(Consumer<? super T> after) {&nbsp; Objects.requireNonNull(after);&nbsp; return (T t) -> { accept(t); after.accept(t); };}我们需要这个的原因? super T是:当我们使用Consumer方法组合两个 s时andThen,假设前者Consumer采用 type 的对象T,我们希望后者采用 type 的对象no more than T,这样它就不会尝试访问任何T不有。因此,我们不是简单地写Consumer<T> afterbut Consumer<? super T> after,而是允许前一个消费者(类型T)与一个消费者结合,该消费者接受一个不完全是 type 的对象T,但可能比 小T,方便covariance。这使得以下代码听起来:Consumer<Student> stu = (student) -> {};Consumer<Person> per = (person) -> {};stu.andThen(per);出于同样的考虑,compose类型方法也适用。Function

沧海一幻觉

IMO 这可能是 vanilla Java 中最复杂的概念。所以让我们把它分解一下。我将从你的第二个问题开始。Function<T, R>t获取type 的实例并返回type 的T实例。通过继承,这意味着您可以提供if类型的实例,并类似地返回if类型。rRfooFooFoo extends TbarBarBar extends R作为一个想要编写一个灵活的泛型方法的库维护者,很难,实际上不可能提前知道所有可能与该方法一起使用的扩展T和R. 那么我们如何编写处理它们的方法呢?此外,这些实例具有扩展基类的类型这一事实与我们无关。这就是通配符的用武之地。在方法调用期间,我们说您可以使用满足所需类范围的任何类。对于所讨论的方法,我们有两个不同的通配符,它们使用上限和下限泛型类型参数:public interface Function<T, R>{&nbsp; &nbsp; default <V> Function<V, R> compose(Function<? super V, ? extends T> before)现在让我们说我们想利用这个方法......例如让我们定义一些基本类:class Animal{}&nbsp;class Dog extends Animal{}&nbsp;class Fruit{}&nbsp;class Apple extends Fruit{}&nbsp;class Fish{}&nbsp;class Tuna extends Fish{}&nbsp;想象一下我们的函数和转换定义如下:Function<Animal, Apple> base = ...;Function<Fish, Animal> transformation = ...;我们可以组合这些函数compose来创建一个新函数:Function<Fish, Apple> composed = base.compose(transformation);这一切都很好,但现在想象一下,在所需的输出函数中,我们实际上只想用作Tuna输入。如果我们不使用下界作为我们传递给的? super V输入类型参数,那么我们会得到一个编译器错误:Functioncomposedefault <V> Function<V, R> compose(Function<V, ? extends T> before)...Function<Tuna, Apple> composed = base.compose(transformation);> Incompatible types:&nbsp;> Found: Function<Fish, Apple>, required: Function<Tuna, Apple>发生这种情况是因为调用的返回类型compose指定V为,Tuna而transformation另一方面指定其“ V”为Fish。所以现在当我们尝试传递transformation给compose编译器时需要transformation接受 aTuna作为其V当然Tuna不完全匹配Fish。另一方面,代码的原始版本 ( ? super V) 允许我们将其视为V下界(即它允许“逆变”与“不变” V)。编译器不会遇到Tuna和之间的不匹配,而是Fish能够成功应用? super V计算结果为 的下限检查Fish super Tuna,自 以来为真Tuna extends Fish。对于另一种情况,假设我们的调用定义为:Function<Animal, Apple> base = ...;Function<Fish, Dog> transformation = ...;Function<Fish, Apple> composed = base.compose(transformation);如果我们没有通配符,? extends T那么我们会得到另一个错误:default <V> Function<V, R> compose(Function<? super V, T> before)Function<Fish, Apple> composed = base.compose(transformation);// error converting transformation from//&nbsp; &nbsp; Function<Fish, Dog> to Function<Fish, Animal>通配符允许它按照is resolved to 的? extends T方式工作,并且通配符 resolves to可以满足约束条件。TAnimalDogDog extends Animal对于你的第一个问题;这些边界实际上只在方法调用的上下文中起作用。在该方法的过程中,通配符将被解析为实际类型,就像? super V被解析为Fish和? extends T被解析为一样Dog。如果没有来自泛型签名的信息,我们将无法让编译器知道可以在类型的方法上使用哪个类,因此不允许使用任何类。
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Java