一 泛型的基本概念
1.1什么是泛型?
泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。
1.2泛型的作用?
JDK5中的泛型允许程序员在编写集合代码时,就限制集合的处理类型,从而把原来程序运行时可能发生问题,转变为编译时的问题,以此提高程序的可读性和稳定性(尤其在大型程序中更为突出)。
1.3需要掌握的知识:
基本用法、泛型擦除、泛型类/泛型方法/泛型接口、泛型关键字、反射泛型(案例)!
二 基本用法
使用泛型的好处就是可以在编译时帮我们检查错误。
从例子入手:
// 运行时期异常
@Test
public void testGeneric() throws Exception {
// 集合的声明
List list = new ArrayList();
list.add("test");
list.add(1);
// 集合的使用
String str = (String) list.get(1);
}
上面代码,程序在运行时期会出现异常:java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
原因就是,ArrayList可以存放任意类型,默认为Object类型,例子中添加了一个String类型,添加了一个Integer类型,再使用时以String的方式使用第二个元素,因此程序崩溃了。为了解决类似这样的问题(在编译阶段就可以解决),泛型应运而生。
// 使用泛型
@Test
public void testGeneric2() throws Exception {
// 声明泛型集合的时候指定元素的类型
List<String> list = new ArrayList<String>();
list.add("test");
// list.add(1);// 编译时期直接报错
String str = list.get(1);
}
注意:泛型的类型参数只能是类类型(包括自定义类),不能是简单类型(如int)
三 泛型擦除
泛型是提供给javac编译器使用的,它用于限定集合的输入类型,让编译器在源代码级别上,即挡住向集合中插入非法数据。但编译器编译完带有泛形的java程序后,生成的class文件中将不再带有泛形信息,以此使程序运行效率不受到影响,这个过程称之为“擦除”。
例如1:
List<String> stringArrayList = new ArrayList<String>();
List<Integer> integerArrayList = new ArrayList<Integer>();
Class classStringArrayList = stringArrayList.getClass();
Class class IntegerArrayList = integerArrayList.getClass(); if(classStringArrayList.equals(classIntegerArrayList)){
Log.d("泛型测试","类型相同");
}
输出结果:D/泛型测试: 类型相同。
例子2:
// 泛型擦除实例
public void save(List p){
}
public void save(List d){ // 报错: 与上面方法编译后一样
}
在例子2中两个重名的方法看似是方法的重载,其实参数类型一致,会报错。因为在编译过程中,正确检验泛型结果后,会将泛型的相关信息擦出,并且在对象进入和离开方法的边界处添加类型检查和类型转换的方法。也就是说,泛型信息不会进入到运行时阶段。
总结就是,泛型类型在逻辑上看似是多个不同的类型,实际上都是相同的基本类型。
四 泛型的使用
4.1 泛型方法
public class GenericDemo {
// 定义泛型方法
public T save(T t) {
return null;
}
// 测试方法
@Test
public void testMethod() throws Exception {
// 使用泛型方法: 在使用泛型方法的时候,确定泛型类型
save(1);
}
}
泛型方法的泛型声明必须是在修饰符之前返回值类型之后定义。例如上面的 “public <> T”;
只有声明了”<>”的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法。
还有一点要注意的是:泛型方法在使用的时候,才确定泛型类型。如上面代码save(1);被调用的的时候才确定类型为Integer.
泛型方法中的T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型。并且泛型的数量也可以为任意多个。
例如:
public class GenericDemo {
// 定义泛型方法
public T save(T t,K k) {
return null;
}
// 测试方法
@Test
public void testMethod() throws Exception {
// 使用泛型方法: 在使用泛型方法的时候,确定泛型类型
save(1.0f, 1);//Double,Integer类型
}
}
4.2 泛型类的使用
public class GenericDemo {
// 定义泛型方法
public T save(T t,K k) {
return null;
}
public void update(T t) {
}
// 测试方法
@Test
public void testMethod() throws Exception {
// 泛型类: 在创建泛型类对象的时候,确定类型
GenericDemo demo = new GenericDemo();
demo.save("test", 1);
}
}
从上面例子中我们可以看到 泛型类的类型声明是在类名之后的。并且在方法中如果需要用到这个T类型可以不用再次声明,直接可以用了。但是K类型还需要在方法中声明一下。
还有就是 当创建泛型类对象的实例时,指定类型为String,才确定类型。
4.3 泛型接口
泛型接口与泛型类的定义及使用基本相同。
例如:
//定义一个泛型接口
public interface Generator {
public T next();
}
//当实现泛型接口的类,未传入泛型实参时:
/** * 未传入泛型实参时,与泛型类的定义相同,
在声明类的时候,需将泛型的声明也一起加到类中 *
即 class FruitGenerator implements Generator{
* 如果不声明泛型,如:class FruitGenerator implements Generator,编译器会报错:"Unknown class" */
如下正确:
class FruitGenerator<T> implements Generator<T>{
@Override
public T next() {
return null;
}
}
当实现泛型接口的类,传入泛型实参时:
public class FruitGenerator implements Generator<String> {
@Override
public String next() {
return "fruit";
} }
注意 当实现类实现泛型接口时,如已将泛型类型传入实参类型,则所有使用泛型的地方都要替换成传入的实参类型 即:Generator<>,public T next();中的的T都要替换成传入的String类型。
五 泛型关键字
泛型中:
? 指定只是接收值
extends 元素的类型必须继承自指定的类
super 元素的类型必须是指定的类的父类
5.1 关键字:? 【通配符】
当我们在调用方法执行之后返回值的时候,或者在方法传递参数的时候,我们不清楚到底返回什么类型,或者传递什么类型,这个时候可以用 ?来代替泛型类型,可以实现扩展性。
public class Test {
// 只带泛型特征的方法
public void save(List> list) {
// 只能获取、迭代list; 不能修改list
}
public static void main(String[] args) {
// ? 可以接收任何泛型集合, 但是不能修改集合元素; 所以一般在方法参数中用
List> list1 = new ArrayList();
List> list2 = new ArrayList();
Test test = new Test();
test.save(list1);
test.save(list2);
// list.add("");// 会报错
}
}
上面代码 List 可以接受两种泛型类型的集合。但是不能修改它。还有就是上面不用再写两个方法来接受不同泛型集合的传递参数,而是只用一个方法来接受所有泛型类型的集合参数。
还有一点就是,之前看到的,类型通配符一般是使用?代替具体的类型实参,注意了,此处’?’是类型实参,而不是类型形参 。再直白点的意思就是,此处的?和Number、String、Integer一样都是一种实际的类型,可以把?看成所有类型的父类。是一种真实的类型。
5.2 关键字 : extends 【上限】
extends关键字的意思就是类型实参只准传入某种类型的子类包括自身。也就是会限定范围。如果不是该父类类型的子类,则报错。
例如:
public class App_extends {
/**
* list集合只能处理 Double/Float/Integer等类型
* 限定元素范围:元素的类型要继承自Number类 (上限)
* @param list
*/
public void save(List extends Number> list) {
}
@Test
public void testGeneric() throws Exception {
List list_1 = new ArrayList();
List list_2 = new ArrayList();
List list_3 = new ArrayList();
List list_4 = new ArrayList();
// 调用
save(list_1);//通过
save(list_2);//通过
save(list_3);//通过
//save(list_4);//会报错 因为String不是Number的子类
}
}
5.3关键字 : super 【下限】
super关键字的意思就是类型实参只准传入某种类型的父类包括自身。也就是所说的上限。
例如:
public class App_super {
/**
* super限定元素范围:必须是String父类 【下限】
* @param list
*/
public void save(List super String> list) {
}
@Test
public void testGeneric() throws Exception {
// 调用上面方法,必须传入String的父类
List
六 反射泛型(BaseDao案例)
在我们做项目的时候会碰到要实现一个模块比如:管理员模块,个人账户模块。我们需要写AdminDao类、然后在类中写一大推数据库操作的方法,增删改查等。然后我们开始写AccountDao类,也会再写一遍增删改查操作。这个时候我们就想能不能优化一下代码,写一个通用的BaseDao,里面有通用的增删改查等常用数据库操作方法,然后让其他模块继承此类,这样不用每次去写一遍了,提高效率。
要想写一个通用的数据库操作方法,必须知道数据库中的表名和操作的对象类型。
因此,要想写一个通用类,我们必须约定数据库中的表名必须和类名一直。
那么类名在哪里可以获得,只能通过泛型类来实现,然后让子类继承BaseDao,在子类中指定父类的泛型类型。
例如:
Class BaseDao<T>{
//根据主键查询通用方法
Public T findById(int id){
//获取对象
//获取表
}
}
Class AdminDao extends BaseDao<Admin>{}
因此在创建子类AdminDao的实例时,将Admin通过泛型传递给BaseDao,然后帮助我们实现数据库通用操作。那么在父类中如何拿到子类的类型。首先我们要知道
什么是参数化类型?例如:“ArrayList《String》 ” 为参数化类型
还需要知道一个类。 ParameterizedType 通过这个类 我们能获取子类的类型。
下面来看一下具体实现。
public class AdminDao extends BaseDao<Admin> {}
public class AccountDao extends BaseDao<Account> {}
/**
* 所有dao的通用方法
*
*/
public class BaseDao<T>{
// 保存当前运行类的参数化类型中的实际的类型
private Class clazz;
// 表名
private String tableName;
// 构造函数: 1. 获取当前运行类的参数化类型;
//2. 获取参数化类型中实际类型的定义(class)
public BaseDao(){
// this 表示当前运行类 (AccountDao/AdminDao)
// this.getClass() 当前运行类的字节码 (AccountDao.class/AdminDao.class)
// this.getClass().getGenericSuperclass(); 当前运行类的父类,即为BaseDao
// 其实就是“参数化类型”, ParameterizedType
Type type = this.getClass().getGenericSuperclass();
// 强制转换为“参数化类型” 【BaseDao】
ParameterizedType pt = (ParameterizedType) type;
// 获取参数化类型中,实际类型的定义 【new Type[]{Account.class}】
Type types[] = pt.getActualTypeArguments();
// 获取数据的第一个元素:Accout.class
clazz = (Class) types[0];
// 表名 (与类名一样,只要获取类名就可以)
tableName = clazz.getSimpleName();
}
/**
* 主键查询
* @param id 主键值
* @return 返回封装后的对象
*/
public T findById(int id){
/*
* 1. 知道封装的对象的类型
* 2. 表名【表名与对象名称一样, 且主键都为id】
*
* 即,
* ---》得到当前运行类继承的父类 BaseDao
* ----》 得到Account.class
*/
String sql = "select * from " + tableName + " where id=? ";
try {
return JdbcUtils.getQuerrRunner().query(sql, new BeanHandler(clazz), id);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
/**
* 查询全部
* @return
*/
public List getAll(){
String sql = "select * from " + tableName ;
try {
return JdbcUtils.getQuerrRunner().query(sql, new BeanListHandler(clazz));
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}