本文最后更新于:8 个月前
                  
                
              
            
            
              
                
                破冰
 🌭 我真的了解泛型吗? - 掘金 (juejin.cn)
思维碰撞
简单概述
- 简单总结下泛型相关内容,后续找时间再系统学习(2023/10/27晚)- 
- 泛型类
- 泛型接口
- 泛型方法:返回值、入参位置、方法体中
 
- 我对泛型的理解:
泛型很好理解:我们经常讲到一个对象实例的就是以类作为模板创建的,那么也可以讲一个普通类可以以泛型类作为模板;那么泛型是用来干嘛的呢,我们为什么要使用泛型呢?其实,所有的泛型类在编译后生成的字节码与普通类无异,因为在编译前,所有泛型类型就被擦除了。所以我们可以把泛型看作一个语法糖,将类型转换的校验提前在编译时,减少类型转换错误的发生,使编写的程序更加具有健壮性。
🍖 AI 总结(2023/10/13晚)
泛型是Java语言中的一项强大的特性,它允许在编译时指定类、接口或方法的参数类型,从而在编译阶段就能够进行类型检查。这样可以减少类型转换的错误,并提高代码的安全性和可读性。
通过使用泛型,我们可以在编译时捕捉到一些类型错误,而不是在运行时才发现,这样可以避免一些潜在的bug。泛型还可以增加代码的可重用性和灵活性,因为泛型类、接口和方法可以用于多种不同的类型,而无需针对每一种类型单独编写或重复编写相似的代码。
总的来说,通过使用泛型,我们可以在编写Java代码时更好地约束和使用类型信息,减少类型错误,提高代码的可读性和健壮性。
👏 这一批代码比较全面的展示了泛型的各种使用场景了
| 12
 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);
 }
 
 
 | 
| 12
 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
| 12
 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);
 }
 }
 
 
 
 | 
| 12
 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这类编辑器工具提供编译检查的话,真让我手写代码话,可能会写出让人笑掉大牙的代码。于是,再次回头来学习一下泛型知识。
泛型由来
很久很久以前。。。
记得小时候,奶奶提了一篮子鸡蛋去赶集卖鸡蛋,结果把几个老鸭蛋也卖了。原来是不经意间把几个鸭蛋混在鸡蛋一起了,幸好没有把鹅蛋也当做鸡蛋卖了,鹅蛋可比鸡蛋贵很多哦。于是,为了便于区分,爷爷就专门用竹子编织了几个不同的篮子专门用于盛放鸡蛋、鸭蛋、鹅蛋。用代码体现,如下:
| 12
 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;
 }
 }
 
 | 
| 12
 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;
 }
 }
 
 | 
| 12
 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还没有泛型,哪怕只有一个变量的类型不一样,也得写出不同类来区分,比如后面家里养了鸽子,得编织一个专门盛放鸽子蛋的篮子,再后来家里养了鸵鸟(这个有点夸张了,哈哈),又得编织一个专门盛放鸵鸟蛋的篮子。这样一直下去的话,就必须编织成百上千个不同类型的篮子了。
此时,你肯定会说,直接搞一个专门盛放蛋的篮子,不管什么蛋都可以装,如下:
| 12
 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的具体类型
- 如果子类在继承该泛型类时,不填充具体的类型,那么子类也可以是泛型类
| 12
 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作用域在整个接口中,在某个具体的类实现泛型接口时,填充具体的泛型类型;在子类接口继承泛型接口时,可以填充具体的参数类型,或者不填充,该子类接口也可以是泛型接口。如下:
| 12
 3
 4
 5
 6
 7
 
 | csharp复制代码
 
 interface Basket<T> {
 void add(T t);
 T get();
 }
 
 | 
| 12
 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 {
 
 }
 
 | 
| 12
 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());
 }
 }
 
 | 
泛型方法
泛型方法,是指泛型参数作用与方法上,一般是在调用方法时,通过入参或者接收返回值的引用指定具体的类型。这里以方法所在的类是非泛型类,只有方法为泛型方法的前提下为例。
| 12
 3
 4
 
 | csharp复制代码public <T> void print() {List<T> list = new ArrayList<>();
 System.out.println(list.size());
 }
 
 | 
非泛型类中的泛型方法,一般 放在返回值类型之前,public权限修饰符、static静态修饰符之后,表示该方法是一个泛型方法
泛型方法的T作用的位置有可以有三处:方法体(方法内部逻辑中)、返回值、入参。可以只作用于一处,也可以两两接结合,甚至三处同时都有,具体场景分析,如下。
- 占位符T只在入参位置,不参与方法体逻辑,也不影响返回值
| 12
 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());
 }
 
 }
 
 | 
| 12
 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();
 }
 
 | 
| 12
 3
 4
 
 | csharp复制代码public static <T> T method4() {
 return 1;
 }
 
 | 
| 12
 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
| 12
 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);
 }
 
 | 
| 12
 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中的栗子:
| 12
 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,这是最常见的一种方式
| 12
 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这个泛型引用,只能指向装鸭蛋的对象实例,如下:
| 12
 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吗?
| 12
 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及其子类,它都能认识,如下:
| 12
 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>的泛型引用,不能添加元素,不能修改原素,又有什么意义呢?
| 12
 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类型的。
| 12
 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);
 }
 }
 }
 
 | 
获取泛型类型
有的时候需要获取到具体的泛型类型来做一些业务判断,或者在研发一些框架时,需要获取到该类填充的具体类型,所以有必要了解下如何获取泛型类型。
- 如何获取本类的泛型具体类型?
| 12
 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{}
 }
 
 | 
- 如何获取父类或者父接口的泛型类型?
| 12
 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{}
 }
 
 | 
- 泛型父类如何获取到自己的具体泛型类型呢?
| 12
 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就是两个不同的类了呢。