看《Effective Java》学会的泛型设计--上下限--PECS法则

对于Java中泛型通配符的上下限,一开始我也是比较懵逼的!
作为代码的搬运工,简单记录下搬砖的过程吧,偷笑ing。

上限(upper bound)
<? extends E> 问号代表一个未知类型,这个未知类型是E的子类型,包括E本身,称 E 是通配符 ? 的上限(upper bound)。
下限(lower bound)
<? super T> 未知类型必须是T的父类或T本身, T 是通配符 ? 的下限(lower bound)。
上小菜

一看就明白的代码:

import java.util.ArrayList;
import java.util.Collection;

class Animal {
}

class Dog extends Animal {
}

class Cat extends Animal {
    
}
public class VideoTutorialGeneric {
    public static void main(String[] args) {
	        List<Dog> dogList = new ArrayList<>();
        // 泛型是具体的 既然指定Animal 那dogList就只能是Animal类型的,即使是其子类也不可以,下面这行出现编译错误 
//        Collection<Animal> animals = dogList;
		
        Collection<Object> c1 = new ArrayList<>();

        // 通配符 ?, 表示任何类型  
        Collection<?> c2 = new ArrayList<>();

        // Animal是上限 使用Object出现编译错误, 只能接收: List<Animal>或List<Animal的子类>
//         Collection<? extends Animal> c9 = new ArrayList<Object>();   compile error
        Collection<? extends Animal> c3= new ArrayList<Animal>();
        Collection<? extends Animal> c4 = new ArrayList<Dog>();
        Collection<? extends Animal> c5 = new ArrayList<Cat>();
        /**
         * 下面注释的这行代码出现编译错误
         * 原因: <? extends Animal>, 放到list中的可能是Animal, 也可能是任何Animal的子类, 编译器没有办法确保类型安全, 所以是不能写入元素的
         */
//        c5.add(new Cat());

        // Animal是下限, 使用Dog Cat这种是Animal子类型的 编译不通过, 只能接收: List<Animal>或List<Animal的父类>
        // Collection<? super Animal> c15 = new ArrayList<Dog>();
        // Collection<? super Animal> c16 = new ArrayList<Cat>();
        Collection<? super Animal> c6 = new ArrayList<Object>();
        Collection<? super Animal> c7 = new ArrayList<Animal>();
        List<? super Animal> c8 = new ArrayList<>();
        // Animal 是下限, 编译器是知道这个类型的, 编译器大佬判定没有安全性的问题,这里可以进行元素的写入
        // c8 里只能 add 「Animal类型」或 「Animal子类类型」
        c8.add(new Dog());
        c8.add(new Animal());
       
       /**
         * 下面注释的这行代码出现编译错误
         * 原因: <? super Animal>, c8 list可以放入Animal或Animal的子类, 编译器不知道get出来的是Animal类型还是Animal的子类型,编译器判定是不安全的,故不让通过编译
         */
//        Animal animal = c8.get(0);
    }
}
主食

摘录《Effective Java》中的模拟数据结构栈操作的一段代码:

public class UpperLowerBoundGenericDemo<E> {
    private Object[] elements;
    private int size = 0;
    private static final int DEFAULT_INITIAL_CAPACITY = 16;


    public UpperLowerBoundGenericDemo() {
        elements = new Object[DEFAULT_INITIAL_CAPACITY];
    }

    public void push(E e) {
        ensureCapacity();
        elements[size++] = e;
    }

    public E pop() {
        if (size == 0)
            throw new EmptyStackException();

        @SuppressWarnings("unchecked")
        E result = (E) elements[--size];
        elements[size] = null; // Eliminate obsolete reference
        return result;
    }

    public boolean isEmpty() {
        return size == 0;
    }

    private void ensureCapacity() {
        if (elements.length == size)
            elements = Arrays.copyOf(elements, 2 * size + 1);
    }
上限

现在往UpperLowerBoundGenericDemo 中加入一个方法,

       public void pushAll1(Collection<E> src) {
        for (E e : src) {
            push(e);
        }
    }

在main方法中测试:

