Java笔记05 - 泛型

泛型是一种"代码模板"

什么是泛型

  • T可以是任何class. 编写一次模板, 创建任意类型的ArrayList
  • 泛型是定义一种模板, 例如: ArrayList<T>, 然后再代码中为用到的类创建ArrayList<类型>
  • 编写一次, 万能匹配, 通过了编译器又保证了类型安全

向上转型

  • 通过应用接口, 实现向上转型
  • ArrayList<T> implements List<T>: ArrayList<T>向上转型为List<T>
  • 模板之间没有任何继承关系: ArrayList<Integer>ArrayList<Number>两者完全没有继承关系
  • 在向上继承时, T不可变!

使用泛型

  • 使用时, 如果不定义泛型类型, 默认是Object.
  • 如果泛型定义为Object没有发挥泛型的优势.
  • 编译器自动推断, 可以省略List<Number> list = new ArrayList<>;

泛型接口

  • 可以在接口中定义泛型类型, 实现此类接口必须实现正确的泛型类型

编写泛型类

  • 一般来说, 泛型类用在集合类中, 我们很少编写泛型类
  • 就是这个类, 可以针对某个特定的类型, 成为一个专门的类
  • 在类后面定义一个<T>, 然后在这个类里面就有了这么一种类型T, 任意使用
  • 泛型类型<T>不能用于静态方法
  • 静态方法, 可以单独改写为"泛型"方法.
  • 静态方法的泛型, 和实例类型的泛型不是一个, 应该清楚的区分开
public class Pair<T> {
  private T first;
  private T last;
  public Pair(T first, T last) {
    this.first = first;
    this.last = last;
  }
  public T getFirst() {
    return first;
  }
  public T getLast() {
    return last;
  }
  
  // 对于静态方法使用<T>
  public static<K> Pair<K> create(K first, K last) {
    return new Pair<K>(first, last);
  }
}

多个反省类型

  • 泛型可以定义多种类型.<T, K>
  • 使用的使用, 分别指出两种类型即可Pair<String, Integer>

擦拭法

  • 泛型是一种模板技术, 不同语言实现方式不同, java是擦拭法
  • 擦拭法: 虚拟机对泛型其实一无所知, 所有的工作都是编译器做的
  • Java的泛型是由编译器在编译时实行的, 编译器内部永远吧所有类型T视为Object处理.
  • 在需要转型的时候, 编译器会根据T的类型, 自动为我们实行安全地强制类型.

java发型的局限

  • <T>不能是基本类型, 例如int, 因为实际类型是Object.
  • 无法取得带泛型的Class: <T>Object, 无论T的类型是什么, getClass()返回同一个Class实例
  • 无法判断带泛型的Class: 不存在Pair<String>.class, 只存在唯一的Pair.class
  • 不能实例化T类型: 只能通过反射传入实现.

不恰当的覆写方法

  • 定义的equals(T t)不会覆写成功, 因为摩擦成equals(Object t), 这就是一个覆写方法了

泛型继承

class IntPair extends Pair<Integer> {
  public IntPair(Integer first, Integer last) {
    super(first, last);
  }
}
public class Pair<T> {
  private T first;
  private T last;
  public Pair(T first, T last) {
    this.first = first;
    this.last = last;
  }
}
// 可以直接使用: Integer ip = new IntPair(1, 2)
// 获取继承泛型类型方法
    Class<IntPair> clazz = IntPair.class;
    Type t = clazz.getGenericSuperclass();
    if (t instanceof ParameterizedType) {
      ParameterizedType pt = (ParameterizedType) t;
      Type[] types = pt.getActualTypeArguments();
      Type firstType = types[0];
      Class<?> typeClass = (Class<?>) firstType;
      System.out.println(typeClass);
    }
  • 因为java中引入了泛型, 所以只用Class来标识泛型不够用. 所以: java类型的体系如下:
    ┌────┐
    │Type│
    └────┘


    ┌────────────┬────────┴─────────┬───────────────┐
    │ │ │ │
    ┌─────┐┌─────────────────┐┌────────────────┐┌────────────┐
    │Class││ParameterizedType││GenericArrayType││WildcardType│
    └─────┘└─────────────────┘└────────────────┘└────────────┘

