Skip to content

面试速答(先看这里)

**一句话结论:**也就是说,在代码中的Foo<String> 和 Foo<Integer>使用的类,经过编译后都是同一个类。

60秒标准回答:

类型擦除是Java在处理泛型的一种方式,如Java的编译器在编译以下代码时

在编译后的字节码文件中,会把泛型的信息擦除掉

也就是说,在代码中的Foo<String> 和 Foo<Integer>使用的类,经过编译后都是同一个类

**答题顺序:**结论 → 原理/机制 → 关键流程 → 场景与取舍 → 易错点

回答主线:

  • **要点1:**类型擦除是Java在处理泛型的一种方式,如Java的编译器在编译以下代码时:
  • **要点2:**所以说泛型技术实际上是Java语言的一颗语法糖,因为泛型经过编译器处理之后就被擦除了。
  • **要点3:**这种擦除的过程,被称之为——类型擦除。
  • **要点4:**类型擦除可以简单的理解为将泛型java代码转换为普通java代码,只不过编译器更直接点,将泛型java代码直接转换成普通java字节码。
  • **要点5:**泛型是一种编程范式,在不同的语言和编译器中的实现和支持方式都不一样。

**记忆锚点:**java → Foo → Java → 编译后都是同一个类 → 型擦除指的是通过类 → 的关键在于从泛型类

加分表达:

  • 类型擦除的关键在于从泛型类型中清除类型参数的相关信息,并且在必要的时候添加类型检查和类型转换的方法。

追问准备:

  • 围绕「java」:底层原理是什么?使用时有哪些边界和常见坑?
  • 围绕「Foo」:底层原理是什么?使用时有哪些边界和常见坑?
  • 围绕「Java」:底层原理是什么?使用时有哪些边界和常见坑?
  • 如果线上出现异常,你会如何定位、验证并规避?

典型回答 ​

类型擦除是Java在处理泛型的一种方式,如Java的编译器在编译以下代码时:

plain
public class Foo<T> {
    T bar;
    void doSth(T param) {
    }
};

Foo<String> f1;
Foo<Integer> f2;

在编译后的字节码文件中,会把泛型的信息擦除掉:

plain
public class Foo {
    Object bar;
    void doSth(Object param) {
    }
};

也就是说,在代码中的Foo<String> 和 Foo<Integer>使用的类,经过编译后都是同一个类。

所以说泛型技术实际上是Java语言的一颗语法糖,因为泛型经过编译器处理之后就被擦除了。

这种擦除的过程,被称之为——类型擦除。所以类型擦除指的是通过类型参数合并,将泛型类型实例关联到同一份字节码上。编译器只为泛型类型生成一份字节码,并将其实例关联到这份字节码上。类型擦除的关键在于从泛型类型中清除类型参数的相关信息,并且在必要的时候添加类型检查和类型转换的方法。

类型擦除可以简单的理解为将泛型java代码转换为普通java代码,只不过编译器更直接点,将泛型java代码直接转换成普通java字节码。

扩展知识 ​

C语言对泛型的支持 ​

泛型是一种编程范式,在不同的语言和编译器中的实现和支持方式都不一样。

通常情况下,一个编译器处理泛型有多种方式,在C++中,当编译器对以下代码编译时:

plain
template<typename T>
struct Foo
{
    T bar;
    void doSth(T param) {
    }
};

Foo<int> f1;
Foo<float> f2;

当编译器对其进行编译时,编译器发现要用到Foo<int>和Foo<float>,这时候就会为每一个泛型类新生成一份执行代码。相当于新创建了如下两个类:

plain
struct FooInt
{
    int bar;
    void doSth(int param) {
    }
};

struct FooFloat
{
    float bar;
    void doSth(float param) {
    }
};

这种做法,用起来的时候很方便,只需要根据具体类型找到具体的的类和方法就行了。但是问题是,当我们多次使用不同类型的模板时,就会创建出来的很多新的类,就会导致代码膨胀。