   public static void main(String[] args) {
        UpperLowerBoundGenericDemo<Number> stack = new UpperLowerBoundGenericDemo<>();
        List<Integer> list = Arrays.asList(12, 4, 4);
       stack.pushAll1(list);
    }

运行pushAll1 方法会出现编译错误,pushAll1(Collection<E> src) 中的E是Number类型的,往pushAll1里传入List<Integer> ,编译器一检测,是通不过的,因为泛型参数对应的是Number 这一具体类型的,pushAll1 方法里只能传入Number类型的。

那就使用通配符的上限来解决这个问题:

    public void pushAll(Collection<? extends E> src) {
        for (E e : src) {
            push(e);
        }
    }

pushAll1 方法换成pushAll 方法就可以正常编译并运行了,E 是Number类型的,那未知类型 ? 就可以是Number 类型的子类比如 IntegerDouble

这里是一个生产读取元素,使用上限类型,不会出现类型安全隐患。

下限

再往UpperLowerBoundGenericDemo 中加入一个方法,

    // 这个方法的功能是将栈中的元素放到 dst集合中 
    public void popAll1(Collection<E> dst) {
        while (!isEmpty()) {
            dst.add(pop());
        }
    }

在main中执行:

  public static void main(String[] args) {
        UpperLowerBoundGenericDemo<Number> stack = new UpperLowerBoundGenericDemo<>();
        List<Integer> list = Arrays.asList(12, 4, 4);
        
        List<Object> objects = new ArrayList<>();
        stack.popAll(objects);
    }

编译错误,跟上面一样,泛型是具体的,编译器一检测泛型类型ObjectNumber 不一致那就抛出一个error吧。

简单修改一下这个方法:

    public void popAll(Collection<? super E> dst) {
        while (!isEmpty()) {
            dst.add(pop());
        }
    }

再在main方法中换成popAll 来调用,编译运行正常。
方法声明中的 <? super E>, 在本例中E代表Number 类型,? 表示的是Object 类型,同时Object 也是Number 的父类型。 对于这个方法,使用List<Object> 去接收栈中的不同类型的元素是很perfect的。

popAll() 是一个消费写的操作,使用下限类型,同样避免了类型的安全隐患。

下午茶一道
  1. 关于Comparable 的:
    public static <T extends Comparable<T>> T max1(List<T> list) {
        Iterator<T> i = list.iterator();
        T result = i.next();
        while (i.hasNext()) {
            T t = i.next();
            if (t.compareTo(result) > 0)
                result = t;
        }
        return result;
    }

看到 <T extends Comparable<T>> 也是瞬间懵逼,what hell!
<T extends Comparable<T>> 这是自限定(self-bounding,《Thinking in Java 》有相关的解读)。这里表示的限制规则简单来说就是:

max1 方法中所能接受的 T 应该定义成这样class A implements Comparable<A> {}。「类型参数」和「使用这个类型参数的类」的类型应该是一样的。

// self-bounding
class OneClass implements Comparable<OneClass> {  
}
// 只有这样定义的 OneClass 才符合 <T extends Comparable<T>> 的限制规则
List<OneClass> d = new ArrayList<>();
max1(d);
  1. 又来了:
   public static <T extends Comparable<? super T>> T max(List<? extends T> list) {
        // some subtype of T
        Iterator<? extends T> i = list.iterator();
        T result = i.next();
        while (i.hasNext()) {
            T t = i.next();
            if (t.compareTo(result) > 0)
                result = t;
        }
        return result;
    }

看到<T extends Comparable<? super T>> 这个我又百脸懵逼!
看下面一个例子,没准你就豁然开朗了:

class AnotherTree {

}

class Tree implements Comparable<Tree> {

    private int age; // 年龄
    private int height; // 高度

    @Override
    public int compareTo(Tree o) {
        int res = this.getAge() - o.getAge();
        return (res == 0) ? (this.getHeight() - o.getHeight()) : res;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public int getHeight() {
        return height;
    }

    public void setHeight(int height) {
        this.height = height;
    }

    public Tree(int age, int height) {
        this.age = age;
        this.height = height;
    }

    @Override
    public String toString() {
        return "Plant{" +
                "age=" + age +
                ", height=" + height +
                '}';
    }

}

class PineTree extends Tree {

