HarmonyOS鸿蒙最全2024年了面试官仍然在考泛型,你确定不了解下么(2),2024年最新拥有百万粉丝的大牛讲述学HarmonyOS鸿蒙的历程

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!


img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上鸿蒙开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

需要这份系统化的资料的朋友,可以戳这里获取

list.add(“aaaa”);
System.out.println(list.get(0)); //list.get(0)得到是 Object,还是 String 呢?
list.add(1);//会报错,为什么呢?

看看字节码一探究竟

注意看图中标注的地方,编译器除了类型擦除,在必要之处,还插入类型转换以保持类型安全(checkCast()方法)

另外除了类型强转,如果涉及到使用泛型类型作为参数的方法,子类做了重写,编译器还会自动生成桥方法以在扩展时保持多态性(Bridge 关键字)

例如:

//父类
public class DRCommonGeneric {
T t;

public void setT(T t) {
this.t = t;
}
}

//子类继承泛型类,将泛型实例化为String
public class DRString extends DRCommonGeneric {

//同时重写了setT
@Override
public void setT(String s) {
super.setT(s);
}
}

泛型真的全部被擦除了吗?

为啥有如此疑问,是因为泛型实际上是支持反射的!!!

拿上文中的 DRCommonGeneric.setT(T t)举例,虽然擦除为 object, 但是通过反射还是能拿到 T 这个我们定义的泛型类型,说明 T 这个参数类型也是在的。

try {
Method method = DRCommonGeneric.class.getMethod(“setT”, Object.class);
Type[] types = method.getGenericParameterTypes();
for (Type type : types) {
System.out.println(“参数类型:”+type);

}
} catch (NoSuchMethodException e) {
e.printStackTrace();
}

输出:

另外如果泛型声明的时候指定了具体的类型,也是可以拿到使用时定义的真实类型。 例如:

// Test.class 中声名了这个变量,T 被明切指定为String
DRCommonGeneric dr = new DRCommonGeneric<>();

//反射获取 Test.class 中的 dr 变量
Field field = Test.class.getDeclaredField(“dr”);
//获取泛型参数真实类型
ParameterizedType pType = (ParameterizedType) field.getGenericType();
System.out.println(pType);

输出:

弱小无助的我,在对天呐喊:不是全部擦除了么?反射那是怎么拿到的!!!反射难道是牛到可以从无到有么?!!!

反射表示这个锅他不背。那咱们还是再仔细看看泛型的字节码实现。

通过搜索,我发现字节码文件中确实有泛型类型 [图片上传失败…(image-70cdd3-1677116394697)]

都是signaturedeclaration 在用。原先看到添加了//注释,我就给忽略了,看来并非这么简单,再次触及知识盲区,我还是浅薄了啊。

截图中它放入注释中,实际上是 IDE 工具的优化,目的方便集中在 code 上去看了,实际上flags/signature都是属于元数据。(换个“反汇编”工具发现的这一点…),declaration 没有查询到,应该也是这个IDE的自我发挥,不用管,主要核心还是看 signature 的逻辑。

signature 是什么

在JVM中,所有的字段、方法、类都有属于自己的签名。比如方法签名由方法名、参数、访问修饰符等构成,用于确定唯一的方法。而类的签名主要是记录一些JVM类型系统以外的额外的类型信息,比如泛型的类型信息,JVM不支持泛型,但是提供了class signature来存储类泛型的类型信息。

在代码层面,实际上它是个属性,

JDK的源码中可以找到相关证据

  • java.lang.reflect.Field
  • java.lang.reflect.Method
  • java.lang.reflect.Constructor

其中都有一个成员变量 signature,注释都写的一摸一样,支持泛型和注解。

// Generics and annotations support
private transient String signature;

可以看到 signature 就是个字符串,所以不同的签名有一定的解析规则,主要的逻辑在 sun.reflect.generics.parser.SignatureParser 类中。

签名的实现有两个,分别是方法签名MethodTypeSignature和类签名ClassSignature,变量虽然也有签名,但是相对来说比较简单,没有专门的解析类。

为了方便理解,咱们还用上文例子,进行介绍

方法签名: 方法声明:public void setT(T t) 签名字符串:(TT;)V

解析出来分为四个部分:

  • 泛型定义:无 (泛型方法才会有)
  • 参数类型: T
  • 返回值类型:V 无返回值
  • 异常类型:无

类签名: 类声明:public class DRCommonGeneric 签名字符串:<T:Ljava/lang/Object;>Ljava/lang/Object; 解析出来分为三个部分:

  • 泛型定义:<T:Ljava/lang/Object;>
  • 继承的类:无
  • 实现的借口:无

签名的创建和保存?

编译时就会生成签名。 这个就是例子的 class signature :

保存在常量池中

签名的解析时机?

咱们通过反射调用泛型有关的API时,例如 method.getGenericParameterTypes() 就会触发到签名的解析,以拿到正确的参数类型。

