28

面试常问的PECS原则,到底是什么鬼?

 4 years ago
source link: http://mp.weixin.qq.com/s?__biz=MzA4MTc4NTUxNQ%3D%3D&%3Bmid=2650520627&%3Bidx=1&%3Bsn=447cc7893452252268dd5e5e20b5125f
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

e6JBVrU.gif

原创:小姐姐味道(微信公众号ID:xjjdog),欢迎分享,转载请保留出处。

温馨提示:泛型相关。以下内容请在安静的场所、充足的时间下查看,因为它非常的绕,容易把人绕晕。

PECS的全程是 Producer Extends Consumer Super ,第一次听说,我一脸懵逼。但看到jdk中越来越多的泛型代码,我决定去了解一下。

java的泛型,只在编译期有效。也就是说,编译之后的字节码,已经抹除了泛型信息。

其实,对于常年接触业务代码的同学来说,泛型用的并不是特别多。当你使用设计模式设计代码,或者在设计一些比较底层的框架时,肯定会碰到这个问题。

一个例子

泛型该怎么写?我们首先看一下jdk中的一些例子。

java.util.function.Consumer

@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);
    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}

java8的interface新增了 staticdefault 方法,我们不去过多关注。你会发现,里面有 <T,R> 字样, <? super V, ? extends T> 字样等。

那什么时候该用super,什么时候该用extends?这就是PECS原则。

为了解释这个原理,我们创建三个类。

A,B,C。

其中。A extends B,B extends C。

static class A extends B{}
static class B extends C{}
static class C {}

然后,我们使用测试类测试一下。

static class Example<T>{
    }

    public static void main(String[] args) {
        {
            Example<? extends A> testAA = new Example<A>();
            Example<? extends A> testAB = new Example<B>();//报错
            Example<? extends A> testAC = new Example<C>();//报错
            Example<? extends B> testBA = new Example<A>();
            Example<? extends B> testBC = new Example<C>();//报错
            Example<? extends C> testCA = new Example<A>();
            Example<? extends C> testCB = new Example<B>();
        }
        {
            Example<? super A> testAA = new Example<A>();
            Example<? super A> testAB = new Example<B>();
            Example<? super A> testAC = new Example<C>();
            Example<? super B> testBA = new Example<A>();//报错
            Example<? super B> testBC = new Example<C>();
            Example<? super C> testCA = new Example<A>();//报错
            Example<? super C> testCB = new Example<B>();//报错
        }

    }

为了更直观一些,我们截个idea的图。

YzEfmaa.jpg!web

我们返回头来再看 <? extends T> ,只要后面的new,声明的是T的子类或者T本身,那么都是没错的。反之,如果是它的父类,则报错。这很好理解,后半部分的实例,一定要能够全面覆盖前面的声明。这也就是Producer-Extends,它可以对外提供对象(难以理解的概念)。

接下来我们看一下 <? super T> 。只要是T的父类或者T本身,都没有什么问题,甚至可以是Object。比如,下面的代码就不会报错。

Example<? super C> testCO = new Example<Object>();

根据字面意思,Consumer-super也比较晦涩,如果设计的类是消费者,那应该用super关键字为此类型指定一个子类。

JZB7nuu.jpg!web

这张图只画了声明部分的原则。为了配合上面这张图,进行更精细的理解,我们创建一个7层的继承关系。

static class Parent1{}
static class Parent2 extends Parent1{}
static class Parent3 extends Parent2{}

static class T extends Parent3{}

static class Child1 extends T{}
static class Child2 extends Child1{}
static class Child3 extends Child2{}

同时,我们创建两个集合容器进行验证。

List<? extends T> extendsT = new ArrayList<>();

List<? super T > superT = new ArrayList<>();

以下代码运行都是没有问题的。

List<? super T > superT = new ArrayList<>();
superT.add(new T());
superT.add(new Child1());
superT.add(new Child2());
superT.add(new Child3());

我们把代码分成两部分,一部分是泛型集合的声明部分。一部分是实例的初始化部分。可以看到,? super T界定了最小子类是T,则声明部分的最小类就是T, ArrayList 后面的 <> ,可以是T的任何父类。但是,当向里面添加元素时,初始化的却是T的 子类

再来看 extendsT 。当我们往里添加数据的时候,无一例外的报错了。

extendsT.add(new T());
extendsT.add(new Child1());
extendsT.add(new Parent1());
extendsT.add(new Parent2());
extendsT.add(new Object());

那是因为,extendsT中存放的其实是T的一种子类(现象),如果我们去添加元素,其实不知道到底应该添加T的哪个子类,这个时候,在进行强转的时候,肯定会出错。 但是如果是从集合中将元素取出来,我们则可以知道取出来的元素肯定是T类型(全是它的子类)。

接下来,我们再强行分析一下 ? super T superT中,因为 的都是类型T的父类(容器),所以如果去添加T类或者T的子类(操作),肯定没什么问题。 但是如果将元素取出来,则不知道到底是什么类型,所以superT可以添加元素但是没法取出来。

按照我们以往的经验,extendsT只出不进,属于生产者一类; superT只进不出,属于消费者。 这也就有了我们上面所提到的“Producer Extends Consumer Super”,也就是PECS原则。

这个过程可真是绕,我认为这是定义非常失败的一个名词。

End

现在,再来看我们文章头部jdk的类Consumer,是不是有了新的理解?其实,这个函数是和函数编程相关的。java8的四个核心函数接口有:Function、Consumer、Supplier、Predicate。

Function<T, R> T:入参类型,R:出参类型。

Consumer<T> T:入参类型;没有出参。

Supplier<T> T:出参类型;没有入参。

Predicate<T> T:入参类型;出参类型Boolean。

想要对PECS有更深入的了解,可以深入了解一下函数编程相关的这四个接口。哪怕你只是看一下它的定义,也会有一种 原来如此 的感觉。

作者简介: 小姐姐味道 (xjjdog),一个不允许程序员走弯路的公众号。聚焦基础架构和Linux。十年架构,日百亿流量,与你探讨高并发世界,给你不一样的味道。我的个人微信xjjdog0,欢迎添加好友,进一步交流。

近期热门文章

996的乐趣,你是无法想象的

魔幻现实主义,关爱神经衰弱

一切荒诞的傲慢,皆来源于认知

不要被标题给骗了,画面感十足的消遣文章

《必看!java后端,亮剑诛仙》

后端技术索引,中肯火爆。全网转载上百次。

《学完这100多技术,能当架构师么?(非广告)》

精准点评100多框架,帮你选型

rIZj63q.gif


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK