Java笔记06 - 泛型

java

泛型是一种"代码模板"

什么是泛型

  • 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两种类型的关系和区别?

  • 谨慎使用泛型可变参数中, 为什么第二个会报错的原因没看懂.

以上是 Java笔记06 - 泛型 的全部内容, 来源链接: utcz.com/z/394767.html

回到顶部