Java泛型探究

1,泛型的引入

先看一个关于ArrayList的使用案例

ArrayList collection = new ArrayList();
collection.add(123);
collection.add("456");
collection.add(12.3);

for(Object element : collection) {
    System.out.println((String) element);
    // java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
}

Java的泛型是在JDK5引入的新特性,它可以让编写的代码应用于不同类型的对象(ArrayList<String>、ArrayList<Integer>),大大的增加了代码的复用性;同时通过类型限定提供了编译时类型安全检测机制,将代码的检测提前到了编译阶段,避免运行时错误,增加了代码的健壮性。

2,泛型的分类

  • 泛型接口,格式是:class IntefaceNme<T1, T2, ..., Tn> [extends ...]
public interface List<E> extends Collection<E>
  • 泛型类,格式是:class ClazzNme<T1, T2, ..., Tn> [extends ...] [implements ...]
public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable
  • 泛型方法,格式是:[public] [static] <T> 返回值类型 方法名(T 参数列表)
public static <T> void sort(List<T> list, Comparator<? super T> c) {
    list.sort(c);
}

泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数,常用的类型参数有:

  • K: 键
  • V: 值
  • N: 数字
  • T: 类型
  • E: 元素
  • S, U, V, R等,泛型声明的多个类型

3,泛型的限定、继承

1,某些特定场合下,需要对的泛型类所操作的数据类型进行限定,要求它必须继承某个类或实现某些接口,如下面这个泛型方法就要求引入的类型必须实现Comparable接口:

public static <T extends Comparable> T getMin(T... array) {        
	if (null == array || array.length <= 0) {
	    return null;
	}
	T min = array[0];
	
	for(int i = 1; i < array.length; i++) {
	    if (min.compareTo(array[i]) > 0) {
	        min = array[i];
	    }
	}
	return min;        
}
泛型限定语法:extends限定可以有多个接口但只能有一个类,且类必须排第一位,用”&“符号连接,多个引入类型使用“,”分割,如:<T extends File & Cloneable, U extends Serializable>要求T必须同时实现File和Cloneable接口,U必须实现Serializable接口

2,Basket<T>与Basket<S>没有任何关系,无论S和T有着怎样的关系

Basket<Apple> appleBasket = new Basket<>();
Basket<Fruit> fruitBasket = appleBasket;
// compile error: Incompatible types, require Basket<Fruit> found Basket<Apple>

3,泛型类或者接口的继承与实现要么是继续声明泛型类型,要么指明实际类型参数

public interface Collection<E> extends Iterable<E>

public class ArrayList<E> extends AbstractList<E>
	implements List<E>, RandomAccess, Cloneable, java.io.Serializable

class MyStringList extends ArrayList<String>

4,通配符类型与PECS原则

1,通配符"?"代表类型未知,对于List<?>中的元素只能用Object来引用且无法添加元素,它的使用场景一般是泛型类中的方法不依赖于类型参数的时候,比如list.size(), 遍历集合等
2,PECS(Producer Extends, Consumer Super)原则

  • 要从泛型类读取类型T的数据,并且不能写入,可以使用?extends T上限限定符,此时泛型类可以视作生产者,它能接收的类型是T或T的子类,从中获取的数据都可向上转型为T
  • 要向泛型类写入类型T的数据,并且不需要读取(读取的数据也只能由Object类型引用),可以使用?super T下限界定符,此时泛型类型可视作消费者,它能接收的类型是T或T的超类,只要是能向上转型为T的数据都能往里塞
  • 如果既想写入又想读出,那就不要用通配符
class Fruit {}

class Apple extends Fruit {}

class GreenApple extends Apple {}

class Orange extends Fruit {}

class StringList extends ArrayList<String> {}

public class Basket<T> {
    private List<T> basketHolder = new ArrayList<>();
    
    public void set(T item) {
        basketHolder.add(item);
    }

    public T get() {
        if (!basketHolder.isEmpty()) {
            return basketHolder.get(0);
        }
        return null;
    }
    
