我为什么要用泛型
当前我有两个场景要用到泛型,而且感觉非常棒!
1、当我写一个工具函数时,所谓工具,肯定就是大家共用的,但使用的对象肯定是不同,不可能每个对象都写个工具函数吧。比如我要从对象集合中随机抽取N个对象,就可以用泛型,一个函数直接搞定。直接上代码:
/**
* 从集合中随机取出N个不重复的元素
* @param list 需要被取出数据的集合
* @param n 取出的元素数量
* @return
*/
public static <T> List<T> createRandomList(List<T> list, int n) {
Map<Integer,String> map = new HashMap();
List<T> news = new ArrayList();
if (list.size() <= n) {
return list;
} else {
while (map.size() < n) {
int random = (int)(Math.random() * list.size());
if (!map.containsKey(random)) {
map.put(random, "");
news.add(list.get(random));
}
}
return news;
}
}
注意:返回类型前面的不可少,这个T意思是告诉编译器,T是个泛型,有兴趣的可以和下面这个写法的区别
private <T> T getStudent(List<T> list){
// 如果入参不是List而是Set,那么返回的也将是Set
}
2、将上面的工具场景进行放大到业务层面,比如我给用户推荐数据时,我推荐的数据会有多种不同的来源,但这些数据都要走一套相同的处理流程,那么就可以将这些数据通道抽象出一部分,这个抽象类可以用泛型来接收不同的通道对象。
@Component
public abstract class AbstractModuleService<T>
{
...
}
@Service
public class StaticModuleServiceImpl extends AbstractModuleService<StaticModule> implements IStaticModuleService
{
...
}
进阶
了解完了基本用法后,看看泛型到底是怎么玩儿的?
首先,大家都知道,我们写的代码是要编译后交给jvm执行的,而虚拟机没有泛型类型的对象。我们来验证下:
1、
ArrayList<String> list1 = new ArrayList<String>();
list1.add("abc");
ArrayList<Integer> list2 = new ArrayList<Integer>();
list2.add(123);
System.out.println(list1.getClass() == list2.getClass());
输出的结果为:true
其实不难理解,大家都是ArrayList,只不过包的东西不一样而已。
2、
ArrayList<Integer> list = new ArrayList<Integer>(); // 定义的Integer类型的
list.add(1); //这样调用add方法只能存储整形,因为泛型类型的实例为 Integer
list.getClass().getMethod("add", Object.class).invoke(list, "我是字符串了"); // 通过反射,发现竟然能添加String类型的
说明Integer泛型实例在编译之后被擦除了,只保留了原始类型。1
思考
知道了泛型的类型擦除,我们不难分析出,为什么T没有任何方法了,它就是一个虚拟的东西。
那么泛型被擦除了,为何还能返回我想要的类型?
1、因为返回时T会被替换为Object,当你用你想要的类型接收时,Object会强转成接收的类型。
List --> List --> List
List --> List --> List --> List
2、如果T通过继承进行了限定,那么T被擦除就会留下限定类型
public static T fun(T[] t)
类擦除后
public static User fun(User[] t)
3、通配符?和T的区别?
?extend User 上界通配符
?super User 下界通配符
List 集合元素必须是同类型的,例:[1,2,3,4]
List<?> 集合元素可以是任何类型,例:[1,“string”,User]
因此T可以操作,?不可以操作,例:有T t = fun(); 而没有? f = fun();
Java泛型基本上都是在编译器这个层次上实现的,在生成字节码中是不包括泛型中的类型信息,使用泛型的时候添加上类型参数,在编译器编译的时候会去掉,这个过程称为类型擦除。 ↩︎