例如反射调用setT(T t)方法, 主要流程就是:

  1. 通过 hasGenericInformation 判断方法签名中(TT;)V是否有存在泛型
  2. 通过 getGenericInfo() 解析方法签名字符串,填充到对应的变量 tree 中 。
  3. computeParameterTypes 方法中,将解析好的方法签名 tree,通过 getParameterTypes 获取到方法参数类型数组,也就是 [T]。
  4. 此时还不知道T的实现类是什么,所以再通过解析类签名<T:Ljava/lang/Object;>Ljava/lang/Object; ,获取T的实际类型java/lang/Object
  5. 放入parameterTypes 数组中返回

以上就是T这个类型参数的保存和解释,上面的例子中其实还举例了 DRCommonGeneric<String> 反射可以拿到String 而不是 Object。也是一样的道理,将泛型信息放入了 signature 中,从而得到了保存。

GSON 针对类型擦除的处理设计

著名的json解析工具 Gson,在反序列化的时候,也是使用同样的原理来支持泛型的序列化。

例如泛序列化List<T> 这类泛型集合:

List resultList = gson.fromJson(json, new TypeToken<List>() {
}.getType());

new TypeToken<List<xxx>>() {}.getType() 这行代码,重点是要注意到大括号 {},这是说明它不是一个实例化了一个对象,而是创建了一个匿名内部类,而且继承于TypeToken

TypeToken 内部也是使用泛型反射的API,区别在于使用了getClass().getGenericSuperclass(),先获得带有泛型的父类,然后再去拿泛型。

这里特殊设计的地方是使用了匿名内部类的方式,为什么要这么设计呢?

目的是为了实现完整的泛型声明。

  • 使用匿名内部类实现:TypeToken<List<xxx>> typeToken = new TypeToken的匿名子类(); 声明了泛型
  • 无匿名内部类实现: new TypeToken<List<xxx>> 未声明泛型

需要明确的是,编译器只会将声明类型的泛型信息进行保存,放在 signature 属性中。

//这是一个完整的泛型使用方式
List list = new ArrayList<>();

相对比而言,下面就没有声明泛型,用的是原始类型List,编译器不认为这个是个泛型需要特别处理

//左侧没有声明泛型类型
List list = new ArrayList();

字节码验证如下:

是不是又get到了一些小细节~

总结

类型擦除的工作过程为:

  1. 检查泛型类型,获取目标类型
  2. 擦除类型变量,并替换为限定类型
  • 如果泛型类型的类型变量没有限定(),则用Object作为原始类型
  • 如果有限定(),则用XClass作为原始类型
  • 如果有多个限定(T extends XClass1&XClass2),则使用第一个边界XClass1作为原始类
  1. 在必要时,插入类型转换以保持类型安全
  2. 在必要时,为泛型类的子类,生成桥方法以在扩展时保持多态性
  3. 编译字节码中新增了Signature属性记录泛型信息,保存在常量池中,来解决泛型参数识别问题,所以通过反射手段可以获取到参数化类型

另外需要注意,编译器只会将声明类型的泛型信息进行保存。开源工具 Gson 处理泛型识别的时候借助匿名内部类的方式解决了这个细节问题。

最后的面经分享

泛型是日常开发中经常接触到的知识,所以算是面试必问题吧。而且有些大厂考的很细(特别是字节)。 一面的时候会通过代码改错的方式,考虑你对泛型的理解,避免你照本宣科背书。

例如:

//编辑报错
Set set = new HashSet()

//应该明确泛型的实现类型
Set set = new HashSet<>()

List strList = new ArrayList<>()
List list= new ArrayList<>()

//说出result的值
boolean result = strList.getClass() == list.getClass()

//实际上类型擦除后,都是List
//就算问反射获取List内部的 elementData ,结果也是一样,因为都被擦除为 java.lang.Object

以上比较经典的面试题,当然还有其他的变种,等我看到值得参考的再补充吧,其实呢,原理都是一样的。咱们只有真正理解了,才能融会贯通,举一反三,任它东南西北风。

与君共勉~

参考资料

Java 泛型,你了解类型擦除吗?

作业5:Java编译原理

泛型(泛型擦除、泛型可以反射、泛型的限制和问题)

Java泛型-4(类型擦除后如何获取泛型参数)

IDE中 代码提示与跳转的原理是什么?

Java泛型的类型擦除始末,找回被擦除的类型

如何理解ByteCode、IL、汇编等底层语言与上层语言的对应关系?

java里JSON使用中TypeToken为什么要用匿名内部类创建的真正原因(泛型擦除)

作者:段浅浅儿
链接:https://juejin.cn/post/7202560190750294075

最后

如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。

如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。

在这里插入图片描述
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!


img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上鸿蒙开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

需要这份系统化的资料的朋友,可以戳这里获取


[外链图片转存中…(img-lHVBx5Z8-1715295545178)]
[外链图片转存中…(img-6vGJRa2D-1715295545179)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上鸿蒙开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

需要这份系统化的资料的朋友,可以戳这里获取

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值