    public PineTree(int age, int height) {
        super(age, height);
    }

    @Override
    public String toString() {
        return "Plant{" +
                "age=" + getAge() +
                ", height=" + getHeight() +
                '}';
    }
}

public class UperBoundDemo {
    public static <T extends Comparable<T>> T max1(List<T> list) {
        Iterator<T> i = list.iterator();
        T result = i.next();
        while (i.hasNext()) {
            T t = i.next();
            if (t.compareTo(result) > 0)
                result = t;
        }
        return result;
    }

    public static <T extends Comparable<? super T>> T max(List<? extends T> list) {
        // some subtype of T
        Iterator<? extends T> i = list.iterator();
        T result = i.next();
        while (i.hasNext()) {
        /*
         The ele-ments returned by the iterator’s next method are of some subtype of T, 
         so they can be safely stored in a variable of type T.
        */
            T t = i.next();
            if (t.compareTo(result) > 0)
                result = t;
        }
        return result;
    }

    public static void main(String[] args) {
        List<AnotherTree> anotherTreeList = new ArrayList<>();
        // AnotherTree 没有implements Comparable
//        max1(anotherTreeList);   // 编译不通过

        List<Tree> treeList = Arrays.asList(new Tree(23, 180), new Tree(24, 109));
        // Tree实现了Comparable 编译通过
        Tree maxTree = max1(treeList);

        List<PineTree> pineTreeList = Arrays.asList(new PineTree(34, 809), new PineTree(23, 567));

//        max1 指定的泛型参数是 「T extends Comparable<T>」,这是一个自限定,
//        这里的泛型参数对传进来的「参数对象类型」的约束是:它的类型参数必须与正在被定义的类相同
//        Tree implements Comparable<Tree>, 这里定义的类(Tree) 和类型参数 Tree 是一样的。
//        PineTree 是 Comparable 类型的,但是参数类型是 Tree,Tree 和 PineTree 是不一样的
//        max1(pineTreeList);   // 编译不通过

    /**
         * max里的泛型限定是:<T extends Comparable<? super T>>
         * 自限定里面加了 Super 关键字,扩大了<T extends Comparable<T>>能表示的范围,这里限制的 T 类型应该类似如下的定义:
         * 1. class Tree implements Comparable<Tree> {}
         * 2. class PineTree implements Comparable<Tree> {}
         * 3. class PineTree implements Comparable<PineTree> {}
         * max(pineTreeList) 的调用中,T 是PineTree
         * PineTree 的父类是Tree,PineTree 继承 Tree,PineTree 是实现了Comparable的,且符合 Comparable<? super PineTree>
         * 和上面的 max1(pineTreeList);  编译不通过对应起来 就好理解了 
         */
        Tree maxPineTree = max(pineTreeList);
        System.out.println(maxPineTree);

    }
}
晚餐时间,少吃点 stay hungry, stay healthy

In total, 引用《Effective Java》里的一段话(不是我装逼,用英文描述真的要清晰一点):

In other words, if a parameterized type represents a T producer, use <? extends T>;
if it represents a T consumer, use <? super T>. 

PECS stands for producer-extends, consumer-super.

And remember that all comparables and comparators are consumers.

有了泛型就可以对类型进行约束, 同时也使得同一类型下的操作变得更加具有扩展性。

public void oneFunction(T t) {

}

对于oneFunction这个函数, 参数t应该定义成<? extends T>还是<? super T>? 如果t是是作为函数oneFunction()函数的生产者, 也就是t里面是有数据的,那就是<? extends T>; 如果t要在函数oneFunction()中消费数据, 也就是t里面是空的,准备往里面放数据,就是一个消费者, 即: <? super T>
还可以对T类型作更进一步的约束:

public <T extends Comparable<? super T>> void oneFunction(T) {

}

其中<T extends Comparable<? super T>> 可以限制T类型还得是一个实现Comparable接口(Comparable<T>Comparable<T的父类>)的类型。

以后只要记住 PECS 就好了。That’s all!

参考:
浅谈Java泛型中的extends和super关键字

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值