    public static void main(String[] args) {

        Basket<? extends Apple> appleBasket1 = new Basket<Apple>();
        /* ? extends上限界定符不可写入
        appleBasket1.set(new Fruit());
        appleBasket1.set(new Apple());
        appleBasket1.set(new GreenApple());
        appleBasket1.set(new Orange());*/
        Apple apple = appleBasket1.get();
		
		Basket<? super Apple> appleBasket2 = new Basket<Apple>();
        // 下限界定符只能写入Apple及其子类
        //appleBasket2.set(new Fruit());
        appleBasket2.set(new Apple());
        appleBasket2.set(new GreenApple());
        //appleBasket2.set(new Orange());
        Object object = appleBasket2.get();
    }
}

5,类型擦除

  • 虚拟机其实是不支持泛型,在JVM运行时根本就不存在泛型信息,编译器会把泛型类型中所有的类型参数替换为它们的上(下)限,如果没有对类型参数做出限制,那么就替换为Object类型,这样就不需要产生新的类型,所有的泛型类型最终都是一种原始类型,所以Java实现的是一种伪泛型机制。
  • 擦除泛型变量后,为保证类型的安全性,编译器会自动完成类型转换。
  • 当编译一个扩展泛型类的类,或一个实现了泛型接口的接口时,编译器会通过自动桥方法完成多态方法的调用

6,泛型约束

  • 运行时类型查询只适用于原始类型
  • 基本类型是不能够作为泛型类型的,需要使用它们对应的包装类。
// error
Map<int,char> wrongMap = new HashMap<>();
// OK
Map<Integer,Character> okMap= new HashMap<>();
  • 泛型类型可以理解为一个抽象类型,只是代表了类型的抽象,因此我们不能直接实例化它
public void test(T param){
    // error
    param = new T();
}
  • 无法进行 instanceof 判断(无界通配符 <?>除外)
public static <E> void test(List<E> list) {
   // error
    if (list instanceof ArrayList<Integer>) {
    }
    // ok
    if (list instanceof ArrayList<?>) {
    }
}
  • 不能创建参数化类型的数组,主要是为了避免数组里出现类型不一致的元素

  • 泛型类的静态上下文中类型变量无效,类( Class )的类型参数( Type Parameters )只存在于类的实例的方法域和成员变量域。换句话说,必须等到实例化泛型类才知道那个类型参数“ T ”是什么鬼。

  • 不能直接或者间接扩展 Throwable.因为异常处理是由JVM在运行时刻来进行的,由于类型信息被擦除,JVM是无法区分两个异常类型MyException和MyException的,对于JVM来说,它们都是 MyException类型的,也就无法执行与异常对应的catch语句。

//  不能间接地扩展 Throwable
class IndirectException<T> extends Exception {}

//  不能直接地扩展 Throwable
class DirectException<T> extends Throwable {}
  • 类型擦除后引发方法冲突
// 由于泛型擦除的原因,以下方法的将不会视为重载且无法编译
public void set(Set<String> strSet) { }
public void set(Set<Integer> intSet) { }

7,协变与逆变

类型变换( type variance)形式化定义:

  • A、B代表两种类型,f(.)表示类型转换,<=代表继承关系,如A<=B表示A继承于B
  • f(.)是协变(covariant)的,如果A<=B,有f(A)<=f(B)
  • f(.)是逆变(contravariant)的,如果A<=B,有f(B)<=f(A)
  • f(.)是不变(invariant)的,上述两种情况都不成立,即f(A)与f(B)没有关系
  • f(.)是双变(bivariant)的,如果A<=B,有f(A)<=f(B)和f(B)<=f(A)同时成立
    举类:
  • Java数组是协变的
Fruit[] fruits = new Apple[3];
fruits[0] = new Apple();
// compile ok, runtime error: ArrayStoreException
fruits[1] = new Fruit();
// ok, GreenApple is Apple's subclass
fruits[2] = new GreenApple(); 
  • 不带通配符的泛型是不变的
// compile error
ArrayList<Fruit> fruits = new ArrayList<Apple>();
  • 泛型可以采用通配符来支持协变和逆变(PECS)
// 协变
ArrayList<? extends Fruit> fruits1 = new ArrayList<Apple>();
// 逆变
ArrayList<? super GreenApple> fruits2 = new ArrayList<Apple>();
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值