本文最后更新于:6 个月前
破冰
🌭 我真的了解泛型吗? - 掘金 (juejin.cn)
思维碰撞
简单概述
简单总结下泛型相关内容,后续找时间再系统学习(2023/10/27晚)
- 泛型类
- 泛型接口
- 泛型方法:返回值、入参位置、方法体中
我对泛型的理解:
泛型很好理解:我们经常讲到一个对象实例的就是以类作为模板创建的,那么也可以讲一个普通类可以以泛型类作为模板;那么泛型是用来干嘛的呢,我们为什么要使用泛型呢?其实,所有的泛型类在编译后生成的字节码与普通类无异,因为在编译前,所有泛型类型就被擦除了。所以我们可以把泛型看作一个语法糖,将类型转换的校验提前在编译时,减少类型转换错误的发生,使编写的程序更加具有健壮性。
🍖 AI 总结(2023/10/13晚)
泛型是Java语言中的一项强大的特性,它允许在编译时指定类、接口或方法的参数类型,从而在编译阶段就能够进行类型检查。这样可以减少类型转换的错误,并提高代码的安全性和可读性。
通过使用泛型,我们可以在编译时捕捉到一些类型错误,而不是在运行时才发现,这样可以避免一些潜在的bug。泛型还可以增加代码的可重用性和灵活性,因为泛型类、接口和方法可以用于多种不同的类型,而无需针对每一种类型单独编写或重复编写相似的代码。
总的来说,通过使用泛型,我们可以在编写Java代码时更好地约束和使用类型信息,减少类型错误,提高代码的可读性和健壮性。
👏 这一批代码比较全面的展示了泛型的各种使用场景了
1 2 3 4 5 6 7 8
| package entity.c;
public interface EndA<T> { void fun_1(T t);
<R> void fun_2(T t, R r); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| package entity.c;
public class EndB<T> { public <R> EndB(T t, R r) { }
}
|
泛型类 EndC<T, E> extends EndB implements EndA
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| package entity.c;
public class EndC<T, E> extends EndB<T> implements EndA<E> { @Override public void fun_1(E t) {
}
@Override public <R> void fun_2(E e, R r) {
}
public <R> void fun_3(T t, R r) { }
public <R> EndC(T t, R r) { super(t, r); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| package entity.c;
public class Result { public static void main(String[] args) { EndB<String> endB = new EndB<>("", 18);
EndC<String, Double> EndC_1 = new EndC<>("", 10);
EndC<Integer, Double> EndC_2 = new EndC<>(10, 10); } }
|
转载内容
曾经年少轻狂的我,以为自己已经轻松拿捏Java泛型,直到后来学习Java 8新特性时,被泛型轻松反拿捏!那时的心情,正如标题一样,自我怀疑:“我真的了解泛型吗?” 答案很明显,不了解!要不是有eclipse/idea这类编辑器工具提供编译检查的话,真让我手写代码话,可能会写出让人笑掉大牙的代码。于是,再次回头来学习一下泛型知识。
泛型由来
很久很久以前。。。
记得小时候,奶奶提了一篮子鸡蛋去赶集卖鸡蛋,结果把几个老鸭蛋也卖了。原来是不经意间把几个鸭蛋混在鸡蛋一起了,幸好没有把鹅蛋也当做鸡蛋卖了,鹅蛋可比鸡蛋贵很多哦。于是,为了便于区分,爷爷就专门用竹子编织了几个不同的篮子专门用于盛放鸡蛋、鸭蛋、鹅蛋。用代码体现,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| 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; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| 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; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| 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还没有泛型,哪怕只有一个变量的类型不一样,也得写出不同类来区分,比如后面家里养了鸽子,得编织一个专门盛放鸽子蛋的篮子,再后来家里养了鸵鸟(这个有点夸张了,哈哈),又得编织一个专门盛放鸵鸟蛋的篮子。这样一直下去的话,就必须编织成百上千个不同类型的篮子了。
此时,你肯定会说,直接搞一个专门盛放蛋的篮子,不管什么蛋都可以装,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| 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的具体类型
- 如果子类在继承该泛型类时,不填充具体的类型,那么子类也可以是泛型类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| 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()); duckEggBasket.add(new ChickenEgg()); }
class SubBasketForEgg extends BasketForEgg<DuckEgg> {}
class SubBasketForEgg2<T> extends BasketForEgg<T> {} }
|
泛型接口
泛型参数T写在类接口名的后面,通过尖括号表示。同泛型类基本一致,泛型参数T作用域在整个接口中,在某个具体的类实现泛型接口时,填充具体的泛型类型;在子类接口继承泛型接口时,可以填充具体的参数类型,或者不填充,该子类接口也可以是泛型接口。如下:
1 2 3 4 5 6 7
| csharp复制代码
interface Basket<T> { void add(T t); T get(); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| typescript复制代码
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 {
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| arduino复制代码
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()); } }
|
泛型方法
泛型方法,是指泛型参数作用与方法上,一般是在调用方法时,通过入参或者接收返回值的引用指定具体的类型。这里以方法所在的类是非泛型类,只有方法为泛型方法的前提下为例。
1 2 3 4
| csharp复制代码public <T> void print() { List<T> list = new ArrayList<>(); System.out.println(list.size()); }
|
非泛型类中的泛型方法,一般 放在返回值类型之前,public权限修饰符、static静态修饰符之后,表示该方法是一个泛型方法
泛型方法的T作用的位置有可以有三处:方法体(方法内部逻辑中)、返回值、入参。可以只作用于一处,也可以两两接结合,甚至三处同时都有,具体场景分析,如下。
- 占位符T只在入参位置,不参与方法体逻辑,也不影响返回值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| csharp复制代码public class GenericMethodDemo {
public static <T> void method1(List<T> list) { System.out.println(list.size()); }
public static void method11(List<?> list) { System.out.println(list.size()); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| csharp复制代码
public static <T> int method2() { List<T> list = new ArrayList<T>(); return list.size(); }
public static int method3() { List<?> list = new ArrayList<>(); return list.size(); }
|
1 2 3 4
| csharp复制代码 public static <T> T method4() { return 1; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| csharp复制代码
public static <T> List<T> method5() { List<T> list = new ArrayList<>(); return list; }
public static void main(String[] args) { List<String> list = method5(); list.add("aa"); List<Integer> list1 = method5(); list1.add(1);
}
|
这种方式有什么意义呢,通过一个方法可以快速得到指定泛型的List
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| ini复制代码public static void main(String[] args) { List<String> list = Arrays.asList("aa", "bb"); String s = method6(list);
List<Integer> list1 = Arrays.asList(11, 22); Integer integer = method6(list1); }
public static <T> T method6(List<T> list) { return list.get(0); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| ini复制代码
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(); }
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(); }
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中的栗子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| csharp复制代码interface Stream<T> { <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); } }
|
- 方法人参、方法体、返回值都有T,这是最常见的一种方式
1 2 3 4 5 6 7 8 9 10 11
| 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这个泛型引用,只能指向装鸭蛋的对象实例,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| scala复制代码
public class BasketDemo { public static void main(String[] args) { 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吗?
1 2 3 4 5
| 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及其子类,它都能认识,如下:
1 2 3 4 5 6 7 8 9 10
| typescript复制代码public class BasketDemo { public static void main(String[] args) { List<? extends Egg> eggList = new ArrayList<DuckEgg>(); eggList = new ArrayList<ChickenEgg>(); 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>的泛型引用,不能添加元素,不能修改原素,又有什么意义呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| 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类型的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| 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<?> list) { for (Object obj : list) { System.out.println(obj); } } }
|
获取泛型类型
有的时候需要获取到具体的泛型类型来做一些业务判断,或者在研发一些框架时,需要获取到该类填充的具体类型,所以有必要了解下如何获取泛型类型。
- 如何获取本类的泛型具体类型?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| csharp复制代码public class GenericTest { public static void main(String[] args) { GenericTest genericTest = new GenericTest(); genericTest.getGenericType(); } public void getGenericType() {
BasketForEgg<DuckEgg> basketForEgg = new BasketForEgg<>(DuckEgg.class); System.out.println(basketForEgg.getClazz().getSimpleName());
}
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{} }
|
- 如何获取父类或者父接口的泛型类型?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
| 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(); ParameterizedType superclass = (ParameterizedType)basketForDuckEgg.getClass().getGenericSuperclass(); Type[] actualTypeArguments = superclass.getActualTypeArguments(); System.out.println(Arrays.toString(actualTypeArguments)); }
public void getGenericType1() { BasketForChickenEgg basketForChickenEgg = new BasketForChickenEgg(); Type[] genericInterfaces = basketForChickenEgg.getClass().getGenericInterfaces(); for (Type genericInterface : genericInterfaces) { ParameterizedType parameterizedType = (ParameterizedType) genericInterface; Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); 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{} }
|
- 泛型父类如何获取到自己的具体泛型类型呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| 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() { 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<T> 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就是两个不同的类了呢。