Java基础笔记(泛型)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_38801354/article/details/83058658

1 泛型

为什么需要泛型?举例,Java集合中有个 ArrayList 类可以看做是一个“可变长度”的数组,在 ArrayList 类中其实是在用一个 Object 数组来存储真正的元素,如果直接使用 ArrayList 存储 String 类型,在读取数据的时候,就需要强制转型了,这样一来就会很不方便而且容易出错,如果为存储 String 类型单独编写一个 ArrayList (内部使用一个 String 数组),这样的话编译器就能帮上忙了(其实都是想依靠编译器的强制检查),问题会暂时解决,但如果还需要存储 Integer 类型呢?
所以需要把 ArrayList 变成一种模板(类是实例的模板,泛型是类的模板),这样一来就实现了编写一次模板可以创建任意类型的 ArrayList
而泛型就是定义这样一个模板,这样的 类的模板 填充具体类型之后构建出来的类,就拥有具体类型了,此时编译器也能针对具体类型作检查。

类是实例的模板,泛型是类的模板

  • ArrayList<T> 是泛型
  • ArrayList<String> 是通过泛型模板出来的类
  • new ArrayList<String>() 是类的实例

注意泛型的继承关系,ArrayList<Integer> 可以向上转型为 List<Integer>,而 ArrayList<Integer>ArrayList<Number> 之间没有继承关系。
试想一下,假如 ArrayList<Integer> 可以向上转型为 ArrayList<Number>,然后这个转型后的 ArrayList<Number> 是可以接受 Float 类型的,问题就来了,这个转型后的 ArrayList<Number> 实际上还是一个 ArrayList<Integer> 对象,它不可能接受一个 Float 类型,所以在获取数据的时候,会产生 ClassCastException,例子:

// 假如 ArrayList<Integer> 可以向上转型为 ArrayList<Number>
ArrayList<Integer> integerList = new ArrayList<Integer>();
integerList.add(123);
ArrayList<Number> numberList = integerList;
numberList.add(123.123);
integerList.get(1); // 抛出 ClassCastException,编译器为了避免这样的错误,根本不允许这样转换

因此 编译器为了避免这样的错误,根本不允许这样转换,即使二者比较了 ClassisAssignableFrom 也返回了 true

个人又能否这样认为泛型的继承关系?

因为 ArrayList<T>List<T>类的模板T 是通配符,本来 ArrayList 就是 List 的子类,ArrayList<T> 可以向上转型为 List<T>,所以 ArrayList<Integer> 也就可以向上转型为 List<Integer>
ArrayList<Integer>ArrayList<Number> 二者只是通过 类的模板 创建的 具体的类型 罢了,是互不相干的类型,它们并没有继承关系,也就无法向上转型了。

1.1 使用泛型

如使用 List 不指定泛型参数类型的时候,即 T 作为 Object 使用,List 的API会变为 Object 类型,当定义了泛型如 List<String> 时,List 的API会变为强类型。
在使用泛型时,可以省略编译器能自动推断出的类型,如:

List<Number> list = new ArrayList<Number>();
// 省略编译器能自动推断出的类型
List<Number> list2 = new ArrayList<>();

1.2 编写泛型类

记得之前有人跟我说过你接触的java泛型类,十之有十都与集合类有关,很少会需要编写泛型类。
其实编写泛型只需根据正常地方式编写一个类,然后把特定的类型扣掉,转换成 <T> 即可,而在类名之后申明 <T>,如:

public class One<T> {
	public T first;
}

定义泛型的时候可以同时定义多个类型,在编写泛型类的时候,泛型类型 <T> 无法用于静态方法或静态字段,它会导致编译错误,而必须另外定义其他参数类型如 <K> 来实现,如:

public static <K> One<K> create(K first) {....}

1.3 泛型的实现方式

Java的泛型实现方式是 擦拭
在Java泛型代码编译的时候,编译器实际是将泛型参数类型 <T> 统一视为 Object 类型,然后在需要的时候,会根据 实现了安全的强制转型,如:

public class One<T> {
	public T first;
}

转换为:

public class One {
	public Object first;
}

换句话说JVM虚拟机其实对泛型一无所知,所有的工作都是编译器做的,例如下面两段代码,第一段是我们书写的实际代码,第二段是虚拟机实际上处理的代码:
第一段

public static void main(String[] args) {
	One<String> one = new One<>("i am first");
	String first = one.first;
}

第二段

public static void main(String[] args) {
	One one = new One("i am first"); // 编译器将 <T> 视为 Object 类型
	String first = (String) one.first; // 根据 <T> 实现了安全的强制转型
}

所以Java的泛型实际上是由编译器在编译的时候进行的,编译器内部永远把参数类型 <T> 视为 Object 类型处理,因此这就有局限了,如:

  • 参数类型 <T> 不能是基本类型
  • 无法获取带泛型的 Class,如 One<String>.class,因为它们都是返回同一个 Class 值。比如获取 One<String> 实例的 Class 和获取 One<Integer> 实例的 Class 一样,都是返回同一个 One.class,因此也无法判断比较泛型的 Class,比如使用 x instanceof One<String> 或使用 isAssignableFrom
  • 不能直接实例化 T 类型,因为擦拭后实际上是 new Object(),要实例化 T 类型可以使用到 Class<T>
  • 还要注意由擦拭带来的方法重名问题,如使用 <T> 作为参数写了一个 public boolean equals(T obj) 方法,由于擦拭参数类型 T 会被是为 Object,这就与源生的 equals 方法发生重复定义了。

1.4 泛型类的继承

平时定义的类可以继承泛型类,如:

public class IntOne extends One<Integer> {
}

这时候编译器就需要将父类的参数类型 TInteger 保存到子类的 Class 中,因此就可以使用子类的 Class 获取到父类的泛型类型,流程如下:
如果一个子类可以通过 getGenericSuperclass 方法得到 ParameterizedType 实例,就可以通过得到的 ParameterizedType 实例使用 getActualTypeArguments 方法获取 Type 数组,通过强制转换为 Class,就可以得知该父类的泛型,代码如下:

public static void main(String[] args) {
    // 获取子类的 class
    Class<IntOne> cls = IntOne.class;
    
    // 这个 Type 的实际类型是 ParameterizedType (参数类型)
    Type t = cls.getGenericSuperclass();
    
    // 测试该子类通过 getGenericSuperclass 是否能得到 ParameterizedType 数值,有则强制转换
    if (t instanceof ParameterizedType) {
        ParameterizedType pType = (ParameterizedType) t;
        // 通过使用它的 getActualTypeArguments,便可以返回 Type 类型的数组,其中包含的就是父类中的泛型数组
        Type[] types = pType.getActualTypeArguments();
        // 这里实例只定义一个 Integer 参数类型,所以数组长度只为 1
        Type firstType = types[0];
        // 转化为 Class,?号是通配符
        Class<?> typeClass = (Class<?>) firstType;
        // 输出 class java.lang.Integer
        System.out.println(typeClass);
    }
}

JDK中定义的 Type 接口,它的实现类有 ClassParameterizedType(泛型类),GenericArrayType(泛型数组) 和 WildCardType(通配符)

1.5 extends 通配符

One<Integer> 不是 One<Number> 的子类,所以在一个方法中想要兼容传入二者,就需要使用到 <? extends Number> 了,它可以让方法接受所有 泛型类型NumberNumber 子类的 One 类,如:

// 泛型类
class One<T> {
	private T first;
	public T getFirst() {
		return first;
	}
	public void setFirst(T first) {
		this.first = first;
	}
}
// 使用 <? extends Number> 通配符,此时只能传入泛型参数类型为Number或子类的One类型了
class OneHelper {
	static void test(One<? extends Number> one) {
		// 此时编译器不可预测实际类型究竟是Integer类型还是其他,但可以安全地将数值赋予给Number类型的变量
		Number float = new Float(1.2f);
		Number number = one.getFirst();
		// one.setFirst(float);
	}
}

查看上面代码,在 test 方法中 onegetFirst 方法签名可以看作为:

extends Number getFirst();

此时编译器不可预测实际类型究竟是 Integer 还是其他,但可以肯定的是它一定是 Number 的子类,所以能够安全地将数值赋予给 Number 类型变量
而在 test 方法中使用 setFirst 方法编译器会报错,其中 setFirst 签名可以看作为:

void setFirst(extends Number)

假设该方法运行不会报错,那么当传入一个实际类型为 One<Integer>,而在方法内部创建了一个 Number 实例但实际类型是 Float,很显然 One<Integer> 没办法接受一个实际类型是 FloatNumber
理解以上的概念要结合什么时候能向上转型和什么时候能向下转型的知识。

因此在使用 <? extends xxx> 统配符作为方法参数的时候,方法中只能允许调用 get 方法获取 xxx 的引用,而不能调用 set 方法传入 xxx 的引用,唯一例外就是可以调用 set 时传入的是 null

1.6 super 通配符

针对 extends 通配符作为作为方法参数的时候不能传入引用,这里可以使用 super 通配符。比如上面情况使用 <? super Integer> 通配符可以使方法接受所有 泛型类型Integer 类型或其父类的 One 类。
此时的 setFirst 方法的签名可以看作:

void setFirst(super Integer)

此时就可以安全的传入 Integer 变量,因为编译器已知 Integer 已经是最底层的子类,即使传入的是不同泛型类型的 One 类,Integer 类也是它们的子类,也就可以安全的转换了。
然而此时也就无法使用 getFirst 方法了,此时它的方法签名可以看作:

super Integer getFirst();

因为编译器此时无法得知将要获取的数值的类型会是什么,这个类型除 Integer 之外都是 Integer 的父类,也就无法安全的转换为合适的类型(除了 Object)。

因此在使用 <? super xxx> 统配符作为方法参数的时候,方法中不允许调用 get 方法获取 xxx 的引用,唯一例外就是将该值转换为 Object 类型,允许调用 set 方法传入 xxx 的引用

1.7 super 通配符与 extends 通配符

在JDK中集合的复制API就是传入了 super 通配符与 extends 通配符的方法参数,这个API为什么要使用这两个通配符定义,而不是简单地使用 <T> 呢?
这是为了实现 List<Integer> 可以复制为 List<Number>,而 List<Number> 不可以复制为 List<Integer>

1.8 泛型与反射

部分反射API是泛型:

Class<String> cls = String.class;
Class<? super String> supCls = cls.getSuperclass();

猜你喜欢

转载自blog.csdn.net/qq_38801354/article/details/83058658