2023年了面试官仍然在考泛型,你确定不了解下么,2024年最新HarmonyOS鸿蒙 面试题库

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数HarmonyOS鸿蒙开发工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年HarmonyOS鸿蒙开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img

img
img
htt

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

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

如果你觉得这些内容对你有帮助,可以添加VX:vip204888 (备注鸿蒙获取)
img

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • java.lang.Object是所有类型的父类,所以转型成任何对象都是有可能的。
  • 类型支持强制转换

但是呢,这种转换关系,只能在运行期间才能被验证是否正确,ClassCastException异常出现的概率就大大提高,而且只能通过人力避免。显然这种机制是有风险且很难改善的。所以就需要一个在编译期间就能保证类型安全的机制出现,所以就出现了泛型机制。

类型擦除是什么 类型擦除的本质就是将原有的类型参数替换成即非泛化的上界 (这个后面会详细介绍)

在编译成.class文件的时机,编译器擦除了泛型类型相关的信息,所以在运行时不存在任泛型类型相关的信息。这样Java就不需要产生新的类型字节码, 所有的泛型类型最终都是一种原始类型,在Java运行时根本就不存在泛型信息,所以说所以泛型技术实际上是java语言的一颗语法糖,Java实现的是一种 伪泛型机制,

例如:

//编译的时候,变为List<Object>
List<String> strList = new ArrayList<>();

//编译的时候,变为List<Object>
List<Integer> intList = new ArrayList<>();


Why-为什么泛型是由类型擦除来实现的?

这个涉及到泛型引入的历史。Java的泛型是在 jdk 1.5 引入的,在此之前已经过去了 10年,已经存在大量没有使用泛型的代码,所以为了能够让这些代码和泛型互用,所以采取了这种方式。总结来说,就是新功能考虑到移植兼容性,所做的决定。

这样做有缺点吗? 当然是有的呀,擦除之后失去了很多特性。 例如:

  • 不支持基本类型。 原因是由于泛型类型擦除后,变成了java.lang.Object类型,这种方式对于基本类型如int/long/float等八种基本类型来说,就比较麻烦,因为Java无法实现基本类型到Object类型的强制转换。

  • 运行期间无法获取泛型实际类型

    • 无法通过 instanceof 判断
    • 无法通过 getClass 判断实际类型
  • 为了保证类型安全,不能使用泛型类型参数创建实例 例如: T object=new T() ;不合法

  • 不能声明泛型实例数组,这会导致运行错误 例如: T[] numbers= new T[capcity];不合法

  • 无法重载泛型参数的方法

  • 在静态的环境下不允许参数类型是泛型类型的 由于泛型类的所有实例都有相同的运行时类,所以泛型类的静态变量和方法是被它的所有实例所共享的。既然是共享的你就没有必要再重新定义一样的泛型类型,那如果你不定义一样的泛型类型,又达不到共享(或者说是一致性),更没有必要让这种情况通过。所以,在静态环境了类的参数被设置成泛型是非法的。

  • 泛型类对象无法被抛出或捕获 因为泛型类不能继承或实现Throwable接口及其子类。

How-具体是如何擦除泛型的?

该怎么探索这一切?

首先可以明确的是时机!

  • 擦除时机:.java->.class

这个工作,是编译器的工作,具体的执行方式一般是使用 javac命令是将源代码编程成class字节码文件

javac xxx.java

.class 文件是二进制格式,打开是下面这个样式的,正常人都只能看懂前面的的 cafe babe

cafe babe 0000 0039 001b 0a00 0200 0307
0004 0c00 0500 0601 0010 6a61 7661 2f6c
616e 672f 4f62 6a65 6374 0100 063c 696e
6974 3e01 0003 2829 5609 0008 0009 0700
//等等.....

所以还需要借助 javap 命令。

  • javap :JDK自带的反汇编器,可以查看java编译器为我们生成的字节码。通过它,我们可以对照源代码和字节码,从而了解很多编译器内部的工作

我们用到的命令是

//反编译class文件,并输出Java字节码,还有丰富的元数据
javap -c -s -p -l -verbose xxx.class


但是,有人会嫌这太麻烦了是不是!!! 如果你像我一样,用的IDE是Intellij idea, 会有更方便的方法。

  1. 选定要查看的 Java文件
  2. 确认已经在项目的out目录下生成了对应的 class 文件 (我的方式,是运行一下包下的随便一个可执行java类就OK了,肯定有其他方法吧但我不知道)
  3. View->Show Bytecode 就可以展示字节码啦

泛型类型被擦除成什么了?

看我们一个 Java 文件的例子实现

public class DRCommonGeneric<T> {
    T t;
}

查看字节码,发现泛型 T 被编译后,类型擦除为 Object

Java 源代码中的 List 就是使用的这种非限定泛型实现,

还记得上文说的么

类型擦除的本质就是将原有的类型参数替换成即非泛化的上界

类型擦除也是有规则的,非泛化的上界的意思是说,如果使用了泛型限定符 <T extends XClass>,会擦除为 XClass。多个的场景下<T extends AClass & BClass>,会默认擦除为第一个。

例如

public class DRGeneric<T extends Number> {
    T t;
}

擦除为:

也许你会问,那如果限定符<T super XClass>呢,哈哈,泛型类是不允许使用这种<T super XClass>修饰符的。

原因是只限定了下限,所以上限最高还是java.lang.Object, 类型擦除以上限为准,Object.class又是所有类型的父类型,所有类型就都可以作为 T,等于没限定,所以是没有意义的事情。

类型擦除是如果保证类型安全的

还是拿List<T> 举例,T 被擦除为 java.lang.Object,那代码逻辑中真正实例化的类型是怎么约束的呢?

例如:

List<String> list = new ArrayList();
list.add("aaaa");
System.out.println(list.get(0)); //list.get(0)得到是 Object,还是 String 呢?
list.add(1);//会报错,为什么呢?


看看字节码一探究竟

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

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

例如:


//父类
public class DRCommonGeneric<T> {
    T t;

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

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

    //同时重写了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<String> 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() 就会触发到签名的解析,以拿到正确的参数类型。

ommonGeneric 签名字符串:<T:Ljava/lang/Object;>Ljava/lang/Object; 解析出来分为三个部分:

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

签名的创建和保存?

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

保存在常量池中

签名的解析时机?

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值