Java 泛型

271 阅读5分钟

Java的泛型的好处,一是提高了代码的复用性和通用性,二是为类型增加一定的约束条件,可以由编译器检查类型,自动和隐式的转换类型。了解泛型要了解一下几个概念。

一、泛型通配符

常见的通配符T、E、K、V、?等,这些通配符本质上,没有任何区别,26个大写字母也可以拿来直接用,都是代表一个类型,只是开发上面,习惯给这些通配符赋予一定的含义。

T:一般用来表示一个Java的类型,比如说String、Integer,或者某个类,Fruit,Apple等
E:一般表示Element
K,V:KV一般搭配使用,表示一个键值对组合
?:表示不确定的Java类型

二、泛型方法和泛型类、泛型接口

1、泛型类和泛型接口

//此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型
//在实例化泛型类时,必须指定T的具体类型
public class Generic<T>{ 
    private T key;
    public Generic(T key) { 
        this.key = key;
    }
    public T getKey(){ 
        return key;
    }
}
// 实例化的时候,可以直接指定类型
Generic<Integer> genericInteger = new Generic<Integer>(123456);

// 泛型接口
public interface Generator<T> {
    public T method();
}
// 不指定类型的实现方式,在实例化的时候,指定类型
class GeneratorImpl<T> implements Generator<T>{
    @Override
    public T method() {
        return null;
    }
}
// 指定类型的实现方式
class GeneratorImpl implements Generator<String>{
    @Override
    public String method() {
        return "hello";
    }
}


2、泛型方法

静态类型的参数上面是不可以加泛型的,主要原因是因为类型擦除导致,类型擦除会在下面讲到,所以不能使用泛型,但是静态方法是可以使用的。

public static T params; // 不允许这样操作,非常不允许,会抛异常

public <T> T showTime(T t){
	return t;
}
public static <T> T showTime(T t){
	return t;
}
public static <T> T showTime<Class<T> t>{
	return t.newInstance();
}

三、类型擦除

Java的泛型是伪泛型,在Java编译期间,所有的泛型信息,都会被擦除,在生成的字节码中,不会有任何泛型的信息,只会保留最原始的类
型,这个过程就叫做泛型擦除。在使用过程中,会再加上泛型的信息,将其转换回来。
如何测试泛型擦除?
1、查看Java编译后的字节码,看看定义的泛型类型是否存在。
2、通过一个代码的小例子,来测试一下泛型擦除。
例1:用ArrayList来判断
ArrayList<String> list1 = new ArrayList<String>();
ArrayList<Object> list2 = new ArrayList<Object>();
list1.getClass == list2.getClass() //true
例2:使用反射判断
ArrayList<String> list1 = new ArrayList<String>();
list1.add(“123"); // 只能写String类型,其他类型,都会抛异常
list1.getClass().getMethod(“add”,Object.class).invoke(list1,1234567); // 没问题,通过反射加进去的数据,
类型在编译之后,已经被擦除了。

四、PECS原则

在解释上界通配符和下界通配符之前,先说一个原则,就是PECS原则

Producer Extends: 如果将存储容器当做一个生产者,我们只是从这个容器里面不断地往外拿数据,那需要使用Extends
只读不可写时,使用List<? extends Fruit>:Producer
Consumer Super:如果将容器当做一个消费者,我们把数据,存储到这个容器里面,那么使用Super
只写不可读时,使用List<? super Apple>:Consumer

五、上界通配符super和下界通配符extends

直接以一个例子解释这两个通配符:

在List<? extends Fruit>的泛型集合中,对于元素的类型,编译器只能知道元素是继承自Fruit,具体是Fruit的哪个子类,这是无法知道的,所以向一个无法知道具体类型的泛型集合中插入元素是不能通过编译的.但是,由于知道元素是继承自Fruit,所以从这个泛型集合中取Fruit类型的元素是可以的.

解释一下List<? extends Fruit> 的意思:extends是一个上界通配符,List<? extends Fruit> 表示 可能是 List<Apple>,
也可能是List<Banana>,还有可能是List<Orange>,或者 List<Fruit> 等等,所以不管你插入什么水果,都有可能不能跟容器的类型匹配,所以不能插入数据;但是取出数据就没有影响了,因为所有的类型都是继承自Fruit这个类型,所以可以从这个泛型集合里面取出数据,符合PE原则List<? super Apple>的泛型集合中,元素的类型是Apple的父类,但无法知道是哪个具体的父类,因此读取元素时无法确定以哪个父类进行读取.插入元素时,可以插入Apple与Apple的子类,因为这个集合中的元素都是Apple的父类.

解释一下:List<? super Apple>:表示这个集合的类型,是Apple或者Apple的父类,那就有可能是List<Apple>,List<Fruit>,List<Food>,或者是List<Object>等,因此如果是Apple或者Apple的子类,都可以往这个里面放数据,但是拿出来的时候就有问题了,因为不知道拿哪一个父类,类型擦除之后,只能拿Object,所以super只能写不能读,符合CS原则


像这种通配符有什么用呢,我知道一点就是:比如说你要开放一个队列,那么生产者只能往里面写数据,消费者只能从里面拿数据,不能双工通
信,那这个时候,我觉得这个就有用,生产者那边,你可以开放出super这个,那么生产者只能往list里面写数据,消费者你可以在接口上面用
extends,这样的话,消费者就只能从里面拿数据,不能回写数据,而且,也可以做一些类型的限制

六、小结

其实泛型一开始的时候,真的挺难理解的,尤其是这个extendssuper,为啥一个只能拿,一个只能写,现在基本上就算是明白了,但是使用
用途这块,还需要继续去琢磨琢磨。