Java 泛型 PECS - 生产者extends消费者super

https://www.kancloud.cn/apachecn/howtodoinjava-zh/1952924

昨天,我正在研究一些 Java 集合 API,并且发现了两种主要用于将元素添加到集合中的方法。 他们俩都使用泛型语法来获取方法参数。 但是,第一种方法是使用<? super T>,而第二种方法是使用<? extends E>。 为什么?

首先,让我们看一下这两种方法的完整语法。

此方法负责将集合c的所有成员添加到另一个调用此方法的集合中。

boolean addAll(Collection<? extends E> c);

调用此方法可将“元素”添加到集合c。

public static <T> boolean addAll(Collection<? super T> c, T... elements);

两者似乎都在做简单的事情,所以为什么它们都有不同的语法。 我们许多人可能会纳闷。 在这篇文章中,我试图揭开围绕它的概念的神秘性,该概念主要被称为 PECS (最早由 Joshua Bloch 在他的著作《Effective Java》中创造的术语)。

为什么要使用泛型通配符?
在我与 java 泛型 相关的上一篇文章中,我们了解到,泛型本质上用于类型安全性和不变式。 用例可以是 Integer 的列表,即List<Integer>。 如果您在 Java 中声明List<Integer>之类的列表,则 Java 保证它将检测并报告您将任何非整数类型插入上述列表的任何尝试。

但是很多时候,我们面临这样的情况,为了特定的目的,我们必须在方法中传递类的子类型或超类型作为参数。 在这些情况下,我们必须使用协变(缩小引用)和逆变(扩大引用)之类的概念。

了解<? extends T>
这是 PECS 的第一部分,即 PE(生产者extends)。 为了将其与现实生活中的术语联系起来,让我们使用一篮子水果(即水果的集合)的类比。 当我们从篮子里摘水果时,我们要确保只取出水果而没有其他东西。 这样我们就可以编写如下泛型代码:

Fruit get = fruits.get(0);
在上述情况下,我们需要将水果的集合声明为List<? extends Fruit>。 例如:

class Fruit {
   @Override
   public String toString() {
      return "I am a Fruit !!";
   }
}

class Apple extends Fruit {
   @Override
   public String toString() {
      return "I am an Apple !!";
   }
}

public class GenericsExamples
{
   public static void main(String[] args)
   {
      //List of apples
      List<Apple> apples = new ArrayList<Apple>();
      apples.add(new Apple());

      //We can assign a list of apples to a basket of fruits;
      //because apple is subtype of fruit 
      List<? extends Fruit> basket = apples;

      //Here we know that in basket there is nothing but fruit only
      for (Fruit fruit : basket)
      {
         System.out.println(fruit);
      }

      //basket.add(new Apple()); //Compile time error
      //basket.add(new Fruit()); //Compile time error
   }
}

查看上面的for循环。 它确保了从篮子里出来的任何东西都肯定会结出果实; 因此,您可以遍历它,然后简单地将其浇铸成水果。 现在,在最后两行中,我尝试在购物篮中添加Apple和Fruit,但是编译器不允许我添加。 为什么?

如果我们考虑一下,原因很简单。 <? extends Fruit>通配符告诉编译器我们正在处理水果类型的子类型,但是我们无法知道哪个水果,因为可能存在多个子类型。 由于没有办法说出来,而且我们需要保证类型安全(不变性),因此您不能在此类结构内放置任何内容。

另一方面,由于我们知道它可能是Fruit的子类型,因此可以从结构中获取数据,并保证它是Fruit。

在上面的示例中,我们从集合List<? extends Fruit> basket中取出元素。所以这个篮子实际上是在生产水果。 简而言之,当您只想从集合中检索元素时,请将其视为生产者并使用<? extends T>语法。 “生产者extends”现在对您来说更有意义。

了解<? super T>
现在以不同的方式查看上述用例。 假设我们正在定义一个方法,在此方法中,我们只会在此购物篮中添加不同的水果。 就像我们在帖子“ addAll(Collection<? super T> c, T... elements)”开头看到的方法一样。 在这种情况下,篮子用于存储元素,因此应称为元素的使用者。

现在看下面的代码示例:

class Fruit {
   @Override
   public String toString() {
      return "I am a Fruit !!";
   }
}

class Apple extends Fruit {
   @Override
   public String toString() {
      return "I am an Apple !!";
   }
}

class AsianApple extends Apple {
   @Override
   public String toString() {
      return "I am an AsianApple !!";
   }
}

public class GenericsExamples
{
   public static void main(String[] args)
   {
      //List of apples
      List<Apple> apples = new ArrayList<Apple>();
      apples.add(new Apple());

      //We can assign a list of apples to a basket of apples
      List<? super Apple> basket = apples;

      basket.add(new Apple());      //Successful
      basket.add(new AsianApple()); //Successful
      basket.add(new Fruit());      //Compile time error
   }
}

我们可以在篮子内添加苹果,甚至是亚洲苹果,但不能在篮子中添加Fruit(苹果的超类型)。 为什么?

原因是购物篮是对Apple的超类商品列表的引用。 同样,我们不知道它是是哪个超类型,但是我们知道可以将Apple及其任何子类型(它们是Fruit的子类型)添加为没有问题(您可以随时在超类的集合中添加一个子类)。 因此,现在我们可以在购物篮中添加任何类型的Apple。

如何从这种类型的数据中获取数据呢? 事实证明,您唯一可以使用的是Object实例:由于我们无法知道它是哪个超类型,因此编译器只能保证它将是对Object的引用,因为Object是任何 Java 类型的超类型。

在上面的示例中,我们将元素放入集合List<? super Apple> basket; 所以在这里,这个篮子实际上是在消耗苹果等元素。 简而言之,当您只想在集合中添加元素时,请将其视为使用者并使用<? super T>语法。 现在,“消费者super”对您也应该更有意义。

总结
基于上述推理和示例,让我们总结要点。

如果需要从集合中检索类型T的对象,请使用<? extends T>通配符。
如果需要将T类型的对象放入集合中,请使用<? super T>通配符。
如果您需要同时满足这两个条件,请不要使用任何通配符。 就这么简单。
简而言之,请记住术语 PECS。 生产者extends消费者super。 真的很容易记住。
这就是 Java 中泛型中简单而又复杂的概念的全部。 通过评论让我知道您的想法。

祝您学习愉快!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 193,812评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,626评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,144评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,052评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,925评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,035评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,461评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,150评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,413评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,501评论 2 307
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,277评论 1 325
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,159评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,528评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,868评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,143评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,407评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,615评论 2 335

推荐阅读更多精彩内容