java 泛型全解 - 绝对最详细

public class A extends Container<Integer, String> {}

  • 也可以不指定具体的类型,系统就会把K,V形参当成Object类型处理

public class A extends Container {}

2.6 泛型构造器

  • 构造器也是一种方法,所以也就产生了所谓的泛型构造器。

  • 和使用普通方法一样没有区别,一种是显示指定泛型参数,另一种是隐式推断

public class Person {

public Person(T t) {

System.out.println(t);

}

}

复制代码

使用:

public static void main(String[] args) {

new Person(22);// 隐式

new Person(“hello”);//显示

}

复制代码

  • 特殊说明:

  • 如果构造器是泛型构造器,同时该类也是一个泛型类的情况下应该如何使用泛型构造器:因为泛型构造器可以显式指定自己的类型参数(需要用到菱形,放在构造器之前),而泛型类自己的类型实参也需要指定(菱形放在构造器之后),这就同时出现了两个菱形了,这就会有一些小问题,具体用法再这里总结一下。 以下面这个例子为代表

public class Person {

public Person(T t) {

System.out.println(t);

}

}

复制代码

正确用法:

public static void main(String[] args) {

Person person = new Person(“sss”);

}

复制代码

PS:编译器会提醒你怎么做的

2.7 高级通配符

2.7.1背景:

2.7.2 <? extends T> 上界通配符

  • 上界通配符顾名思义,<? extends T>表示的是类型的上界【包含自身】,因此通配的参数化类型可能是T或T的子类。

  • 正因为无法确定具体的类型是什么,add方法受限(可以添加null,因为null表示任何类型),但可以从列表中获取元素后赋值给父类型。如上图中的第一个例子,第三个add()操作会受限,原因在于List和List是List<? extends Animal>的子类型。

它表示集合中的所有元素都是Animal类型或者其子类

List<? extends Animal>

复制代码

  • 这就是所谓的上限通配符,使用关键字extends来实现,实例化时,指定类型实参只能是extends后类型的子类或其本身。

  • 例如:

  • 这样就确定集合中元素的类型,虽然不确定具体的类型,但最起码知道其父类。然后进行其他操作。

//Cat是其子类

List<? extends Animal> list = new ArrayList();

复制代码

2.7.3 <? super T> 下界通配符

  • 下界通配符<? super T>表示的是参数化类型是T的超类型(包含自身),层层至上,直至Object

  • 编译器无从判断get()返回的对象的类型是什么,因此get()方法受限。但是可以进行add()方法,add()方法可以添加T类型和T类型的子类型,如第二个例子中首先添加了一个Cat类型对象,然后添加了两个Cat子类类型的对象,这种方法是可行的,但是如果添加一个Animal类型的对象,显然将继承的关系弄反了,是不可行的。

它表示集合中的所有元素都是Cat类型或者其父类

List <? super Cat>

复制代码

  • 这就是所谓的下限通配符,使用关键字super来实现,实例化时,指定类型实参只能是extends后类型的子类或其本身

  • 例如

//Animal是其父类

List<? super Cat> list = new ArrayList();

复制代码

2.7.4 <?> 无界通配符

  • 任意类型,如果没有明确,那么就是Object以及任意的Java类了

  • 无界通配符用<?>表示,?代表了任何的一种类型,能代表任何一种类型的只有null(Object本身也算是一种类型,但却不能代表任何一种类型,所以List和List的含义是不同的,前者类型是Object,也就是继承树的最上层,而后者的类型完全是未知的)


3、泛型擦除


3.1 概念

编译器编译带类型说明的集合时会去掉类型信息

3.2 验证实例:

public class GenericTest {

public static void main(String[] args) {

new GenericTest().testType();

}

public void testType(){

ArrayList collection1 = new ArrayList();

ArrayList collection2= new ArrayList();

System.out.println(collection1.getClass()==collection2.getClass());

//两者class类型一样,即字节码一致

System.out.println(collection2.getClass().getName());

//class均为java.util.ArrayList,并无实际类型参数信息

}

}

复制代码

  • 输出结果:

true

java.util.ArrayList

复制代码

  • 分析:

  • 这是因为不管为泛型的类型形参传入哪一种类型实参,对于Java来说,它们依然被当成同一类处理,在内存中也只占用一块内存空间。从Java泛型这一概念提出的目的来看,其只是作用于代码编译阶段,在编译过程中,对于正确检验泛型结果后,会将泛型的相关信息擦出,也就是说,成功编译过后的class文件中是不包含任何泛型信息的。泛型信息不会进入到运行时阶段。

  • 在静态方法、静态初始化块或者静态变量的声明和初始化中不允许使用类型形参。由于系统中并不会真正生成泛型类,所以instanceof运算符后不能使用泛型类


