Skip to content

Latest commit

 

History

History
215 lines (171 loc) · 8.02 KB

泛型.md

File metadata and controls

215 lines (171 loc) · 8.02 KB

1.泛型

泛型就是通过编写模板代码来适应任意类型,也可以在使用的时候指定类型。它的好处在于使用时不必对类型进行强制转换,可以通过编译器完成对类型的检查。

注意泛型的继承关系:可以把 ArrayList<Integer> 向上转型为 List<Integer>(类可以变), 但不能把 ArrayList<Integer> 向上转型为 ArrayList<Number>(T不能变成父类)。

泛型只在编译阶段有效。

List<Integer> integerList = new ArrayList<>();
List<String> stringList = new ArrayList<>();
System.out.println(integerList.getClass().equals(stringList.getClass()));  // true

有上面的例子可以看出,java 的泛型只在编译阶段有效,之后会将相关信息擦拭,不会进入到运行时期。

2.泛型类

将泛型类型用于类中,这样定义的类被称为泛型类,如 ListSetMap 等。

class Generic<T> {
    private T name;

    public Generic(T name) {
        this.name = name;
    }

    public T getName() {
        return name;
    }
}

上面是自定义的一个普通泛型类,在使用泛型类的时候可以传入泛型实参,也可以不传入,这样类型可以为任何类型。
注意:泛型的参数类型只能是类(Integer 等),不能是基础类型(int 等);

3.泛型接口

泛型接口与泛型类的定义和使用方法类似,常被用在各种类的 generator 中。

interface Generator<T> {
    int size();

    T add(T element);
interface Generator<T> {
    int size();

    T add(T element);

    T remove(int index);
}

// 未传入泛型实参时,类也要申明泛型,否则编译报错:class MyGenerator implements Generator<T>
class MyGenerator<T> implements Generator<T> {
    @Override
    public int size() {
        return 0;
    }

    @Override
    public T add(T element) {
        return null;
    }

    @Override
    public T remove(int index) {
        return null;
    }
}

// 传入实参时,方法的泛型参数也要申明类型
class StringGenerator implements Generator<String> {
    private List<String> stringList = Arrays.asList("a", "b", "c");
    
    @Override
    public int size() {
        return stringList.size();
    }

    @Override
    public String add(String element) {
        stringList.add(element);
        return element;
    }

    @Override
    public String remove(int index) {
        return stringList.remove(index);
    }
}
    T remove(int index);
}

4.泛型通配符

WHY?
Java 泛型的泛型没有协变和逆变特性。
数组是可以协变的,比如 Dog extends Animal,那么 Animal[] 里面可以添加Dog对象。
而集合是不能协变的,也就是说 List<Animal> 不是 List<dog> 的父类,这时候就可以用到通配符了。

List<Number> numbers = new ArrayList<Integer>();  // Incompatible types. Found: 'java.util.ArrayList<java.lang.Integer>', required: 'java.util.List<java.lang.Number>'
List<? extends Number> extendNumbers = new ArrayList<Integer>();  // 使用泛型通配符,OK

通配符主要有三类:

a. 无边界的通配符(Unbounded Wildcards), 就是 <?>, 比如 List<?>, 它的主要作用就是让泛型能够接受未知类型的数据。

List<?> list = new ArrayList<>();
list.add(1);  // 'add(capture<?>)' in 'java.util.List' cannot be applied to '(int)'
list.add("abc");
list.add(null);  // 只有null是所有引用数据类型都具有的元素.
int a = list.get(1);
String b = list.get(2);  // Incompatible types. Found: 'capture<?>', required: 'java.lang.String'
Object c = list.get(3);  // 只能返回Object对象

List list1 = new ArrayList<>();
list1.add(2);  // Unchecked call to 'add(E)' as a member of raw type 'java.util.List'
Object d = list1.get(0);

由上例可以看到 List<?> 只能 add nullget 返回的是 ObjectList 啥都能添加,但会有警告。

b. 固定上边界的通配符(Upper Bounded Wildcards), 使用它的泛型能够接受指定类及其子类类型的数据, 采用 <? extends E> 的形式声明使用该类通配符, 这里的 E 就是该泛型的上边界。

public class Test {
    public static void main(String[] args) {
        ArrayList<Integer> integerArrayList = new ArrayList<>();
        integerArrayList.add(1);
        // 'test(java.util.ArrayList<java.lang.Number>)' in 'cn.shawda.Test' cannot be applied to '(java.util.ArrayList<java.lang.Integer>)'
        System.out.println(test(integerArrayList));
    }
    
    static int test(ArrayList<Number> arrayList) {
        return arrayList.get(0).intValue();
    }
}

可以看到,由于 ArrayList<Integer> 不是 ArrayList<Number> 的子类,所以 test 方法不接受前者为参数。若前者以参数传到 test 方法中也能得到返回的值,使用 ArrayList<? extend Number> 可以使 test 方法接收所有泛型类型为 Number 或其子类的 ArrayList 类型。

public class Test {
    public static void main(String[] args) {
        ArrayList<Integer> integerArrayList = new ArrayList<>();
        integerArrayList.add(1);
        System.out.println(test(integerArrayList));  // OK
    }

    static int test(ArrayList<? extends Number> arrayList) {
        Number number0 = 0;
        arrayList.add(number0);  // 无法add Number类型
        arrayList.add(new Integer(10));  // 'add(capture<? extends java.lang.Number>)' in 'java.util.ArrayList' cannot be applied to '(java.lang.Integer)'
        arrayList.add(null); // OK
        Number number = arrayList.get(0);  // Number类型
        return number.intValue();
    }
}

可以 get 到 Number 类型的结果,只能add null

由上可知,在使用类似 <? extend Number> 通配符作为方法的参数时:

  • 方法内部可以调用获取 Number 的方法,如 Number number = arrayList.get(0);
  • 方法内部无法调用写 Number 的方法(除 null),如 arrayList.add(new Integer(10));

也就是:使用 extends 通配符表示可读不可写。

c. 固定下边界的通配符(Lower Bounded Wildcards), 使用它的泛型能够接受指定类及其父类类型的数据, 采用<? super E>的形式明使用该类通配符, 这里的 E 就是该泛型的下边界。

public class Test {
    private static void testSuper(List<? super Integer> list) {
        list.add(1);
        // Incompatible types. Found: 'capture<? super java.lang.Integer>', required: 'java.lang.Integer'
        Integer integer = list.get(0);
        Object object = list.get(0);  // OK
    }
}

由上可知,使用 <? super Integer> 通配符表示,允许调用 add 方法传入 Integer 的引用,不允许调用 get 方法获得 Integer 的引用,只能获得 Object 的引用。

PECS 原则

可以简单将 extends 看成允许读不允许写,super 看成允许写不允许读。

分别在何时使用它们呢?这就要说到 PECS 原则了:Producer Extends Consumer Super,也就是:若需要返回,那它是生产者,使用 extends 通配符;若需要写入,那它是消费者,使用 super 通配符。

这里以 Collectionscopy() 方法为例:

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 的值需要返回,是生产者,用 extends 通配符;dest 的值要写入,是消费者,用 super 通配符。

无限定通配符

无限定通配符 <?> 不允许读(只能获取 Object)也不允许写(null 除外),只能做一些 null 判断。