Java | 泛型知识知多少?

什么是泛型

泛型就是在编译阶段执行的一种模版,例如ArrayList,在代码中为用到的类创建对应的ArrayList<类型>

ArrayList<String> strList = new ArrayList<String>();

既实现了编写一次,万能匹配,又通过编译器保证了强类型语言的类型安全:这就是泛型

向上转型

  • ArrayList可以向上转型为List<T>,因为ArrayList<T>实现了List<T>接口
public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
	... 
}
  • 但是,不能把ArrayList<Integer>向上转型为ArrayList<Number>或List<Number>
// 创建ArrayList<Integer>类型:
ArrayList<Integer> integerList = new ArrayList<Integer>();
// 添加一个Integer:
integerList.add(new Integer(123));
// 向上转型为ArrayList<Number>:
ArrayList<Number> numberList = integerList;
// 添加一个Float,因为Float也是Number:
numberList.add(new Float(12.34));
// 从ArrayList<Integer>获取索引为1的元素(即添加的Float):
Integer n = integerList.get(1); // ClassCastException!

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

使用泛型

使用泛型时,把泛型参数替换为需要的class类型,例如:ArrayList<String>,ArrayList<Number>等;

可以省略编译器能自动推断出的类型,例如:

List<String> list = new ArrayList<>();

不指定泛型参数类型时,编译器会给出警告,且只能将<T>视为Object类型;

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

编写泛型

编写泛型时,需要定义泛型类型<T>;

public class ArrayList<T> implements List<T> {
    ...
}
List<String> list = new ArrayList<String>();

静态方法不能引用泛型类型<T>,必须定义其他类型(例如<K>)来实现静态泛型方法;

class demo{
	// Error: Cannot resolve symbol 'T'
	public static T show (T tmp){
	return tmp;
	}
}

泛型可以同时定义多种类型,例如Map<K, V>。

擦拭法

Java语言的泛型实现方式是擦拭法

  • 所谓擦拭法是指,虚拟机对泛型其实是一无所知的,所有工作都是编译器做的

Java使用擦拭法实现泛型

  • 编译器把类型<T>视为Object;
  • 编译器根据<T>实现安全的强制转型。

Java泛型的局限

  • 1、<T>不能是基本类型

    • 例如int,因为实际类型是Object,Object类型无法持有基本类型
  • 2、无法取得带泛型的Class

    • 因为T是Object,我们对Pair<String>和Pair<Integer>类型获取Class时,获取到的是同一个Class,也就是Pair类的Class。

⚠️ 在JVM看来,这个class文件里没有任何关于T的信息

  • 3、无法判断带泛型的类型

    • 原因同上
  • 4、不能实例化T类型

    • 要实例化T类型,必须借助额外的Class参数:
	public class Pair<T> {
    private T first;
    private T last;
    public Pair(Class<T> clazz) {
        first = clazz.newInstance();
        last = clazz.newInstance();
    }
}

⚠️ 代码借助Class参数并通过反射来实例化T类型,使用的时候,也必须传入Class

不恰当的覆写方法

  • 某些方法是继承自Object的,编译器会阻止一个实际上会变成覆写的泛型方法定义,比如equals(Object t)
  • 只需要换个方法名即可,比如same(Object t)

泛型继承

  • 子类可以获取父类的泛型类型<T>

extends通配符

使用<? extends Number>的泛型定义称之为上界通配符(Upper Bounds Wildcards),即把泛型类型T的上界限定在Number了

extends通配符的作用

  • <? extends T>
  • 允许调用get()方法获取T的引用;
  • 不允许调用set(? extends T)方法并传入任何T的引用(null除外)
  • 只能读不能写

使用extends限定T类型

  • 在编写泛型类型时,可以利用extends通配符来限定T的类型
  • <? extends T>:表示泛型类型限定为T以及T的子类

super通配符

使用<? super Integer>通配符表示

  • 允许调用set(? super Integer)方法传入Integer的引用;
  • 不允许调用get()方法获得Integer的引用
  • 只能写不能读

对比extends和super通配符

  • <? extends T>允许调用读方法T get()获取T的引用,但不允许调用写方法set(T)传入T的引用(传入null除外);
  • <? super T>允许调用写方法set(T)传入T的引用,但不允许调用读方法T get()获取T的引用(获取Object除外)。

PECS原则

  • 何时使用extends,何时使用super

    • 如果需要返回T,它是生产者(Producer),要使用extends通配符;如果需要写入T,它是消费者(Consumer),要使用super通配符。

无限定通配符

使用<?>通配符表示

  • 不允许调用set(T)方法并传入引用(null除外);
  • 不允许调用T get()方法并获取T引用(只能获取Object引用)
  • 既不能读也不能写,只能做一些null判断
  • 可以用<T>替换

<?>通配符有一个独特的特点,就是:Pair<?>是所有Pair<T>的超类

  • 可以安全地向上转型

泛型和反射

部分反射API也是泛型

  • Class<T>就是泛型
  • Constructor<T>也是泛型

泛型数组

  • 可以声明带泛型的数组,但不能用new操作符创建带泛型的数组
 Pair<String> [] ps = null; // ok
 Pair<String> [] ps = new Pair<String>[2]; // compile error!
  • 必须通过强制转型实现带泛型的数组
@SuppressWarnings("unchecked")
Pair<String> ps = (Pair<String>[]) new pair[2]
  • 创建泛型数组
public class Abc<T>{
	 // Error: 不能直接创建泛型数组[T],因为擦拭后代码变为Object[]
     T[] createArray(){
           return new T[5];
     }
	// Ok: 必须借助Class<T>来创建泛型数组
	T[] createArray(Class<T> cls) {
    	return (T[]) Array.newInstance(cls, 5);
	}
}
public class ArrayHelper {
	// 还可以利用可变参数创建泛型数组T[](谨慎使用)
	// 如果在方法内部创建了泛型数组,最好不要将它返回给外部使用。
    @SafeVarargs
    static <T> T[] asArray(T... objs) {
        return objs;
    }
}
String[] ss = ArrayHelper.asArray("a", "b", "c");
Integer[] ns = ArrayHelper.asArray(1, 2, 3);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

孟华328

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值