extends通配符

  • Pair<Integer>类型, 符合参数Pair<? extends Number>: 上界通配符, 类型T上界限定在Number
  • 方法参数签名setFirst(? extends Number)无法传递任何Number类型给setFirst(? extends Number)
  • 唯一的例外可以传入null, // ok, 但是后面排除NullPointerException

extends通配符的作用

  • 可以通过get获取一个指定类型的返回结果
  • 无法通过set进行设置和修改类型
  • 使用extends通配符表示可以读, 不能写.

使用extends限定T类型

  • 在定义泛型类型Pair<T>的时候, 可以使用extends通配符来限定T的类型

super通配符

  • 允许set(? super Integer)传入Integer的引用
  • 不允许调用get()方法获得Integer的引用
  • 使用<? super Integer>通配符作为方法参数, 便是方法内部代码对于参数只能写, 不能读

对比extendssuper通配符

  • <? extends T>允许调用方法T get()获取T的引用, 但不允许调用方法set(T)传入T的引用(传入null除外)
  • <? super T>允许调用方法set(T)传入T的引用, 但不允许调用方法T get()获取T的引用(获取Object除外)
class Collections {
  public static <T> void copy(List<? super T> dest, List<? extends T> src) {
    //  编辑器可以避免: 意外读取dest, 意外操作src. 安全操作数组
    for (int i = 0; i < src.size(); i++) {
      T t = src.get(i);
      dest.add(t);
    }
  }
}

PECS原则

  • Producer Extends Consumer Super
  • 如果需要返回T, 它是生产者(Producer), 需要使用extends通配符
  • 如果需要写入T, 它是消费者(Consumer), 需要使用super通配符
  • 需要返回Tsrc是生产者, 声明为<? extends T>
  • 需要写入Tdest是消费者, 声明为<? super T>

无限定通配符

  • 无限定通配符(Unbounded Wildcard Type), 即只定义一个?
  • void sample(Pair<?> p) {}
  • <?>具体作用:
    • 不允许调用set<T>方法传入引用T(null除外)
    • 不允许调用T get<>方法并获取T引用(只能获取Object引用)
    • 换句话说: 既不能读, 也不能写, 只能做一些null判断
  • 只做一些null判断, 大多数情况下, 引入泛型参数<T>消除<?>通配符
  • Pair<?>是所有的Pair<T>的超类
Pair<Integer> p = new Pair<>(123, 456);
Pair<?> p2 = p; // 安全的向上转型

泛型和反射

  • Class<T>就是泛型
  • 调用ClassgetSuperclass()方法返回的Class类型是Class<? super T>
  • 构造方法Constructor<T>也是泛型
Class<Integer> clazz = Integer.class;
Constructor<Integer> cons = clazz.getConstructor(int.class);
Integer i = cons.newInstance(123);
  • 可以声明但泛型的数组, 但不能用new操作符创建, 必须通过强制转型实现.
// Pair<String>[] ps = null; // ok
// Pair<String>[] ps = new Pair<String>[2]; // Cannot create a generic array of Pair<String>
  Pair<String>[] ps = (Pair<String> []) new Pair[2];
  • 使用泛型数组, 是有风险的, 必须扔到初始数组的引用, 直接进行强制转换
class ABC<T> {
//  T[] createArray() {
//    return new T[5];
//  }
  T[] createArray(Class<T> cls) { // 借助`Class<T>创建泛型数组`
    return (T[]) Array.newInstance(cls, 5);
  }
}

谨慎使用泛型可变参数

疑问

  • intInteger两种类型的关系和区别?
  • 谨慎使用泛型可变参数中, 为什么第二个会报错的原因没看懂.

猜你喜欢

转载自www.cnblogs.com/zhangrunhao/p/12655285.html