引子
最近在重构代码,发现很多情况下,抽取基类,都离不开泛型这个知识点,用好泛型,是重构代码的第一步
什么是泛型
当你想要定义一个基类,或者是通用的类、接口、方法时,你还不知道以后的使用者要往里面传入什么类型的对象,这时候,你就不能把这个数据类型写死了,写死了就不具有扩展性、通用性,就不能称之为基(通用)了。这时候你就需要泛型了。
当然,你会问,我不知道要传入什么类型,我直接写个Object类型不就行了吗,这样不是不行,但是这样极易产生《强制转换异常》,
而且这个《强制转换异常》属于运行时异常,编译期间你不会发现错误,只有在运行时,才会报错,
泛型的经典定义:
泛型,就是,一种把 明确数据类型的工作,放在了创建对象,或者调用方法时候 进行的特殊类型。
泛型用(声明)在什么位置
- 方法上,在调用方法时候,明确数据类型 方法返回值之前
- 类上,在new 对象时候,明确数据类型 类名之后
- 接口上,在实现接口(或者在new 接口的实现类的)时候,明确数据类型接口名之后
< T >:表示声明泛型
T: 表示使用泛型
T:只能是引用类型,不能是基本类型(自动装包了int–>Integer)
方法上声明泛型
public class Tool {
/**
* 1.在方法上使用泛型
* 这是一个输出语句的方法
* 但是我们在定义这个方法时候,还不确定要输出什么类型的对象
* 有可能要输出一个int类型,也有可能是String类型,或者是boolean类型
* 那么这里我们就不能把这个类型定死了,应该用一个“泛型”
* ok,我们在形参里面写(T t),就表明了,调用者可以输入任意类型的对象
* 问题来了,你平白无故来了个T,谁知道这个T是什么呢
* 就像是你直接来了个变量i,上来就i=0,这样是不行的,你应该先声明这个变量:int i;
* 然后你才可以使用这个i,类比到泛型上
* 你想使用泛型T,那么首先你应该声明这个泛型T,如何声明呢
* 很简单,只要在方法返回值前面写一个T即可(这里说的是方法上使用泛型的情况)
* @param t 形参的泛型对象
* @param <T> 声明泛型在方法上
*/
public <T> void show(T t) {
System.out.println(t);
}
}
在调用这个方法时,你可以传入任意的对象了
Tool tool = new Tool();
/**
*这时候数据类型是在使用方法时才明确的,
*所以你传入什么数据类型,就使用什么数据类型,
*是不是很方便,使用的时候在确定数据类型
**/
tool.show(100);
tool.show("hello");
tool.show(true);
tool.show(new Date());
tool.show('A');
tool.show(85.7);
tool.show(new Byte("100"));
tool.show(new Character('C'));
类上声明泛型
/**
* 泛型声明在类上
* @param <T> 声明泛型在类上
*/
public class Tool1 <T>{
//泛型T在类或者接口上声明过,就不需要在方法上声明了
public void show(T t) {
System.out.println(t);
}
}
使用这个泛型类
Tool1<String> tool1=new Tool1<>();
// 因为你在new对象时就确定了要传入的数据类型是String,
// 所以你在这里传入Integer,就直接报错,
// 是不是把运行时异常提前到编译期间了
// tool1.show(100);
tool1.show("hello");
// 还是那个Tool1<T>泛型类,这次你new对象时候,指明了明确的数据类型Integer
Tool1<Integer> tool11=new Tool1<>();
tool11.show(100);//不能再传String了
接口上声明泛型
接口上声明泛型有两种情况
1. 接口上声明了泛型,在创建该接口的实现类时,明确了泛型的具体类型
2. 接口上声明了泛型,在创建该接口的实现类时,还未明确泛型的具体类型,而是在new 实现类时,才明确泛型的具体类型
先定义一个泛型接口
/**
* 泛型声明在接口上
* @param <T> 声明泛型在接口上
*/
interface ToolInter<T> {
public void show(T t);
}
第一种情况
在创建该接口的实现类时,明确了泛型的具体类型
实现类
/**
* 由于在创建实现类时,就明确了泛型的具体类型
* 所以在该类下,所有使用该泛型的地方,都同时明确了数据类型
* 注意:只需在接口名称后面跟上<具体的数据类型>
*/
public class ToolImpl implements ToolInter<String>{
@Override
public void show(String s) {
System.out.println(s);
}
}
使用之
//由于ToolImpl已经明确了数据类型为String,这里就不需要再次指定数据类型了
ToolImpl toolImpl = new ToolImpl();
//而这个方法的泛型,也在ToolImpl里面就已经明确了,只能是String
toolImpl.show("hello");
第二种情况
在创建该接口的实现类时,还未明确泛型的具体类型,而是在new 实现类时,才明确泛型的具体类型
实现类
/**
* 由于在创建实现类时,还没有明确了泛型的具体类型
* 所以在该类下,所有使用该泛型的地方,都还没有明确具体数据类型
* 注意:
* 1.在接口名称后面跟上<泛型T>
* 2.在实现类名称后面跟上<泛型T>
* 3.在方法上使用泛型T,泛型T在类或者接口上声明过,就不需要在方法上声明了
*/
public class ToolImpl1<T> implements ToolInter<T>{
@Override
public void show(T t) {
System.out.println(t);
}
}
使用之
//由于ToolImpl1还没有明确了数据类型,所以在这里必须指定具体的数据类型
ToolImpl1<String> toolImpl1 =new ToolImpl1<>();
//而这个方法的泛型,也在上一步new ToolImpl1明确了是String,
// 所以方法的具体类型只能是String
toolImpl1.show("toolImpl1");
泛型的使用也就是以上的三种情况了