首页 > 编程知识 正文

java pecs原则,深入理解java泛型详解

时间:2023-05-03 07:29:28 阅读:225901 作者:764

说到泛型,大家应该都很熟悉,最常用的就是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一份,下面直接讲使用哈。

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

/** * 动物(祖父类级别) */@Datapublic class Animal { /** * 判断条件,默认为false */ private Boolean flag = false ; /** * 名字 */ private String name; /** * 重量 */ private String weight;}/** * 鸟类 */@Datapublic class Bird extends Animal{ /** * 飞多高 */ private String flyHeight;}/** * 鹰 */@Datapublic class Eagle extends Bird{ /** * 住在山上 */ private String mountain; /** * 吃肉 */ private String meat;}/** * 鹦鹉 */@Datapublic 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("缥缈的戒指"); 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":"缥缈的戒指"} 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 Animal()); sList.add(new Object());// 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~

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。