4、泛型与反射


  • 把泛型变量当成方法的参数,利用Method类的getGenericParameterTypes方法来获取泛型的实际类型参数

  • 例子:

public class GenericTest {

public static void main(String[] args) throws Exception {

getParamType();

}

/利用反射获取方法参数的实际参数类型/

public static void getParamType() throws NoSuchMethodException{

Method method = GenericTest.class.getMethod(“applyMap”,Map.class);

//获取方法的泛型参数的类型

Type[] types = method.getGenericParameterTypes();

System.out.println(types[0]);

//参数化的类型

ParameterizedType pType = (ParameterizedType)types[0];

//原始类型

System.out.println(pType.getRawType());

//实际类型参数

System.out.println(pType.getActualTypeArguments()[0]);

System.out.println(pType.getActualTypeArguments()[1]);

}

/供测试参数类型的方法/

public static void applyMap(Map<Integer,String> map){

}

}

复制代码

  • 输出结果:

java.util.Map<java.lang.Integer, java.lang.String>

interface java.util.Map

class java.lang.Integer

class java.lang.String

复制代码

  • 通过反射绕开编译器对泛型的类型限制

public static void main(String[] args) throws Exception {

//定义一个包含int的链表

ArrayList al = new ArrayList();

al.add(1);

al.add(2);

//获取链表的add方法,注意这里是Object.class,如果写int.class会抛出NoSuchMethodException异常

Method m = al.getClass().getMethod(“add”, Object.class);

//调用反射中的add方法加入一个string类型的元素,因为add方法的实际参数是Object

m.invoke(al, “hello”);

System.out.println(al.get(2));

}

复制代码


5 泛型的限制


5.1 模糊性错误

  • 对于泛型类User<K,V>而言,声明了两个泛型类参数。在类中根据不同的类型参数重载show方法。

public class User<K, V> {

public void show(K k) { // 报错信息:‘show(K)’ clashes with ‘show(V)’; both methods have same erasure

}

public void show(V t) {

}

}

复制代码

由于泛型擦除,二者本质上都是Obejct类型。方法是一样的,所以编译器会报错。

换一个方式:

public class User<K, V> {

public void show(String k) {

}

public void show(V t) {

}

}

复制代码

使用结果:

可以正常的使用

5.2 不能实例化类型参数

编译器也不知道该创建那种类型的对象

public class User<K, V> {

private K key = new K(); // 报错:Type parameter ‘K’ cannot be instantiated directly

}

复制代码

5.3 对静态成员的限制

静态方法无法访问类上定义的泛型;如果静态方法操作的类型不确定,必须要将泛型定义在方法上。

如果静态方法要使用泛型的话,必须将静态方法定义成泛型方法

public class User {

//错误

private static T t;

//错误

public static T getT() {

return t;

}

//正确

public static void test(K k) {

}

}

复制代码

5.4 对泛型数组的限制

写在最后

学习技术是一条慢长而艰苦的道路,不能靠一时激情,也不是熬几天几夜就能学好的,必须养成平时努力学习的习惯。所以:贵在坚持!

最后再分享的一些BATJ等大厂20、21年的面试题,把这些技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,上面只是以图片的形式给大家展示一部分。

蚂蚁金服三面直击面试官的Redis三连,Redis面试复习大纲在手,不慌

Mybatis面试专题

蚂蚁金服三面直击面试官的Redis三连,Redis面试复习大纲在手,不慌

MySQL面试专题

蚂蚁金服三面直击面试官的Redis三连,Redis面试复习大纲在手,不慌

并发编程面试专题

定义在方法上。

如果静态方法要使用泛型的话,必须将静态方法定义成泛型方法

public class User {

//错误

private static T t;

//错误

public static T getT() {

return t;

}

//正确

public static void test(K k) {

}

}

复制代码

5.4 对泛型数组的限制

写在最后

学习技术是一条慢长而艰苦的道路,不能靠一时激情,也不是熬几天几夜就能学好的,必须养成平时努力学习的习惯。所以:贵在坚持!

最后再分享的一些BATJ等大厂20、21年的面试题,把这些技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,上面只是以图片的形式给大家展示一部分。

[外链图片转存中…(img-nRnsf3Tj-1719261720020)]

Mybatis面试专题

[外链图片转存中…(img-uu5vN6OP-1719261720021)]

MySQL面试专题

[外链图片转存中…(img-bOajwNRH-1719261720022)]

并发编程面试专题

  • 10
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值