Java高级面试 —— Java的泛型实现机制是怎么样的?

文章目录

    • 1. 类型擦除有什么优势?
        • 1.1 减小运行时内存负担
        • 1.2 向前兼容性好
    • 2. 类型擦除存在什么问题?
        • 2.1 基本类型无法作为泛型实参
        • 2.2 泛型类型无法用作方法重载
        • 2.3 泛型类型无法当做真实类型使用
        • 2.4 静态方法无法引用类泛型参数
        • 2.5 泛型类型会带来类型强转的运行时开销
    • 3. 类型擦除后怎么获取泛型参数?

说到Java泛型,面试官其实就是想要问你是否了解过Java中的类型擦除。当然,如果你面临的是校招面试,能说出类型擦除已经很不错,但是我个人觉得,如果面的是中级Android程序员甚至高级Android程序员,对类型擦除的各种特性以及优势还是需要有比较深的了解。所以,回答这个问题,先从类型擦除入手:

1. 类型擦除有什么优势?

1.1 减小运行时内存负担

实际上,我们知道在Java中,是不存在类似ListList等等这样的类型,真正被加载进方法区存储的只有List类型,这就是类型擦除。
Java高级面试 —— Java的泛型实现机制是怎么样的?_第1张图片

1.2 向前兼容性好

Java在1.5版本才推出泛型这个概念,当时Java语言的用户群已经是一个相当庞大的数量了,所以向前兼容也是当时Java开发者着重考虑的一个点。不管在前泛型时代还是泛型时代,以下的写法都是被允许的,它们的泛型元素都是Raw类型:

  List list;
  ArrayList array;

2. 类型擦除存在什么问题?

2.1 基本类型无法作为泛型实参

基本类型无法作为泛型实参,在使用过程中就意味着会有装箱和拆箱的开销:

  List<int> intArray;          // compile error
  List<Integer> intArray;      // compile success
  List<double> intArray;       // compile error
  List<Double> intArray;       // compile success

为此,谷歌也推出了SpareArray的数据结构来提高执行效率,如使用SparseBooleanArray来取代HashMapSparseIntArray用来取代HashMap等等,大家有兴趣的可以研究。

2.2 泛型类型无法用作方法重载

类型擦除意味着ListList编译后其类型都是List,也就是属于同个方法:

  public void testMethod(List<Integer> array) {}
  public void testMethod(List<Double> array) {}    // compile error

2.3 泛型类型无法当做真实类型使用

由于类型擦除后像List这样的类型是不存在的,所以也就无法直接当成真实类型使用:

  static <T> void genericMethod(T t) {
    T newInstance = new T();              // compile errror
    Class c = T.class;                    // compile errror
    List<T> list = new ArrayList<T>();    // compile errror
    if (list instance List<Integer>) {}   // compile errror
  }

这也是Gson.fromJson需要传入Class的原因:

  public <T> T fromJson(String json, Class<T> classOfT) 
          throws JsonSyntaxException {
    Object object = fromJson(json, (Type)classOfT);
    return Primitives.wrap(classOfT).cast(object);
  }

2.4 静态方法无法引用类泛型参数

类的泛型参数只有在类实例化的时候才知道,而静态方法的执行不需要有类的示例存在,所以静态方法无法引用类泛型参数:

  class GenericClass<T> {
    public static T max(T a, T b) {}
  }

2.5 泛型类型会带来类型强转的运行时开销

在实际项目中我们会经常看见这样的代码:

  List<String> strList = new Array<>();
  strList.add("Hallo");
  String value = strList.get(0);

但实际字节码指令执行strList.get()方法时,经过类型擦除后,还是需要做类型强转:

  INVOKEINTERFACE java/util/List.get (I)Ljava/lang/Object;
  CHECKCAST java/lang/String

3. 类型擦除后怎么获取泛型参数?

在Java中,泛型类型虽然被擦除了,但是被擦除的类型信息还是会以某种形式存储下来,并支持在运行时获取。这种形式就是指元素附加的签名信息(Signatures),谷歌是这么定义Signatures

Signatures encode declarations written in Java programming language that use types outside the type system of the Java Virtual Machine. They support reflection and debugging, as well as compilation when only class files are available.

获取Signatures的方法如下:

  class GenericClass<T> {}
  class ConcreteClass extends GenericClass<String> {
    public List<String> getArray() {}
  }
  // 获取类元素泛型
  ParameterizedType genericType = 
       (ParameterizedType)ConcreteClass.class.getGenericSuperClass();
  // 获取方法元素泛型
  ParameterizedType genericType = 
       (ParameterizedType)ConcreteClass.class.getMethod("getArray").getGenericReturnTypes、();

当然,在混淆时需要保留签名信息:

  -keepattributes Signature

一个很经典的例子,Gson构建泛型Type,实际上调用的就是getGenericReturnTypes方法:

  Type genericType = new TypeToken<List<Integer>>(){}.getType();

总结起来就是,Java的泛型实现正是使用类型擦除机制,而对于类型擦除机制,有其优势也有其弊端,在编程开发中需要有对其详细的见解才能写出高效的代码。

你可能感兴趣的:(Android面试)