泛型总结

一 容器类:请取出同一类型的物品

1 不使用泛型

// A是B的父类
List list = new ArrayList<>();
list.add(new A());
list.add(new A());
list.add(new String());

System.out.println((A)list.get(0));
System.out.println((A)list.get(1));
// 编译时不报错,运行时报错:java.lang.ClassCastException
System.out.println((A)list.get(2)); 

2 不恰当泛型

// A是B的父类
List<A> list = new ArrayList<>();
list.add(new A());
list.add(new B());
// list.add(new String()); // 加上时,编译会报错

System.out.println((B)list.get(0));
// 编译时不报错,运行时报错:java.lang.ClassCastException
System.out.println((B)list.get(1)); 
  • 补救方案:先进行类型判断
A a1 = list.get(1);
if(a1.getClass() == B.class){
    System.out.println((B)a1); 
}

3 正确使用泛型

// A是B的父类
List<A> list = new ArrayList<>();
list.add(new A());
list.add(new B());

// 如果把B放入A的容器中,就把B当成A使用,不要想着强转回来使用了。
System.out.println(list.get(0));
System.out.println(list.get(1));

4 总结

  • 没有泛型时,不能依赖编译器的语法检查,由于List 等同于 List<Object>。
  • 泛型能够给编译器提供类型检查。
  • 父类容器虽然可以放入子类,但取出来尽量不要还原回子类进行使用,费时费力。

二 泛型通配符

1 准备

public class Main {
    static class A {

    }

    static class B extends A {

    }

    static class C<T>{
        private T t;

        public C(){

        }

        public T getT() {
            return t;
        }

        public void setT(T t) {
            this.t = t;
        }
    }
}

2 测试

(1)类型擦除和类型强转

  • java
public class Main {
    public static void main(String[] args) {
        C<A> cA = new C<>();
        // 类型强转:这就是为什么1.1节中编译不报错,运行时报错的原因。编译时类型擦错,并不进行检查。
        // Object obj = cA.getT();
        // A t = (A)obj;
        A t = cA.getT(); 
    }
}
  • 字节码
public static void main(java.lang.String[]);
    Code:
       0: new           #2       // class Main$C
       3: dup
       4: invokespecial #3       // Method Main$C."<init>":()V
       7: astore_1
       8: aload_1
       9: invokevirtual #4       // Method Main$C.getT:()Ljava/lang/Object;
      12: checkcast     #5       // class Main$A
      15: astore_2
      16: return

(2)C<A>和C<B>是相同类型吗?

public static void main(String[] args) {
    C<A> cA = new C<>();
    C<B> cB = new C<>();
    // 为什么Class<? extends C> 而不是Class<C> Class<?> Class<? super C> ?
    Class<? extends C> aClass = cA.getClass();
    Class<? extends C> bClass = cB.getClass();
    boolean b = aClass == bClass; // true
}

(3)通配符作为入参

  • demo1
public static void main(String[] args) {
    C<A> cA = new C<>();
    C<B> cB = new C<>();
    test(cA);
    test(cB); // 编译时错误:Error: java: 不兼容的类型
}

public static void test(C<A> c){
    A a = c.getT();
}
  • demo2: C<Object>不是C<A>的父类
public static void main(String[] args) {
    C<A> cA = new C<>(new A());
    C<B> cB = new C<>(new B());
    test(cA); // 编译时错误:Error: java: 不兼容的类型
    test(cB); // 编译时错误:Error: java: 不兼容的类型
}

public static void test(C<Object> c) {
    Object t = c.getT();
}
  • demo3:C<?>接收所有类型
public static void main(String[] args) {
    C<A> cA = new C<>(new A());
    C<B> cB = new C<>(new B());
    test(cA);
    test(cB);
}

public static void test(C<?> c) { // 或者C c也行
    Object t = c.getT();
}
  • demo4:定义上界限,泛型是A或者继承A即可。
public static void main(String[] args) {
    C<A> cA = new C<>(new A());
    C<B> cB = new C<>(new B());
    test(cA);
    test(cB);
}

public static void test(C<? extends A> c) {
    A t = c.getT();
}
  • demo5:定义下界限,泛型是A或A的父类。
public static void main(String[] args) {
    C<A> cA = new C<>(new A());
    C<B> cB = new C<>(new B());
    test(cA);
    test(new C<Object>());
    test(cB); // 编译时报错:
}

public static void test(C<? super A> c) {
    Object t = c.getT();
}

(4)统配符作为返回值

  • demo1:可以修改,但是需要是泛型是A或者A的子类.
public static void main(String[] args) {
    C<A> test = test();
    test.setT(new A());
    test.setT(new B());
    test.setT(new Object()); // 编译错误
}

public static C<A> test() {
    C<A> cA = new C<>(new A());
    return cA;
}
  • demo2:?返回,无法修改,只能读取
public static void main(String[] args) {
    C<?> test = test();
    test.setT(new A()); // 编译错误
    test.setT(new B()); // 编译错误
    test.setT(new Object()); // 编译错误
}

public static C<?> test() {
    C<A> cA = new C<>(new A());
    return cA;
}
  • demo3:?extends 返回,无法修改,只能读取,和demo2相同,但是类中的泛型是什么。
public static void main(String[] args) {
    C<? extends A> test = test();
    test.setT(new A()); // 编译错误
    test.setT(new B()); // 编译错误
    test.setT(new Object()); // 编译错误
}

public static C<? extends A> test() {
    C<A> cA = new C<>(new A());
    return cA;
}
  • demo4:可以修改,但泛型需要是A或者继承A的类,和demo1相同。
public static void main(String[] args) {
    C<? super A> test = test();
    test.setT(new A());
    test.setT(new B());
    test.setT(new Object()); // 编译错误
}

public static C<? super A> test() {
    C<A> cA = new C<>(new A());
    return cA;
}
  • 结论:
    • 泛型返回时,如果可以修改则指定泛型类型即可,如demo1
    • 泛型返回时,如果不可以修改,则使用demo3,这样可以知道泛型类型是什么。这也是为什么c.getClass()方法返回Class<? extends C>的原因。

(5)泛型多继承

static class A {}

static interface D {}

// extends 类或接口 & 接口 & 接口 ...
static class C<T extends A & D & Comparable> {
    private T t;
}

3 总结

  • 编译时进行类型擦除,取出时进行类型强转。
  • Class类型相同,但是泛型类型不同。
  • 传参:Class类型相同情况下
    • ?:接收所有类型的泛型类型
    • ? extends T:接收泛型类型为T或者T的子类
    • ? super T:接收泛型类型为T或者T的父类
    • T:只接收泛型类型为T
  • 返回:都支持读取
    • ?:无法修改
    • ? extends T:无法修改,但知道泛型类型为T
    • ?super T:支持修改为T或T的子类的对象
    • T:支持修改为T或T的子类的对象
  • 泛型继承:T extends 类或者接口 & 接口 & 接口 …