-
什么是泛型?
- 泛型是JDK1.5中引入的一种"参数化类型"特性.
- 泛型可以限制参数类型,不需要强制类型转换.
- 泛型会在编译器检测类型是否匹配,避免了运行时类型不一致引起的"ClassCastException".
-
泛型的简单使用
-
不使用泛型可以添加任何类型
ArrayList list = new ArrayList();
list.add("hello");
list.add(18);
list.add('u');
-
不使用泛型获取数据会转为Object,使用的时候需要强转才可以
for (Object o : list) {
if (o instanceof String) {
String item = (String) o;
}else if(o instanceof Integer){
Integer item = (Integer) o;
}else if ...
...
}
-
使用泛型添加数据必须与定义的泛型统一
//定义泛型为String类型
ArrayList<String> list = new ArrayList<>();
list.add("hello");
list.add(18); //报错 类型不匹配
list.add('u'); //报错 类型不匹配
-
使用泛型获取数据会自动转为泛型类型,无需强转
for (String s : list) {
System.out.println(s);
}
-
泛型的定义
- 学习泛型定义之前,先学习一下泛型的格式
泛型由"<泛型类型>"组成
泛型类型可以是任意引用类型或自定义标识
********************************
引用类型: <String> 或 <Object>
********************************
自定义标识符是可以随便写的
比如: <T> <SB> <TMD> <吃了吗>
********************************
但是通常我们不建议这样写,推荐常用的几个
E: 元素 (Element)
K: 关键字 (Key)
N: 数字 (Number)
T: 类型 (Type)
V: 值 (Value)
-
泛型类
public class Generic<String>{
private String item;
}
或者
public class Generic<T>{
private T item;
}
-
泛型接口
public interface Generic<String>{
String getItem();
}
或者
public interface Generic<T>{
T getItem();
}
-
泛型方法
//情况1 类没有定义泛型,方法必须定义泛型才可以使用
public class GenericTest {
public <T> T getItem(T item) {
return item;
}
}
注意看上下的区别 <T>
//情况2 类已经定义泛型,方法无需定义泛型就可以使用
public class GenericTest<T> {
public T getItem(T item) {
return item;
}
}
-
泛型实战
- 定义一个简单的类,一个成员变量和get set方法
public class TestData<T> {
private T item;
private void setItem(T item) {
this.item = item;
}
private T getItem() {
return item;
}
}
-
使用
//泛型传入String
public static void main(String[] args) {
TestData<String> stringData = new TestData<>();
stringData.setItem("我是String类型");
String item = stringData.getItem();
}
或者
//泛型传入Integer
public static void main(String[] args) {
TestData<Integer> integerData = new TestData<>();
integerData.setItem(18);
Integer item = integerData.getItem();
}
-
实战总结:
- 泛型限定自定义标识符为T,可以传入任意类型
- 类的内部会自动将T转为传入的类型(这里不考虑泛型擦除的情况)
- 假入传入的是<String>
//正常代码
public class TestData<T> {
private T item;
public void setItem(T item) {
this.item = item;
}
public T getItem() {
return item;
}
}
//转换后的代码
public class TestData<String> {
private String item;
public void setItem(String item) {
this.item = item;
}
public String getItem() {
return item;
}
}
-
无界通配符
无界通配符 <?> 通常在泛型中使用一个问号标识,可以代表任意类型,例如:
public class GenericClass {
//无界通配符泛型参数
public void setList(List<?> list) {
System.out.println("List的长度是: "+list.size());
}
}
//调用时可以传入任意类型泛型参数
ArrayList<Integer> integersList = new ArrayList<>();
ArrayList<Number> numbersList = new ArrayList<>();
GenericClass genericClass = new GenericClass();
genericClass.setList(numbersList); //Integer类型
genericClass.setList(integersList); //Number类型
-
<?> 和 <T> 有什么区别?
本身就是占位符可以代表任意类型,<?>也可以是任意类型,那有什么区别呢? 下面看一段代码:
代码片段1:
// <?>定义到类上会报错
public class GenericClass<?> {
//<?>定义到方法上也会报错
public <?> void setList(List<?> list){
}
}
// <T>定义到类和方法都不会报错
public class GenericClass<T> {
public <T> void setList(List<T> list) {
System.out.println("List的长度是: "+list.size());
}
}
代码片段2:
//<T>不能当做泛型参数
ArrayList<T> list = new ArrayList<>();
//<?>可以当做不确定的泛型参数
ArrayList<?> list = new ArrayList<>();
总结:
- 和< T> 都是泛型通配符,可以当做通用占位符.
- 是一个不确定类型,不能定义到类和方法上,通常用于当做形参传入.
- < T>是一个确定的类型,可以定义到类和方法上,可以用来做一些操作,例如: T t = getNum();
-
有界通配符
先来一段代码,研究一下为什么会报错
//先定义三个类 儿子 爸爸 爷爷,继承关系如下:
public class Son extends Father{}
public class Father extends Grandpa{}
public class Grandpa {}
//定义儿子和爸爸的集合
ArrayList<Son> sons = new ArrayList<>();
ArrayList<Father> fathers = new ArrayList<>();
setList(sons); //报错 纳尼??? Son不是Father的子类吗? 为什么会报错?
setList(fathers); //正常
//接收一个泛型是Father的集合
private static void setList(List<Father> list){
}
这里就涉及到了泛型中的extends了,泛型里面是不认类中的继承关系的,所以List只能接收泛型为Father的集合,就算是Father的子类也不行,如果想要泛型也实现继承关系应该怎么办呢? 这里就要用到下面的有界通配符了.
-
上界通配符 <? extends T>
现在我们把List改成List<? extends Father> 试试
ArrayList<Son> sons = new ArrayList<>();
ArrayList<Father> fathers = new ArrayList<>();
ArrayList<Grandpa> grandpas = new ArrayList<>();
setList(sons); //正常
setList(fathers); //正常
setList(grandpas); //报错, 因为只能传入Father或Father的[子类]
private static void setList(List<? extends Father> list){
}
-
下界通配符 <? super T>
现在我们把List改成List<? super Father> 试试
ArrayList<Son> sons = new ArrayList<>();
ArrayList<Father> fathers = new ArrayList<>();
ArrayList<Grandpa> grandpas = new ArrayList<>();
setList(sons); //报错, 因为只能传入Father或Father的[父类]
setList(fathers); //正常
setList(grandpas); //正常
private static void setList(List<? super Father> list){
}
总结:
- 上界通配符<? extends T>会限制只能传入T的子类或者T本身
- 下界通配符<? super T> 会限制只能传入T的父类或T本身
-
有界通配符的副作用
//上界通配符<? extends Father>
private static void setList(List<? extends Father> list){
list.add(new Son()) //报错
list.add(new Father()) //报错
//获取可以用Father
for (Father father : list) {
System.out.println(father);
}
}
//下界通配符<? super Father>
private static void setList(List<? super Father> list){
list.add(new Son()) //正常
list.add(new Father()) //正常
list.add(new Grandpa()) //报错, 只能添加Father或Father的子类
//获取只能用Object
for (Object o : list) {
System.out.println(o);
}
}
总结:
- 上界通配符不能添加,只能获取 因为无法确定传来的泛型是T本身还是T的子类,如果是T的子类,那添加T本身一定会报错,所以直接上界通配符直接禁止添加,但是可以正常获取,获取的时候用T来接收.
- 下界通配符可以添加,但是只能添加T和T的子类,获取的时候只能用Object接收,因为无法确定传来的泛型是T的哪一个父类,所以只能用最大的父类Object来接收.
-
泛型擦除
由于泛型是JDK1.5才引入的,为了向下兼容,所以泛型只存在于代码编译期,进入JVM前泛型相关的代码会被擦除掉.
-
怎么证明泛型被擦除了?
可以看到下面创建了两个泛型不同的集合,但是运行后发现Class都是java.util.ArrayList,并不存在泛型,所以结果为ture
public static void main(String[] args) {
ArrayList<Integer> list1 = new ArrayList<>();
ArrayList<String> list2 = new ArrayList<>();
System.out.println(list1.getClass() == list2.getClass()); //true
}
-
无限制类型擦除,直接变成Object
-
有限制类型擦除,变成上限
-
维持多态性,系统自动生成桥接方法