1 泛型的概念&为什么需要泛型
泛型泛化类型就是参数化类型,使原本固定的类型参数化,把类型的确定推迟到使用时才确定。
为什么需要泛型:
泛型的作用有两个:
一是起到约束的作用,定义泛型之后传入其他类型参数编译期就会报错,不会等到使用时发生类型转换错误(ClassCastException)
二是定义一次,多种类型都可以使用。
泛型中使用较多的是集合。
public static void main(String[] args) {
//未指定泛型
List list = new ArrayList();
list.add("sldjflsf");
list.add(1234);
for (int i = 0; i < list.size(); i++) {
//没有问题
System.out.println(list.get(i)+"");
}
for (int i = 0; i < list.size(); i++) {
//强制类型转换会报错
System.out.println((String)list.get(i)+"");
}
}
result:
sldjflsf
1234
sldjflsf
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
at com.ldx.extend.Demo5.main(Demo5.java:22)
定义泛型之后编辑器就会报错:
指定泛型类型之后,添加时不能添加指定类型以外的类型,强制类型转换时也就不会出错(其实也不会出现强制类型转换)
2 java中泛型使用方式
java中泛型的使用分成三类,泛型类,泛型接口,泛型方法。
/**
* 泛型类的类型只能是类类型不能使普通类型
*如果使用时,初始化不做任何限制,则可以传入任何的类型
*/
public class GenericDemo1 <T>{
private T key;
public GenericDemo1(T t) {
this.key = t;
}
//这是一个普通的方法并不是泛型方法
public T getKey() {
return key;
}
}
interface GenerticInterface <T>{
public T getData();
}
/**
* 可以传入确定类型参数,但泛型将毫无意义
* 未传入时在声明类的时候,需将泛型的声明也一起加到类中
*
*/
class GenerticDemo2<T> implements GenerticInterface<T>{
@Override
public T getData() {
// TODO Auto-generated method stub
return null;
}
}
使用方式类似集合泛型使用,这里不再举例。泛型可以指定多个<K,V>
注意:
定义了泛型不一定非得使用,如果不使用任何类型参数都可以传入,但可能会发生类型转换错误
- 泛型的类型参数只能是类类型,不能是简单类型。
- 不能对确切的泛型类型使用instanceof操作。
- if(instance instanceof GenericDemo<Number>){ }编译时就会出错。
- 泛型可以用任意字符表示,但是最好是E,K,V,N,T,分别代表Element,Key,Value,Number,Type。
泛型方法:
public class Demo1 {
public static void main(String[] args) {
// TODO Auto-generated method stub
}
//<T>是泛型方法的标识,表明该方法使用泛型类型T
//泛型类中的没有<E>的方法不是泛型方法,
//参数中使用泛型类的也不是泛型方法
public <T> T getData(GenericDemo1<T> obj) {
return obj.getKey();
}
}
class GengerDemo3<T>{
//在泛型类中声明了一个泛型方法,使用泛型E,这种泛型E可以为任意类型。
public <E> void getData1(E t){
System.out.println(t.toString());
}
//在泛型类中声明了一个泛型方法,使用泛型T,注意这个T是一种全新的类型,可以与泛型类中声明的T不是同一种类型。
public <T> void getData2(T t){
System.out.println(t.toString());
}
//传递的可变参数类型可以不一样
public <T> void GetData3( T... args){
for(T t : args){
}
}
//这是错误的
public static void getData4(T data) {
}
//这是正确的
public void getData5(T data) {
}
}
静态方法与泛型
静态方法要使用泛型,静态方法无法访问类上定义的泛型;如果静态方法操作的引用数据类型不确定的时候,必须要将泛型定义在方法上。
//会报Cannot make a static reference to the non-static type T 的错误提示
public class Demo4<T> {
public T t;
public static T staticMethod(T key) {
return key;//无意义操作
}
}
//正确的使用方法
public class Demo4<T> {
public T t;
public static<T> T staticMethod(T key) {
return key;
}
}
3 泛型通配符和类型的上下边界
通配符
泛型通配符用?表示,此处?是实参而不是形参,当我们只需要使用Object类的功能时就可以使用通配符。
ArrayList<?> list;可以接收任意类型Arraylist,获取数据时是Object类型
Class<?> clazz = Class.forName();
通配符?用在接收参数,方法中的形参用?修饰时可以传入的参数会受到限制。
泛型类型的上下边界:
? 通配符类型
<? extends T> 表示类型的上界,表示参数化类型的可能是T 或是 T的子类
<? super T> 表示类型下界(Java Core中叫超类型限定),表示参数化类型是此类型的超类型(父类型),直至Object
上面使用了extends 和super关键字,泛型类型的上下边界也就是这两个关键字的作用。
当我们使用泛型并把泛型定义为T时,是没有办法使用T所代表的类型的函数的,泛型还可以对传入的泛型类型实参进行上下边界的限制,类型实参只准传入某种类型的父类或某种类型的子类。
3.1. “?”不能添加元素
以“?”声明的集合,不能往此集合中添加元素(但是可以接收现有子类型的赋值),所以它只能作为生产者(亦即它只能被迭代),如下:
public void get() {
//list1此时无法添加元素,获取元素也只能获取Object
List<?> list1 = new ArrayList();
// 通配符声明的集合,获取的元素都是Object类型
List<String> list2 = new ArrayList();
list2.add("aaaa");
list2.add("aaab");
list2.add("aaac");
list2.add("aaad");
list1 = list2;
// 只能以Object迭代元素
for(Object data: list1) {
System.out.println(data);
}
}
3.2 “? extends T”也不能添加元素
以“? extends T”声明的集合,不能往此集合中添加元素(可以接收现有子类型的赋值,可以添加null但是无意义),所以它也只能作为生产者,如下:
相对于以“?”声明的集合,“? extends T”能更轻松地迭代元素:
List<? extends String> list3 = new ArrayList();
list3 = list2;
//错误的无法添加元素只能获取元素
// list3.add("alsdjlsf");
for(String data:list3) {
System.out.println(data);
}
3.3 “? super T”能添加元素
只有“? super T”能添加元素,所以它能作为消费者。
List<? super String> list4 = new ArrayList();
list4.add("sldjfsf");
//错误无法确定类型
/*for(String data : list4) {
}*/
for(Object data : list4) {
}
针对采用“? super T”通配符的集合,对其遍历时需要多一次转型,如下:
// 只能获取到Object类型
for(Object data: list) {
// 这里需要一次转型
System.out.println(data);
}
4 泛型的擦除
java的泛型是伪泛型,是通过擦除实现的,java泛型在编译期有效,在运行期被删除,泛型参数类型在编译后都会被清除掉(其实并非全部)。
ArrayList<Integer>和ArrayList<String>被认为是不同的,但是对于编译后的代码,它们是完全相同的,只有ArrayList.class,没有ArrayList<Integer>.class和ArrayList<String>.class。
类型擦除的基本过程先找到用来替换类型参数的具体类,一般是Object。如果指定了类型参数的上界的话,则使用这个上界。把代码中的类型参数都替换成具体的类。同时去掉出现的类型声明,即去掉<>的内容。接下来就可能需要生成一些桥接方法(bridge method),这是由于擦除了类型之后的类可能缺少某些必须的方法。
关于泛型还有很多深入内容要研究,敬请期待后文。