为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

达令说 2023-06-14 14:43:06
关于通配符的问题例子: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)编译成功呢?的设计java.util.function.Functionpublic 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?(还可以通过编译灵活使用)
查看完整描述

2 回答

?
HUX布斯

TA贡献1876条经验 获得超6个赞

要理解这些问题,您必须了解泛型是如何工作的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方法)的 a 必须提供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是:当我们使用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


查看完整回答
反对 回复 2023-06-14
?
沧海一幻觉

TA贡献1824条经验 获得超5个赞

IMO 这可能是 vanilla Java 中最复杂的概念。所以让我们把它分解一下。我将从你的第二个问题开始。


Function<T, R>t获取type 的实例并返回type 的T实例。通过继承,这意味着您可以提供if类型的实例,并类似地返回if类型。rRfooFooFoo extends TbarBarBar 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输入类型参数,那么我们会得到一个编译器错误:Functioncompose


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接受 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

//    Function<Fish, Dog> to Function<Fish, Animal>

通配符允许它按照is resolved to 的? extends T方式工作,并且通配符 resolves to可以满足约束条件。TAnimalDogDog extends Animal


对于你的第一个问题;这些边界实际上只在方法调用的上下文中起作用。在该方法的过程中,通配符将被解析为实际类型,就像? super V被解析为Fish和? extends T被解析为一样Dog。如果没有来自泛型签名的信息,我们将无法让编译器知道可以在类型的方法上使用哪个类,因此不允许使用任何类。


查看完整回答
反对 回复 2023-06-14
  • 2 回答
  • 0 关注
  • 121 浏览

添加回答

举报

0/150
提交
取消
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号