一.概述
1.定义
泛型本质是参数化类型,即把一个所操作数据类型声明为一个参数。类似于形参,该形参代表的是一种数据类型,在调用时指定实参该数据类型才能确定。
2.特点
- 泛型只在编译时检测提示,在编译后泛型会被擦除
- 泛型可以操作不同的类型数据,提高了重用性,一次定义,多种使用
- 泛型传递实参时必须是引用类型
3.好处(相对于Object)
- 提高可读性,清晰知道操作的具体类型
- 提高安全性,在编译期间对数据类型进行检查
- 在获取数据时不需要强制转换类型
二.应用
1.泛型类
- 定义
//泛型类:在类上定义泛型
//<T>定义了一个泛型T,泛型名称就像变量名,根据自己爱好改
//可以一次定义多个泛型,如<A,B,C,D,E>
class GenericClass<T>{
//类的泛型可以在类中非静态成员中使用
private T t;
public GenericClass(T t){
this.t=t;
}
public T get(){
return this.t;
}
//类的泛型不能用于静态成员,因为在创建类对象时才能确定泛型的具体类型
//private static T t2; //编译错误
//public static T get2(){} //编译错误
}
- 访问
//泛型类声明和创建对象泛型要一致(左右一致)
//泛型必须是引用类型,这里int自动装箱成Integer
GenericClass<Integer> g=new GenericClass<Integer>(12345);
//在jdk1.7增加了菱形泛型,只需声明时指定实际泛型,创建时可以省略
//GenericClass<Integer> g=new GenericClass<>(12345);
System.out.println(g.get());
- 注意
- 类的泛型不能用于静态成员,因为在创建类对象时才能确定泛型的具体类型
- 泛型必须是引用类型
2.泛型接口
- 定义
//泛型接口跟泛型类的定义一样
interface GenericInterface<T>{
//接口中属性都是静态,所以无法使用接口懂的泛型
//T t; //编译错误
//接口中的方法可以使用接口的泛型
T get();
void set(T t);
}
- 实现泛型接口
//方式1:使用泛型类实现泛型接口
class GenericClass<T> implements GenericInterface<T>{
@Override
public T get() {return null;}
@Override
public void set(T t) {}
}
//方式2:使用普通类实现泛型接口,接口中的泛型需要给出实际的引用类型
class CommonClass implements GenericInterface<String>{
//因为泛型接口中的泛型已确定为String,所以重写的方法的T都变成了String
@Override
public String get() {return null;}
@Override
public void set(String t) {}
}
3.泛型方法
- 定义
//泛型方法可以定义在普通类或泛型类中
class CommonClass{
//泛型方法:在方法返回值前使用<>定义泛型
//泛型方法中的泛型可以在[方法返回值]丶[方法参数]丶[方法体内]使用
public <M> M genericMethod(List<M> list){
M m=list.get(0);
return m;
}
//静态泛型方法
public static <M> M staticGenericMethod(List<M> list){
M m=list.get(1);
return m;
}
}
- 访问
List<Integer> list=new ArrayList<Integer>();
list.add(1);
list.add(2);
CommonClass cc=new CommonClass();
//调用泛型方法时不用显式指明实际泛型,系统会自动根据方法参数进行判断
Integer i1= cc.genericMethod(list);
Integer i2=CommonClass.staticGenericMethod(list);
System.out.println(i1);
System.out.println(i2);
三.泛型通配符与边界
1.通配符
泛型通配符?表示不确定的泛型实参,可代表多种类型,常搭配泛型类为方法参数。
public static void main(String[] args) {
//传递各种泛型
printList(new ArrayList<Integer>());
printList(new ArrayList<Number>());
printList(new ArrayList<String>());
}
//使用?代替具体的泛型,该方法参数可接收任意泛型的List
//这里不可以用List<Object>代替,虽然Object是所有类的父类,但Object泛型类不是所有泛型类的父类
public static void printList(List<?> list){}
<T>
与?
区别- 都表示多种不确定的类型
<T>
主要用于定义各种泛型类丶泛型方法等,相当于形参- ?主要用于泛型类的实参
2.下边界<? super 底层类>
下边界限制了泛型只能是本类或其祖先类
- 特点
- 可改,泛型类存放的数据都是底层类或底层类的父类,所以修改时传递数据
限定为底层类或其底层类子类
,就会形成多态的父类引用指向子类对象 - 可读,因为所有类的父类是Object,所以读取时
用Object接收
就行
class A{}
class B extends A{}
class C extends B{}
class D extends C{}
//下边界为B
List<? super B> list=new ArrayList<>();
//List<? super B> list=new ArrayList<A>();
//编译错误,修改时不能传递B的父类
//list.add(new A());
//正常,修改时只能传递B类和B的子类
list.add(new B());
list.add(new C());
list.add(new D());
//编译错误,因为泛型类没有限制上边界,所以默认上边界只能时Object
//B b= list.get(0);
Object o=list.get(0);
3.上边界<? extends 顶层类>
上边界限制了泛型只能是本类或其子孙类
- 特点
- 不可改(null除外),因为泛型没有下边界,所以传递进来的泛型数据不确定,当修改时可能会出现类型转换异常
- 可读,因为上边界已经限制了顶层类,所以读取时
用顶层类接收
就行(多态的父类引用指向子类地址)
//上边界为B
List<? extends B> list=new ArrayList<>();
//添加ABCD均编译错误,因为没有设定下边界
//list.add(new A());
//list.add(new B());
//list.add(new C());
//list.add(new D());
//正常,因为上边界为B,而A和Object是B的父类
B b= list.get(0);
A a=list.get(0);
Object o=list.get(0);
四.泛型可变参数
public static void main(String[] args) {
//泛型可变参数可放任意类型,但会被解析成Object数组
Object[] o= genericMethod(1,1.2,"abc");
//放同种类型就会被解析成该类型的数组
Integer[] i=genericMethod(1,2,3);
}
//泛型可变参数
public static <T> T[] genericMethod(T... args){
return args;
}
五、获取子类上继承类的泛型实参
1.概念
List<E>
E表示泛型参数形参ArrayList<Integer>
Integer表示泛型参数实参ArrayList<Integer>
ArrayList<Integer>整个表示参数化类型
2.步骤
- 获取子类对象的Class
- 获取带泛型的参数化类型的父类
- 将带有泛型的父类的类型转成具体参数化的类型ParameterizedType
- 通过ParameterizedType的方法获取泛型实参
public class BaseDaoImpl<T> extends HibernateDaoSupport implements BaseDao<T>{
private Class clazz;
public BaseDaoImpl() {
//举例:class UserDaoImpl extends BaseDaoImpl<User>
//对子类进行反射,this在子类实例化是指向子类对象(UserDaoImpl)
Class clazz = this.getClass();
//获取父类的参数化类型,即子类extends后面那一坨(BaseDaoImpl<User>,都带包名的,此处省略)
Type type = clazz.getGenericSuperclass();
//强转成ParameterizedType
ParameterizedType pType = (ParameterizedType) type;
//通过参数化类型获得实际类型参数(User)
Type[] types = pType.getActualTypeArguments();
//因为BaseDaoImpl<User>只有一个实际类型参数,获取第一个即可
this.clazz = (Class) types[0];
}
}