1. 泛型的出现
为什么要有泛型?
- 实现不同类型的同一个方法时,泛型的出现解决了重载问题,通过泛型只需要复用同一个方法即可
- 泛型的出现解决了元素存储的安全性问题 如:任何元素都可以加入集合中会导致类型不安全
- 泛型的出现解决了获取元素时需要强制转换的问题 如:从集合中取出Object类型元素需要强转为所需类型的对象
泛型主要优点是能够在编译时而不是运行时检测错误
即Java泛型可以保证如果程序在编译时没有发出警告,运行时就不会产生ClassCastException
异常。
如何理解Java中的泛型是伪泛型?
Java泛型是在JDK1.5后引入的,为了兼容之前的版本,Java泛型采用伪泛型策略,即Java在语法上支持泛型,但是在编译阶段会进行所谓的 “类型擦除”(Type Erasure),将所有的泛型表示(尖括号中的内容)都替换为具体的类型(其对应的原生态类型),就像完全没有泛型一样。
泛型的声明与实例化
interface List<T>
中的T
表示类型,可以使用任意字母- 在类名后面指定类型参数的值 如:
List<String> list = new ArrayList<String>();
- 注意:
T
只能是类,不能使用基本数据类型填充,但可以使用包装类填充
泛型在继承上的体现
- 如果B是A的一个子类型,而G是具体泛型的声明的类,
G<B>
并不是G<A>
的子类型 - 如:
String
是Object
的子类,但是List<String>
并不是List<Object>
的子类。
2. 自定义泛型结构
自定义泛型结构 ①泛型类 ②泛型接口 ③泛型方法
注意事项:
- 泛型类可以有多个参数,如
Map<K, V>
- 泛型要么一开始就不使用,要么使用就要一直用
- JDK1.7后泛型可简化为
List<String> list = new ArrayList<>();
- 静态方法中不能使用泛型作为参数类型 如:
public static void add(T t)
是不允许的- 异常类不能是泛型的
- 不能使用
new E[]
,但可以使用E[] elements = (E[]) new Object[];
- 父类有泛型,子类可以选择保留泛型也可以选择指定泛型类型 (保留泛型可以是全部保留也可以是部分保留, 指定泛型类型可以是直接擦除(没有类型)也可以是指定具体类型)
第4条静态方法中不能使用类的泛型 因为T是非静态的
2.1 自定义泛型类
/**
* @author 兴趣使然的L
* @date 2022/12/11
*/
public class Node<T>{
// val的类型由T指定
private T val;
public Node(T val) {
this.val = val;
}
private T getVal() {
return val;
}
public void setVal(T val) {
this.val = val;
}
}
复制代码
注意:可以看到自定义泛型类的构造器并不是 public Node<>(){}
而是 public Node(){}
2.2 自定义泛型接口
/**
* @author 兴趣使然的L
* @date 2022/12/11
*/
interface INode<T>{
// 返回类型由T指定
public T getVal();
}
public class Node<T> implements INode<T> {
private T val;
public Node(T val){
this.val = val;
}
public void setVal(T val) {
this.val = val;
}
@Override
public T getVal() {
return null;
}
}
复制代码
2.3 自定义泛型方法
语法: [访问权限] <泛型> 返回类型 方法名([泛型标识 参数名称]) 抛出的异常
/**
* 自定义泛型方法
* @param clazz 泛型对象
* @param <E> 声明一个泛型为E的泛型方法
* @return 泛型对象声明
*/
public <E> E getInstance(Class<E> clazz) throws InstantiationException, IllegalAccessException {
E e = clazz.newInstance();
return e;
}
复制代码
3. 通配符的区别
常用的通配符有
T
,E
,K
,V
任意字母都可以作为通配符,只是上述的这些通配符更见名知意一点
- T (type) 表示具体的一个java类型
- K V (key value) 分别代表java键值中的Key Value
- E (element) 代表Element
无界通配符
?
表示不确定的java类型通过
List<?> list = new ArrayList<>();
理解?
通配符
- 读取
List<?>
的对象list的元素永远是安全的,因为不管list的真实类型是什么都包含Object
- 无法写入list中的元素,会报错,因为不知道元素类型不能向其添加对象
- 但是可以写入null,因为null是所有类型的成员
通配符使用注意点:
通配符
?
使用场景对于不确定或不关心实际操作的类型,可以使用无界通配符
?
表示无限制类型,或者使用泛型上界限制使用一些类型,如下面的query1
方法
class Father {
String name;
public Father(String name) {
this.name = name;
}
}
class Son extends Father{
String name;
public Son(String name) {
super(name);
}
}
public class Main {
public static void main(String[] args) {
List<Son> sonList = new ArrayList<>();
query1(sonList);
query2(sonList);
}
public static void query1 (List<? extends Father> fathers) {
for (Father father : fathers) {
System.out.println(father.name);
}
}
public static void query2 (List<Father> fathers){
for (Father father : fathers) {
System.out.println(father.name);
}
}
}
复制代码
对于使用query2会导致报错,因为类型限制只能使用Father
3.1 上界/下界通配符
上界通配符
<? extends E>
:使用时指定的类型必须是继承E或E自身(即只允许泛型是E及E的派生类)在类型参数中使用 extends 表示这个泛型中的参数必须是 E 或者 E 的子类,如果传入的类型不是E或E的子类,编译不成功
上界通配符 <? extends E>
的特点:不能往容器中存东西,只能(有限制的)往外取东西
/**
* @author 兴趣使然的L
* @date 2022/12/12
*/
class Father { }
class Son extends Father{ }
public class Main {
public static void main(String[] args) {
List<? extends Father> list = new ArrayList<>();
list.add(new Son()); // 报错
list.add(new Father()); // 报错
Object o = list.get(0);
Father f = list.get(0);
Son s = list.get(0); // 报错
}
}
复制代码
可以看到上界通配符对于存任何数据都会报错, 但是如果是存null的话就不会报错
对取数据也有要求,因为上界通配符的限制是E及E的派生类(子类),所以容器里自然就是E及E的派生类的对象,取的自然不能使用E以下的派生类接收,接收数据只能是E及E的基类(父类)接收
下界通配符
< ? super E>
:用super
进行声明,表示参数化的类型可能是所指定的类型,或者是此类型的父类型,直至Object类
下界通配符 < ? super E>
的特点:不影响存数据,但取数据只能使用Object接收
/**
* @author 兴趣使然的L
* @date 2022/12/12
*/
class Father { }
class Son extends Father{ }
public class Main {
public static void main(String[] args) {
List<? super Father> list = new ArrayList<>();
list.add(new Son());
list.add(new Father());
list.add(new Object()); // 报错
list.add(null);
Object o = list.get(0);
Father f = list.get(0); // 报错
Son s = list.get(0); // 报错
}
}
复制代码
分析: 下界通配符在容器中存的都是E及E的基类(父类),取出来的数据自然也都是E及E的基类,具体并不清楚是哪个类,所以只能使用Object接收
PECS原则
- 频繁往外读取数据的适合:
上界通配符<? extends T>
- 频繁往里插入数据的适合:
下界通配符<? super T>
3.2 ?
与 T
的区别
T
是一个 确定 的类型,通常用于泛型类和泛型方法的定义,?
是一个 不确定 的类型,通常用于泛型方法的调用代码和形参,不能用于定义类和泛型方法。
通过
T
来确保泛型参数的一致性
使用多重限定:
T
可以 而?
不可以
使用超类限定:
?
可以 而T
不可以