泛型
之前对泛型一直没有系统的学习,直到学习函数式编程时才发现还是有很多空缺,现在就系统的学习一下。
1. 概述
泛型在Java中还是比较重要的,提供了编译时的类型检查。泛型的解释是参数化的类型。这还是比较好理解的,就是将类型作为一个参数提供给方法或者类,就和方法的形参是类似的,不过写法不一样。在我们调用方法的时候才会确定具体需要的类型。泛型可以用在类,接口,方法上,被称为泛型类,泛型接口,泛型方法。
2. 对比
可能说的还是有点抽象了,举个大家常用的例子对比一下就知道了,我们现在想要存一组数字
List list = new ArrayList();
list.add(100); //int型数据
list.add("20"); //String类型数据
for (int i = 0; i < list.size(); i++){
Integer item = (Integer)list.get(i); //这一步会在运行时报类型转换异常
System.out.println(item);
}
List<Integer> list = new ArrayList<>();
list.add(100);
list.add("20"); //这一步将在编译时报错
从上面这两个代码中我们就能看出不使用泛型的话,编译时期是无法发现类型错误的,而运行时会可能会产生类型转换异常,这是因为在向list中存入数据的时候没有对存入数据的类型进行约束。而使用泛型的话将强制我们传入的数据类型与之匹配,否则就无法通过编译。这里强调一点泛型仅在编译时有效,运行时无效
3. 泛型通配符
在介绍泛型的使用之前,需要先了解几个通配符:
- T:type,表示一个Java类型
- K:key,代表键值对中的key
- V:value,代表键值对中的value
- E:element,代表集合中的element
- ?:标识不确定的Java类型
这些通配符都可以使用 super,extends来定义上界和下界,super就表示必须是某个类或它的超类,extends表示是某个类或它的子类
3.1 T 和 ?
T
代表一个Java类型(注意这里说的是一个),在同一个类型或方法中,T
不管使用多少次,都代表相同的类型。而?
则表示不确定的java类型,?
代表的类型可能不一致。并且T
是可以当作一个类型来使用的,而?
不是可以的
栗子
T t = test(); //正确✔
? u = test(); //错误❌
//param1和param2类型相同,param3和param4类型不一定相同
public <T extends Number> void test(T param1, T param2, <? extends Object> param3, <? extends Object> param4){
}
4. 使用
前面说了泛型使用有三种,泛型类,泛型接口,泛型方法。
4.1 泛型类
格式:类型名称后面加<泛型通配符>
public class Test<T>{
// 这里指定属性类型为T
private T arg;
// 构造函数参数指定为泛型
public Test(T arg){
this.arg = arg
}
}
根据在3.1节讲的,在同一个类型或方法中,T
不管使用多少次,都代表相同的类型。这个Test类中所有的T都代表同一个类型。
Test<Integer> test = new Test<Integer>("123"); //编译不通过
Test<Integer> test = new Test<Integer>(123); //编译通过
4.2 泛型接口
和泛型类大致相同,值得注意的是,实现接口时也要将泛型带上
public insterface MyMap<T>{
public T print();
public void set (T param);
}
// 必须添加泛型,否则编译不同通过
public class MyMapImpl<T> implements MyMap<T>{
}
4.3 泛型方法
泛型方法相对泛型类来说稍微难一些,泛型方法的定义是需要在权限修饰符和返回类型之间增加<泛型通配符>。public <T> void print(T param)
只是使用了泛型通配符定义参数的方法不是泛型方法,如:public void print(T param)
泛型类型中可以有泛型方法,但如果有定义的通配符相同,则以泛型方法中的通配符为准。T可以当作一个类型来使用
public class Test<T> {
// 泛型方法形参中的T和泛型类中的T不是同一个类型。
public <T> void print(List<T> params){
for(T item : params){
...
}
}
}
5. 总结
泛型给我们提供了编译时类型检查的功能,合理的运用泛型可以在很大程度上避免我们运行时抛出类型转化异常,这一点在集合类中体现最明显。泛型仅在编译时发挥作用,运行时没有泛型的概念,所以不要试图比较泛型类。如:new ArrayList<String>().getClass().equals(new ArrayList<Integer>().getClass())
测试结果总是相等的。