深入理解java泛型------PECS法则

说到泛型,大家应该都很熟悉,最常用的就是List<T> list这种。如果不是对方法的封装处理,利用反射,泛型类直接写的话,其实没什么需要特别注意的,缺少泛型,编译器自然会提醒,加上也就行了。

好了,我们先引入一个简单的泛型。

     /**
         * 这个Integer可以写成int么?为什么?
         */
        List<Integer> list = new ArrayList<>();

我们知道创建一个集合,都是要定义泛型的,当然也可以模糊定义,比如List<T> list等。那么这个Integer可以写成int么?这个当然是不能的,如下图,我们可以看到,泛型一般都是继承于Object,也就是说是一个包装类,int只是一个基本类型,是无法作为泛型的

好了,我们接着说,<T>和<?>这两种泛型的使用不少伙伴应该都遇到过,可能在某一段时间内都比较茫然,这俩哥们都是类型的代表,有啥子区别啊?

这个呢,说开了其实很简单,<T>(当然<E>也是一样,都是一个字母代替)这种标识,大家可以看到,<T>这个字母是固定的,一个字母也就是代替一个包装类,也就是说,这个泛型是一个类,比如说T表示的是Integer,那么只要使用List<T>或者其他包含T的使用,都是代表的是Integer。

<?>泛型是一个问号,其实表示的也很清楚,就是说不知道会传来什么类型,什么类型都可以表示。

我们知道,泛型的使用一般是方法的封装,利用反射等技术,抽取代码中的公共部分,这个时候一般才会使用<T>,<?>,可能有些伙伴还不是很明白,我们展开下。

Java泛型是J2 SE1.5中引入的一个新特性,其本质是参数化类型,也就是说所操作的数据类型被指定为一个参数(type parameter)这种参数类型可以用在接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法

概念的解释,我不是很擅长, 直接copy一份,下面直接讲使用哈。

先创建几个类(可以跳过)

/**
 * 动物(祖父类级别)
 */
@Data
public class Animal {

    /**
     * 判断条件,默认为false
     */
    private Boolean flag = false ;

    /**
     * 名字
     */
    private String name;
    /**
     * 重量
     */
    private String weight;

}

/**
 * 鸟类
 */
@Data
public class Bird extends Animal{
    /**
     * 飞多高
     */
    private String flyHeight;

}

/**
 * 鹰
 */
@Data
public class Eagle extends Bird{

    /**
     * 住在山上
     */
    private String mountain;
    /**
     * 吃肉
     */
    private String meat;

}

/**
 * 鹦鹉
 */
@Data
public class Parrot extends Bird {

    /**
     * 住的笼子
     */
    private String cage;

    /**
     * 吃米饭
     */
    private String rice;
}

我们写个来一步步看哈,先来个简单的,就是传来一个对象Animal,根据规则判断,然后进行赋值name,不用泛型,抽取方法中的公共部分,就是传来一个Bird会处理,传来一个Parrot也会处理,写一个简单的方法setAnimalName(Animal animal),实现下逻辑。

  /**
     * 设置动物名称
     * 无泛型,入参直接写父类名称,子类也可以传进来处理
     * 不过要处理的类型最好是父类的,不然要特殊处理(用反射)
     * @param animal
     */
    private static void setAnimalName(Animal animal){
        if(animal.getFlag()){
            animal.setName("animal");
        }
    }

    public static void main(String[] args) {
        Eagle eagle = new Eagle();
        eagle.setMeat("beef");
        eagle.setName("jake");

        Parrot parrot = new Parrot();
        parrot.setFlag(true);
        parrot.setCage("red-cage");

        setAnimalName(eagle);
        setAnimalName(parrot);
        System.out.println(" eagle:"+ JSON.toJSONString(eagle)+
                 "\n parrot:"+JSON.toJSONString(parrot));

    }

可以看下打印输出,虽然没有用泛型,但是继承关系可以实现:

 eagle:{"flag":false,"meat":"beef","name":"jake"}
 parrot:{"cage":"red-cage","flag":true,"name":"animal"}

有些小伙伴总感觉这种应该要用泛型吧?为啥没用呢?当然,用也是可以的,我们来看下。

   /**
     * 使用泛型
     * @param t
     */
    private static <T extends Animal> void setAnimalName(T t){
        if(t.getFlag()){
            t.setName("animal");
        }
    }

