Java学习笔记12-泛型
一、泛型的引入
JDK 5.0引入了泛型(Genetic)。JDK 5.0之前的集合容器类在声明阶段无法确定容器内存的什么类型数据,JDK 5.0中将元素类型设计为一个参数,这个类型参数就是泛型。例如Collection<E>
、List<E>
、ArrayList<E>
中的<E>
就是类型参数,即泛型。
定义:泛型就是允许在定义类、接口时通过一个标识表示类中某个属性的类型或者是某个方法的返回值及参数类型。这个类型参数将在使用时(例如继承或实现这个接口、用这个类型声明变量、创建对象时)确定(即传入实际的类型参数,也称为类型实参)。
如果没有泛型,任何类型都可以添加到集合中,类型不安全,且读取出来的对象需要强制转换,可能会有ClassCastException
异常。使用泛型可以在编译时就检查,只有指定类型才可以添加到集合中。
二、在集合中使用泛型
集合接口或集合类在jdk5.0时都修改为带泛型的结构。
在实例化集合类时,可以指明具体的泛型类型。指明完以后,在集合类或接口中凡是定义类或接口时,内部结构(比如:方法、构造器、属性等)使用到类的泛型的位置,都指定为实例化的泛型类型。比如:add(E e)
,实例化以后:add(Integer e)
注意点:
- 泛型的类型必须是类,不能是基本数据类型。需要用到基本数据类型的位置,用包装类替换。
- 如果实例化时没有指明泛型的类型,默认类型为
java.lang.Object
类型。 - 以
List
为例,List
实际上表示持有任何Object类型的原生List,而List<?>
表示具有某种特定类型的非原生List,只是我们不知道哪种类型是什么
集合中使用泛型的例子:
1 |
|
泛型的嵌套使用:
1 |
|
三、自定义泛型结构
可以在类、接口以及方法上使用泛型,分别为泛型类、泛型接口、泛型方法。
1、泛型类与泛型接口
泛型类可以有多个参数,此时应将多个参数一起放在尖括号内,比如
class Test<E1,E2,E3>
。泛型类的构造器不能有尖括号结构,和一般类的构造器一样。
类实例化以后,操作原来泛型位置的结构必须与指定的泛型类型一致。
泛型不同的引用不能相互赋值,比如
List<Object> list = new ArrayList<String>();
是错误的,提示无法类型转换。泛型如果不指定,将被擦除,泛型对应的类型均按照
Object
处理,但不等价于Object
。建议如果使用泛型就一直使用泛型,如果不使用就都不使用。泛型擦除后,编译不会进行类型检查。1
2
3
4
5
6
7
8
9
10
11
12
13public class Test {
public static void main(String[] args) {
//使用时:类似于Object,但不等同于Object
ArrayList list = new ArrayList();
list.add(33);
test(list); //泛型擦除,编译不会类型检查
//一旦指定Object,编译会类型检查,必须按照Object处理
ArrayList<Object> list2 = new ArrayList<Object>();
//test(list2); //会报错,类型必须相同
}
public static void test(ArrayList<Integer> list) { }
}如果泛型结构是接口或抽象类,则不可创建泛型类的对象。
在类/接口上声明的泛型,在本类或本接口中即代表某种类型,可以作为非静态属性的类型、非静态方法的参数类型、非静态方法的返回值类型。但在静态方法中不能使用类的泛型,因为类的泛型是在实例化时指定的,静态方法不需要通过对象调用。
异常类不能是泛型的,try-catch中不能用泛型。
不能实例化泛型,即不能
new E[]
,可以先声明Object
类型的数组,然后强制转换:E[] elements = (E[])new Object[capacity];
参考ArrayList
源码中的声明Object[] elementData;
,而非泛型参数类型数组。1
2
3
4
5
6public class Order<T> {
public Order(){
T[] arr = new T[10]; //错误,编译不通过
T[] arr = (T[]) new Object[10]; //正确,编译通过
}
}父类有泛型,子类可以选择指定泛型类型或者保留泛型:
- 子类不保留父类的泛型:按需实现
- 如果没有类型,则进行擦除
- 指定具体类型
- 子类保留父类的泛型:泛型子类。泛型子类在实例化时可以指定泛型类型
- 全部保留
- 部分保留
结论:子类必须在保留或者指定二者之间做出选择。此外,子类除了指定或保留父类的泛型,还可以额外增加自己的泛型。
比如下面的例子:
1
2
3
4
5
6
7
8
9
10class Father<T1,T2>{}
//子类不保留父类泛型
class Son1 extends Father{} //1.没有类型,擦除
//2.指定类具体类型。这时的Son2类不是泛型子类,只是个普通的类。
class Son2 extends Father<Integer,String>{}
//子类保留父类泛型
//Son3和Son4都是泛型子类,因为它们都有泛型
class Son3<T> extends Father<T1,T2>{} //1.全部保留。子类可以有自己的泛型
class Son4 extends Father<Integer,T2>{} //2.部分保留- 子类不保留父类的泛型:按需实现
2、泛型方法
方法中也可以使用泛型,泛型方法中可以定义泛型参数,此时参数的类型就是传入数据的类型。
泛型方法的泛型与类的泛型无关,即泛型方法所在的类是不是泛型类没有关系。
只使用类的泛型的方法不是泛型方法。
泛型方法可以是静态的,因为静态方法在调用的时候会指定泛型方法的类型参数。
泛型方法的格式:
权限符 <泛型> 返回类型 方法名(泛型 参数1,...)
比如:
1 |
|
四、泛型在继承上的体现
如果类A
是类B
的父类,那么class <A>
和class <B>
二者不具备子父类关系,二者是并列关系。
比如ArrayList<Object> list1
和ArrayList<String> list2
,那么list1 = list2
是错误的。
如果A
是类B
的父类,A<E>
是 B<E>
的父类。
举例:
1 |
|
五、通配符
通配符?
代表具体的类型参数,例如List<?>
是List<String>
、List<Object>
等各种泛型List
的父类。
假设现在有List<?>
的一个对象list
,那么可以安全读取list
中的元素,因为一定是Object
类型的。不可以向list
中写入元素,因为不知道list
中元素的类型,但是可以写入null
,其他都都不可以。
代码举例:
1 |
|
注意点:
public static <?> void test(ArrayList<?> list){}
编译错误,不能用在泛型方法声明上,返回值类型前面<>
不能使用?
通配符。返回值必须是确定的类型。class GenericTypeClass<?>{}
编译错误,?
不能用在泛型类的声明上。ArrayList<?> list2 = new ArrayList<?>();
编译错误,?
不能用在创建对象上,右边属于创建集合对象,必须是确定的类型。
有限制的通配符
? extends A
:理解为<=A类,类型限定了只能是A类或者是A类的子类。class<? extends A>
可以作为class<A>
和class<B>
的父类,其中B是A的子类
? super A
:理解为>=A类,类型限定了只能是A类或者是A类的父类。class<? super A>
可以作为class<A>
和class<B>
的父类,其中B是A的父类
- 对于接口,
<? extends Comparable>
表示只允许泛型为实现Comparable
接口的实现类的引用调用。
使用举例:
1 |
|
总结:
- 使用
extends
通配符只能读,不能写(只能写null
),类似于?
通配符。因为有上限,返回的任意类型都能够向上转型到确定的类型,但是不允许使用set
方法传入引用(null
除外)。 - 使用
super
通配符可以写,也可以读。写的时候可以安全地向上转型,读的时候,只能使用Object
类型接收,因为Object
是所有类的根父类,可以向上转型为Object
。
- 本文作者:Kangshitao
- 本文链接:http://kangshitao.github.io/2021/04/11/java-note-1201/index.html
- 版权声明:本博客所有文章均采用 BY-NC-SA 许可协议,转载请注明出处!