前言
曾经年少轻狂的我,以为自己已经轻松拿捏Java泛型,直到后来学习Java 8新特性时,被泛型轻松反拿捏!那时的心情,正如标题一样,自我怀疑:"我真的了解泛型吗?" 答案很明显,不了解!要不是有eclipse/idea这类编辑器工具提供编译检查的话,真让我手写代码话,可能会写出让人笑掉大牙的代码。于是,再次回头来学习一下泛型知识。
泛型由来
很久很久以前。。。
记得小时候,奶奶提了一篮子鸡蛋去赶集卖鸡蛋,结果把几个老鸭蛋也卖了。原来是不经意间把几个鸭蛋混在鸡蛋一起了,幸好没有把鹅蛋也当做鸡蛋卖了,鹅蛋可比鸡蛋贵很多哦。于是,为了便于区分,爷爷就专门用竹子编织了几个不同的篮子专门用于盛放鸡蛋、鸭蛋、鹅蛋。用代码体现,如下:
- 装鸡蛋的篮子
arduino
/**
* 蛋
*/
class Egg {}
/**
* 鸡蛋
*/
class ChickenEgg extends Egg {}
/**
* 装鸡蛋的篮子
*/
class BasketForChickenEgg {
int capacity = 30;
ChickenEgg[] container = new ChickenEgg[capacity];
int size;
public void add(ChickenEgg chickenEgg) {
if (size >= capacity) {
throw new RuntimeException("basket is full!");
}
container[size++] = chickenEgg;
}
public ChickenEgg get() {
if (size >= 0) {
return container[size--];
}
return null;
}
}
- 装鸭蛋的篮子
arduino
/**
* 蛋
*/
class Egg {}
/**
* 鸭蛋
*/
class DuckEgg extends Egg {}
/**
* 装鸭蛋的篮子
*/
class BasketForDuckEgg {
int capacity = 30;
DuckEgg[] container = new DuckEgg[capacity];
int size;
public void add(DuckEgg duckEgg) {
if (size >= capacity) {
throw new RuntimeException("basket is full!");
}
container[size++] = duckEgg;
}
public DuckEgg get() {
if (size >= 0) {
return container[size--];
}
return null;
}
}
- 装鹅蛋的篮子
arduino
/**
* 蛋
*/
class Egg {}
/**
* 鹅蛋
*/
class GooseEgg extends Egg {}
/**
* 装鸭蛋的篮子
*/
class BasketForGooseEgg {
int capacity = 30;
GooseEgg[] container = new GooseEgg[capacity];
int size;
public void add(GooseEgg gooseEgg) {
if (size >= capacity) {
throw new RuntimeException("basket is full!");
}
container[size++] = gooseEgg;
}
public GooseEgg get() {
if (size >= 0) {
return container[size--];
}
return null;
}
}
从上面的代码可以看出,几个装蛋的篮子类,除了蛋的类型不一样,其他的变量、方法实现逻辑都一样。那时候java还没有泛型,哪怕只有一个变量的类型不一样,也得写出不同类来区分,比如后面家里养了鸽子,得编织一个专门盛放鸽子蛋的篮子,再后来家里养了鸵鸟(这个有点夸张了,哈哈),又得编织一个专门盛放鸵鸟蛋的篮子。这样一直下去的话,就必须编织成百上千个不同类型的篮子了。
此时,你肯定会说,直接搞一个专门盛放蛋的篮子,不管什么蛋都可以装,如下:
arduino
/**
* 蛋
*/
class Egg {}
/**
* 装蛋的篮子
*/
class BasketForEgg {
int capacity = 30;
Egg[] container = new Egg[capacity];
int size;
public void add(Egg egg) {
if (size >= capacity) {
throw new RuntimeException("basket is full!");
}
container[size++] = egg;
}
public Egg get() {
if (size >= 0) {
return container[size--];
}
return null;
}
}
如上,这个篮子有点特殊,可以装各种蛋,如:鸽子蛋、鹌鹑蛋、鸡蛋。。。但是,小时候的我,非常调皮,经常偷偷的把鸽子蛋和鹌鹑蛋捡到一起,由于这两种蛋大小、颜色都非常相似,奶奶拿到街上去卖的时候,就不好分辨了,很容易卖错价钱!
对比咱们曾经写过的业务代码,有没有发现和装蛋的篮子故事很相似:有某些类的实现算法、逻辑几乎相同,只是个别参数或变量的类型不同,但是根据业务需要,我们不得不写很多这些极其相似的类或者方法,如果有成千上万这样的不同类型的变量,就得写成千上万个非常类似的类,这就导致了非常严重的代码冗余问题了。
JDK1.5以后。。。
于是,为了解决这一问题,我们把这样的在通用模板中,存在变化的的东西提取出来,用参数化表示 ,即用T来表示。如,BasketForEgg就是一个模板类,只需要指定这个T为ChickenEgg,就可以得到Basket类,指定T为DuckEgg便可得到Basket类,这样就不用写成千上万个类啦,只需要一个模板类就搞定了,换句话说就是通过通用模板类,达到制作完全不同类的目的。如下图所示:
通过BasketForEgg这个模板类,制作出了BasketForEgg、BasketForEgg、BasketForEgg三个不同的类。虽然,目前java在运行期间会存在擦除,这只是java实现泛型的一种机制,但是在逻辑上(编译层面) 我们把它们当做三个完全不同的类,你能用BasketForEgg basket = new BasketForEgg()吗?显然不能,编译不通过。所以在有了泛型之后,要把类名和泛型参数绑在一起来看,才能算一个独立的类,如BasketForEgg是一个类,而不是BasketForEgg类。
一句话总结:泛型类是类的模版,类是对象实例的模版! 有了泛型之后,再也不用写那么多重复的类了!
【effective java】的作者:泛型类有点像普通类的工厂,其实就是一个意思啦
泛型定义
泛型即参数化类型,通常用T来占位,如需多个就用多个大写字母来占位,如E、K、V等,通过填充不同类型的参数,便能得到填充类型的类,常用在类、接口、方法上。如BasketForEgg就是一个泛型类,而Egg、DuckEgg这些都是具体的类。jdk1.5后,几乎所有的集合类都支持了泛型,常见的开源框架也纷纷支持泛型,jdk8中Stream流更是把泛型的应用体现得淋漓尽致。
泛型分类
根据泛型参数T,所在的位置不同,可以分为泛型接口、泛型类、泛型方法, 其实接口和类是一个意思,也可以分为两种泛型方法、泛型类/接口。
泛型类
泛型参数T写在类名的后面,通过尖括号表示,写在类上面的泛型参数T,它的作用域自然就是整个类了。那一般什么时候是去填充这个T的具体类型呢?常见的两种方式:
- 在对这个泛型类实例化时填充T的具体类型
- 在子类继承这个泛型类时填充T的具体类型
- 如果子类在继承该泛型类时,不填充具体的类型,那么子类也可以是泛型类
scala
/**
* 装蛋的篮子
*/
class BasketForEgg<T> {
int capacity = 30;
Object[] container = new Object[capacity];
int size;
public void add(T t) {
if (size >= capacity) {
throw new RuntimeException("basket is full!");
}
container[size++] = t;
}
public T get() {
if (size >= 0) {
return (T) container[size--];
}
return null;
}
public static void main(String[] args) {
//实例化对象时,填充具体的泛型类型,这里以鸭蛋为例
BasketForEgg<DuckEgg> duckEggBasket = new BasketForEgg<DuckEgg>();
//装一个鸭蛋,正常
duckEggBasket.add(new DuckEgg());
//编译器报错,因为指定了泛型类型为DuckEgg,就只能装鸭蛋了
duckEggBasket.add(new ChickenEgg());
}
/**
* 子类在继承泛型类时,指定具体的泛型类型
*/
class SubBasketForEgg extends BasketForEgg<DuckEgg> {}
/**
* 子类在继承泛型类时,若未填充具体的类型,则子类也是一个泛型类
*/
class SubBasketForEgg2<T> extends BasketForEgg<T> {}
}
泛型接口
泛型参数T写在类接口名的后面,通过尖括号表示。同泛型类基本一致,泛型参数T作用域在整个接口中,在某个具体的类实现泛型接口时,填充具体的泛型类型;在子类接口继承泛型接口时,可以填充具体的参数类型,或者不填充,该子类接口也可以是泛型接口。如下:
- 装东西的篮子
csharp
/**
* 装东西的篮子接口:泛型接口
*/
interface Basket<T> {
void add(T t);
T get();
}
- 装水果的篮子
typescript
/**
* 装水果的篮子:在实现篮子接口时,填充具体的泛型类型
* 这里要把Basket<Fruit>看成一个整体,BasketForEgg实现的是Basket<Fruit>接口,
* 而不是Basket接口
*/
class BasketForFruit implements Basket<Fruit> {
int capacity = 30;
Object[] container = new Object[capacity];
int size;
@Override
public void add(Fruit fruit) {
if (size >= capacity) {
throw new RuntimeException("basket is full!");
}
container[size++] = fruit;
}
@Override
public Fruit get() {
if (size >= 0) {
return (Fruit) container[size--];
}
return null;
}
public static void main(String[] args) {
Basket basket = new BasketForFruit();
basket.add(new Fruit());
}
}
class Fruit {
}
- 装蛋的篮子
arduino
/**
* 装蛋的篮子:在实现篮子接口时,指定填充的泛型类型
* 注意这里要把Basket<Egg>看成一个整体,BasketForEgg实现的是Basket<Egg>接口,
* 而不是Basket接口
*/
class BasketForEgg implements Basket<Egg> {
int capacity = 30;
Object[] container = new Object[capacity];
int size;
public void add(Egg egg) {
if (size >= capacity) {
throw new RuntimeException("basket is full!");
}
container[size++] = egg;
}
public Egg get() {
if (size >= 0) {
return (Egg) container[size--];
}
return null;
}
public static void main(String[] args) {
BasketForEgg duckEggBasket = new BasketForEgg();
duckEggBasket.add(new DuckEgg());
}
}
泛型方法
泛型方法,是指泛型参数作用与方法上,一般是在调用方法时,通过入参或者接收返回值的引用指定具体的类型。这里以方法所在的类是非泛型类,只有方法为泛型方法的前提下为例。
csharp
public <T> void print() {
List<T> list = new ArrayList<>();
System.out.println(list.size());
}
非泛型类中的泛型方法,一般 放在返回值类型之前,public权限修饰符、static静态修饰符之后,表示该方法是一个泛型方法
泛型方法的T作用的位置有可以有三处:方法体(方法内部逻辑中)、返回值、入参。可以只作用于一处,也可以两两接结合,甚至三处同时都有,具体场景分析,如下。
- 占位符T只在入参位置 ,不参与方法体逻辑,也不影响返回值
csharp
public class GenericMethodDemo {
/**
* 只有入参传入了T, 方法体和返回值都未用到T,所以不管传入List<String>
* 还是List<Integer>,不影响此方法
*/
public static <T> void method1(List<T> list) {
System.out.println(list.size());
}
/**
* 把上面的泛型方法改成此种非泛型方法,因为没有必要
*/
public static void method11(List<?> list) {
System.out.println(list.size());
}
}
- T只出现在方法体中,入参和返回值都没有T
csharp
/**
* 占位符T在方法体中有用到,入参和返回值都没有用到
*/
public static <T> int method2() {
List<T> list = new ArrayList<T>();
return list.size();
}
/**
* 我们用占位符T,目的就是想在调用方法时填充这个T,method2中这个T毫无意义
* 所以可以直接改成非泛型方法,像method3这样
*/
public static int method3() {
List<?> list = new ArrayList<>();
return list.size();
}
- 返回值中有T,入参和方法体中没有T
csharp
//编译不同过,方法体都没有T,怎么会返回T呢,所以不存在这种说法
public static <T> T method4() {
return 1;
}
- 入参没有T,方法体和返回值都有T
csharp
/**
* 这里只有方法体中和返回值中有T,那么这个T什么时候填充的?
*/
public static <T> List<T> method5() {
List<T> list = new ArrayList<>();
return list;
}
public static void main(String[] args) {
//这里在引用接收的时候填充了T,这使得同一个方法返回多种不同类型的值
List<String> list = method5();
list.add("aa");
List<Integer> list1 = method5();
list1.add(1);
}
这种方式有什么意义呢,通过一个方法可以快速得到指定泛型的List
- 方法入参有T,返回值有T,方法体没有T
ini
public static void main(String[] args) {
//通过传入的参数来确定T的类型
List<String> list = Arrays.asList("aa", "bb");
String s = method6(list);
List<Integer> list1 = Arrays.asList(11, 22);
Integer integer = method6(list1);
}
/**
* 方法入参有T, 返回值有T,方法体中没有T
*/
public static <T> T method6(List<T> list) {
return list.get(0);
}
- 入参有T,方法体有T,但是返回值没有T
ini
/**
* 方法入参有T, 返回没有T,方法体有T
*/
public static <T> int method7(List<T> list) {
T t1 = list.get(0);
T t2 = list.get(1);
Map<String, T> map = new HashMap<>();
map.put("aaa", t1);
map.put("bbb", t2);
return map.size();
}
/**
* method8和method7区别,好像都一样,但是语义不一样
*/
public static int method8(List<?> list) {
Object t1 = list.get(0);
Object t2 = list.get(1);
Map<String, Object> map = new HashMap<>();
map.put("aaa", t1);
map.put("bbb", t2);
return map.size();
}
/**
* method9
*/
public static <T, K> boolean method9(List<T> list, List<K> list1) {
T t1 = list.get(0);
T t2 = list.get(1);
Map<String, Object> map = new HashMap<>();
map.put("aaa", t1);
map.put("bbb", t2);
K k1 = list1.get(0);
K k2 = list1.get(1);
K k3 = list1.get(2);
Map<String, Object> map1 = new HashMap<>();
map1.put("ccc", k1);
map1.put("ddd", k1);
map1.put("eee", k3);
return map.size() > map1.size();
}
如上代码所示,对比method7和method8发现,方法入参有T,方法体内用到了T,返回值没有T,这里就不会在乎T传入的是什么类型了,都不会影响方法体的执行,如果把List改成List<?>也不会有任何影响,唯一要变化的就是如method8中,把T换成Object,方法体依然正常执行,但是这样从某种意义上来说破坏了语义,例如我明明传给你的是装鸭蛋的篮子(List), 你却把装鸭蛋的篮子变成了装蛋的篮子(List), 这不是又混淆了吗?所以这里语义上要求不能变,就需要时要具体的泛型T,而不是通配符" ?"。
再例如method9方法中,有两个入参,其实都改成List<?>也不是不可以,只是语义不清晰,所以还是使用泛型方法比较好,虽说方法体执行结果都一样,但是代码语义清晰,更方便阅读、理解
总结:方法入参有T,方法体用到了T,但是返回值无T,无论List填充什么类型都不影响具体的执行结果,只是在语义上有差别
针对此种类型,举个JDK中的栗子:
csharp
interface Stream<T> {
//这里T因为接口上已经有了,先忽略,只看泛型方法中的R、A
<R, A> R collect(Collector<? super T, A, R> collector);
}
public final class Collectors {
public static Collector<CharSequence, ?, String> joining() {
return new CollectorImpl<CharSequence, StringBuilder, String>(
StringBuilder::new, StringBuilder::append,
(r1, r2) -> { r1.append(r2); return r1; },
StringBuilder::toString, CH_NOID);
}
public static Collector<CharSequence, ?, String> joining(CharSequence delimiter) {
return joining(delimiter, "", "");
}
public static <T> Collector<T, ?, List<T>> toList() {
return new CollectorImpl<>((Supplier<List<T>>) ArrayList::new, List::add,
(left, right) -> { left.addAll(right); return left; },
CH_ID);
}
}
//现在jdk8下用得最常用的Stream流API,它的collect方法是一个泛型方法,占位符有R、A,入参也有R和A,但是返回值只有R,没有A,根据上面所讲的,A这个泛型参数不在乎它具体是什么类型,即使方法体有用到A,返回值只有R,那么在转入Collector时,这个A就可以用?来代替。
//再看Collecters类中的joining()、toList()方法,返回的恰好就是Collector<CharSequence, ?, String>,用?指定了A。
- 方法人参、方法体、返回值都有T,这是最常见的一种方式
ini
public static <T> T method10(Class<T> clazz) {
T t1 = null;
try {
t1 = clazz.newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return t1;
}
泛型引用
在java语言中,数据类型可以分为两大类:基础数据类型 、引用数据类型 ,那么加上泛型参数后的引用类型,这里姑且叫做泛型引用类型,泛型引用和其他对象引用不同,它通常只能指向它自己类型的实例。
普通的泛型引用
还是以装蛋的篮子为例,这里为了简化代码,使用jdk的List类代替Basket,ArrayList这个泛型引用,只能指向装鸭蛋的对象实例,如下:
scala
/**
* 装蛋的篮子
*/
public class BasketDemo {
public static void main(String[] args) {
//ArrayList<DuckEgg> 与 ArrayList<ChickenEgg>是两个完全不同的类型
//这里编译报错,ArrayList<DuckEgg>只能指向new ArrayList<DuckEgg>()实例
ArrayList<DuckEgg> duckEggArrayList1 = new ArrayList<ChickenEgg>();
//正常通过
ArrayList<DuckEgg> duckEggArrayList = new ArrayList<DuckEgg>();
}
}
/**
* 蛋
*/
class Egg {}
/**
* 鸡蛋
*/
class ChickenEgg extends Egg {}
/**
* 鸭蛋
*/
class DuckEgg extends Egg {}
/**
* 鹅蛋
*/
class GooseEgg extends Egg {}
那么问题来了,DuckEgg是Egg的子类,那ArrayList可以指向new ArrayList()对象实例吗?
答案很明显,编译报错!这里再次强调文章开头的观点,ArrayList与ArrayList在编译器的眼里是两个完全不同的具体类,即使Egg是DuckEgg的父类,所以这里类型不匹配,编译不通过,不能这么指定。
问题又来了,由于ArrayList是实现了List接口的,那List泛型类型可以指向ArrayList吗?
typescript
public class BasketDemo {
public static void main(String[] args) {
List<Egg> duckEggArrayList = new ArrayList<Egg>();
}
}
答案是肯定的。因为ArrayList本身就实现了List接口,所以在实例化ArrayList时,填充E为Egg类型,自然而然ArrayList就实现了List接口,使用父类引用指向子类实现,这不就是咱们的多态嘛!这里把ArrayList看着是一个整体类,List看做一个整体类,ArrayList是实现了List接口的,所以这里是可以的,也是经常用得最多的方式。
那如何让一个List这样的引用,既可以指向ArrayList、又可以指向ArrayList呢?
通配符"?"泛型引用
为了让一个泛型引用,指向它的多个不同类型的实例,这里需要使用到通配符"?",通过List<? extends Egg>表示只要是Egg及其子类,它都能认识,如下:
typescript
public class BasketDemo {
public static void main(String[] args) {
//反应引用List<? extends Egg>指向了ArrayList<DuckEgg>实例
List<? extends Egg> eggList = new ArrayList<DuckEgg>();
//引用变量eggList又指向了ArrayList<ChickenEgg>实例
eggList = new ArrayList<ChickenEgg>();
//引用变量eggList又指向了ArrayList<GooseEgg>实例
eggList = new ArrayList<GooseEgg>();
}
}
如上代码所示,List<? extends Egg>在这里是一个引用标识 ,并没有任何具体的List<? extends Egg>类型,所以也不能直接使用new关键字new一个ArrayList<? extends Egg>()实例,它的作用就是扩大泛型引用的范围,它可以指向多个填充不同类型的具体类,如:ArrayList、ArrayList()、ArrayList、ArrayList。
那么问题来了,像List<? extends Egg>这样的引用指向的实例对象,可以正常CRUD吗?
如上代码所示,在往List<? extends Egg>里面添加一个new Egg()时,编译器提示错误,这是因为List<? extends Egg>是一个泛型引用,在使用eggList这个引用变量添加元素时,还是会受到泛型引用接口的限定,它可能指向一个装鸭蛋的list实例,也可能指向一个装鸡蛋的list实例,它到底指向哪个具体的类型,它不知道,所以这里它不敢乱添加,但是它知道我这里面装的就是Egg类型,get出来的一定是Egg类型。
这就是常见的上下边界问题,通过这种方式是为了扩大泛型引用的范围。当然还有个List<? super Egg> 泛型引用,和extends一样,只不过方向相反。
问题又双叒来了,像这样的List<? extends Egg>的泛型引用,不能添加元素,不能修改原素,又有什么意义呢?
typescript
public class GenericTest {
public static void main(String[] args) {
List<DuckEgg> duckEggs = new ArrayList<DuckEgg>();
List<ChickenEgg> chickenEggs = new ArrayList<ChickenEgg>();
iterateList(duckEggs);
iterateList(chickenEggs);
}
/**
* 主要目的用于非修改遍历
*/
public static void iterateList(List<? extends Egg> list) {
for (Egg egg : list) {
System.out.println(egg);
}
}
}
如上代码所示,由于List<? extends Egg> list可以指向多个具体的实例,又不能对其进行修改,所以这里的主要目的就是非修改遍历。
再看看List<?>这个泛型引用,这个 ?没有上下边界了,它不知道指向具体哪个类型,所以它泛化的对象就是Object,在Java里面Object就是根对象,是一切对象的父对象,所以通过调用get方法,得到的必然也是Object类型的。
typescript
public class GenericTest {
public static void main(String[] args) {
List<DuckEgg> duckEggs = new ArrayList<DuckEgg>();
List<ChickenEgg> chickenEggs = new ArrayList<ChickenEgg>();
iterateList(duckEggs);
iterateList(chickenEggs);
}
/**
* List<?> 更进一步,可以指向填充任意类型的实例对象
*/
public static void iterateList(List<?> list) {
for (Object obj : list) {
System.out.println(obj);
}
}
}
获取泛型类型
有的时候需要获取到具体的泛型类型来做一些业务判断,或者在研发一些框架时,需要获取到该类填充的具体类型,所以有必要了解下如何获取泛型类型。
- 如何获取本类的泛型具体类型?
csharp
public class GenericTest {
public static void main(String[] args) {
GenericTest genericTest = new GenericTest();
genericTest.getGenericType();
}
public void getGenericType() {
//这里在编译期指定为鸭蛋,但是运行期,会被擦除,如:BasketForEgg basketForEgg = new BasketForEgg();
//所以这样是拿不到具体的泛型类型的。
//BasketForEgg<DuckEgg> basketForEgg = new BasketForEgg<>();
BasketForEgg<DuckEgg> basketForEgg = new BasketForEgg<>(DuckEgg.class);
System.out.println(basketForEgg.getClazz().getSimpleName()); //DuckEgg
}
/**
* 装蛋的篮子
*/
class BasketForEgg<T> {
//用于获取具体的泛型类型,无其他作用
Class<T> clazz;
public BasketForEgg(Class<T> clazz) {
this.clazz = clazz;
}
public Class<T> getClazz() {
return clazz;
}
}
class Egg{}
class DuckEgg extends Egg{}
}
- 如何获取父类或者父接口的泛型类型?
scala
public class GenericTest {
public static void main(String[] args) {
GenericTest genericTest = new GenericTest();
genericTest.getGenericType();
genericTest.getGenericType1();
}
/**
* 获取父类泛型类型
*/
public void getGenericType() {
BasketForDuckEgg basketForDuckEgg = new BasketForDuckEgg();
//通过class对象获取父类泛型参数类型,ParameterizedType是Type子类,所以这里需要强转一下
ParameterizedType superclass = (ParameterizedType)basketForDuckEgg.getClass().getGenericSuperclass();
//因为泛型参数可能有多个,所以这里拿到实际的泛型类型为一个数组
Type[] actualTypeArguments = superclass.getActualTypeArguments();
//得到结果:[class com.kang.mybatis.study.sort.GenericTest$DuckEgg]
System.out.println(Arrays.toString(actualTypeArguments));
}
/**
* 获取父接口的泛型类型
*/
public void getGenericType1() {
BasketForChickenEgg basketForChickenEgg = new BasketForChickenEgg();
//通过class对象获取父接口泛型参数类型,存在实现多个接口的情况
Type[] genericInterfaces = basketForChickenEgg.getClass().getGenericInterfaces();
for (Type genericInterface : genericInterfaces) {
ParameterizedType parameterizedType = (ParameterizedType) genericInterface;
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
//执行结果就是鸡蛋啦:[class com.kang.mybatis.study.sort.GenericTest$ChickenEgg]
System.out.println(Arrays.toString(actualTypeArguments));
}
}
/**
* 装鸭蛋的篮子
*/
class BasketForDuckEgg extends BasketForEgg<DuckEgg> {
}
/**
* 装鸡蛋的篮子
*/
class BasketForChickenEgg implements Basket<ChickenEgg> {
}
interface Basket<T> {}
/**
* 装蛋的篮子
*/
class BasketForEgg<T> {
}
class Egg{}
class DuckEgg extends Egg{}
class ChickenEgg extends Egg{}
}
- 泛型父类如何获取到自己的具体泛型类型呢?
scala
public class GenericTest {
public static void main(String[] args) {
BasketForDuckEgg basketForDuckEgg = new BasketForDuckEgg();
basketForDuckEgg.getGenericType();
}
/**
* 装鸭蛋的篮子
*/
static class BasketForDuckEgg extends BasketForEgg<DuckEgg> {
/**
* 通过子类获取填充的父类泛型类型给父类使用
*/
public void getGenericType() {
//BasketForEgg<DuckEgg>想要获取自己的泛型类型,通过其子类来实现
ParameterizedType parameterizedType = (ParameterizedType) this.getClass().getGenericSuperclass();
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
Class clazz = (Class) actualTypeArguments[0];
System.out.println(clazz.getSimpleName());
super.setClazz(clazz);
}
}
/**
* 装蛋的篮子
*/
static class BasketForEgg<T> {
//通过一个class变量来获取子类实现时指定的泛型类型
Class<T> clazz;
/**
* 子类调用
* @param clazz
*/
public void setClazz(Class<T> clazz) {
this.clazz = clazz;
System.out.println(clazz.getName());
}
}
class Egg{}
class DuckEgg extends Egg{}
class ChickenEgg extends Egg{}
}
- 总结
-
- 对于直接获取本类的泛型具体类型,通过一个成员变量来保存泛型的具体类型,因为泛型参数是通过new关键字来确定的,在运行期泛型已经被擦除了,所以是拿不到自己本类的泛型具体类型的;
- 而某个类继承某个泛型类或者实现某个泛型接口时,是可以直接拿到泛型父类/父接口,这是因为在编译期通过 extends、implements时确定尖括号这个T的类型的,编译后会把这个T类型保留在字节码里;
- 为什么会有获取具体泛型类型这样的需求?在一些开源框架中,随处可见这样的用法,例如spring的泛型注入,mybatis、hibernate等根据泛型类型做orm映射,还有咱们在导出Excel时,List也需要用到具体的泛型类型,来确定导出时列名称等。
泛型原理
这个就不用多说了,泛型目前针对java来说,就是一种语法糖,用来骗骗编译器的,把类型转化风险提前放在编译期解决。为了兼容老版本的jdk,实际的泛型会被擦除,所以编译后的class和之前的版本没什么区别。
展望未来,说不定以后的jdk更新版本中,会保留真实的泛型类型类,同C++一样。如ArrayList与ArrayList就是两个不同的类了呢。
在C++语言是保留了真实的泛型类的,只是目前java还没有