是不是感觉有点意思的样子?结果是一样的,就不贴出来了。

下面延伸一下,假如我没有继承关系,我只知道传来的对象里有一个字段name和一个字段flag,这个时候,当然只能使用泛型了,就是使用简单的反射和递归,来看下。

    /**
     * 使用泛型
     * @param t
     */
    private static <T> void setAnimalName(T t){
        Class<?> temp = t.getClass();
        try {
            //flag 可能在父类中,使用clzz = clzz.getSuperclass() 递归
            Method method = getMethod(temp,"getFlag");
            Object invoke = method.invoke(t);
            if(invoke instanceof Boolean && (Boolean) invoke){
                Method setNameMethod = getMethod(temp,"setName",String.class);
                //设置名称
                setNameMethod.invoke(t, "animal");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 递归获取方法
     * @param temp
     * @param methodName
     * @return
     */
    private static Method getMethod(Class<?> temp,String methodName,Class<?>... parameterTypes){
        Method method = null ;
        while (method == null && !temp.getName().equalsIgnoreCase("java.lang.Object")){
            try {
                method = temp.getDeclaredMethod(methodName,parameterTypes);
            } catch (NoSuchMethodException e) {
                //源码中抛出了异常,所以还要捕获处理掉
            }
            temp = temp.getSuperclass();
        }
        return method;
    }
}

常用的泛型处理就说到这,下面我们来看下PECS,简单的说就是 生产者(Producer)使用extends,消费者(Consumer)使用super,代码看的有点懵的话可以先看下面的附图。

 /**
     * PECS理解
     */
    static public void test(){

        //继承Bird 的类是有多个的,这个类不是固定的,所以使用?,使用T会报错
        List<? extends Bird> pList = new ArrayList<>();

        pList = Arrays.asList(new Bird(),new Bird());
//        pList.add(new Bird());//报错
//        pList.add(new Eagle());//报错
//        pList.add(new Parrot());//报错
//        pList.add(new Animal()); //报错
        Bird bird = pList.get(0);
        System.out.println("sList的大小:"+pList.size() +"\n bird"+bird);

        List<? super Bird> sList = new ArrayList<>();
        sList.add(new Bird());
        sList.add(new Parrot());
        sList.add(new Eagle());
//        Object o = sList.get(0);  //返回的是Object,没啥意义

        System.out.println("sList的大小:"+sList.size());
    }
    

上面就是PECS的操作代码,直接看的话有点晕,下面画了一张图,大家可以看看哈,不对的话可以指出。

 

PECS原则总结:

  • 如果要从集合中读取类型T的数据,并且不能写入,可以使用 ? extends 通配符;(Producer Extends)
  • 如果要从集合中写入类型T的数据,并且不需要读取,可以使用 ? super 通配符;(Consumer Super)
  • 如果既要存又要取,那么就不要使用任何通配符。

 接着来看一个经典的应用JDK 8 Collections.copy()

public static <T> void copy(List<? super T> dest, List<? extends T> src) {
        int srcSize = src.size();
        if (srcSize > dest.size())
            throw new IndexOutOfBoundsException("Source does not fit in dest");

        if (srcSize < COPY_THRESHOLD ||
            (src instanceof RandomAccess && dest instanceof RandomAccess)) {
            for (int i=0; i<srcSize; i++)
                dest.set(i, src.get(i));
        } else {
            ListIterator<? super T> di=dest.listIterator();
            ListIterator<? extends T> si=src.listIterator();
            for (int i=0; i<srcSize; i++) {
                di.next();
                di.set(si.next());
            }
        }
    }

大家可以看到,src是数据源头,赋值到dest,相当于src 生产(Producer)数据,dest消费(Customer)数据。dest重新赋值数据,数据类型是T或者T的子类,当然也是T父类的子类。而src进行去数据,获取的数据返回的类型都是T(其中可能有T的子类,不过向上转换了,也是T)。这段源码不是很难,但是其中的思想还是很经典的。大家可以看看哈。这个源码不是很繁琐,大伙应该都能看的懂,就不写注释了哈。

No sacrifice,no victory~

猜你喜欢

转载自blog.csdn.net/zsah2011/article/details/109130371