如何应对Android面试官->Java泛型

475 阅读4分钟

1、常见问题

  1. 为什么我们需要泛型?
  2. 泛型类、泛型接口、泛型方法
  3. 如何限定类型变量?
  4. 泛型使用中的约束和局限性?
  5. 泛型类型能继承吗?
  6. 泛型中通配符类型?
  7. 虚拟机是如何实现泛型的?

2、Java 泛型的原理?

Java 的泛型是 JDK5 引入的新特性,为了向下兼容,虚拟机其实是不支持泛型的,所以 Java 实现的是一种伪泛型机制,也就说 Java 在编译期擦除了所有的泛型信息,这样 Java 就不会产生新的类型到字节码,所有的泛型类型最终都是一种原始类型,在 Java 运行时根本不存在泛型信息;

3、泛型产生的原因?

  1. 参数化类型;

  2. 适用于多种数据类型执行相同的代码;

  3. 编码过程中指定数据类型而不需要进行强制类型转换,将运行时的错误,提前到编译期,防止运行时异常;

4、泛型的使用

4.1 泛型接口

public interface Plate<T> {
	void set(T data);
	T get();
}

4.2 泛型类

public class AIPlate<T> implements Plate<T> {		
    ArrayList<T> arrayList = new ArrayList<T>(10);
}

4.3 泛型方法

public <T> getPlate<T> () {	    
    return new AIPlate<T>();
}

4.4 泛型界定注意点(限定类型变量)

  • A extends B

  • B可以是类,也可以是接口

  • B还可以是多个

  • A extends B&C&D

  • 当B既有类又有接口的时候,类必须在接口的前面,且类只能有一个<T extends ArrayList&Comparable>

  • 具有多个限定的类型变量是范围中列出的所有类型的子类型,如果范围之一是类,则必须首先指定类,才能指定接口

  • 先继承类后实现接口

  • 单继承

  • 单一继承

5、泛型擦除

5.1、什么是泛型擦除?

  • 为了向下兼容,虚拟机其实是不支持泛型的,所以 Java 实现的是一种伪泛型机制,也就说 Java 在编译期擦除了所有的泛型信息;

5.2、泛型擦除,擦除之后的类型是代码中第一个声明的限定类型,举例如下:

  •   public class AIPlate<<T extends Comparable<T>>> implements Plate<T> {
          ArrayList<T> arrayList = new ArrayList<T>(10);	
      }
    

               泛型擦除之后是 Comparable

  • 编译后的字节码,会生成两个 get ,set 方法,如下的例子:一个是 AIPlate 的 set,get 方法,一个是父接口的桥方法,里面使用 CheckCast 关键字进行了强制类型转换      

5.3、泛型擦除残留

  • 直接查看 class 文件,泛型会有残留。应该查看字节码。这里看到的其实是签名而已,还保留了定义的格式,这样对于分析字节码是有好处的;

  • 这些擦除的信息会存在类的常量池里面 所以反射还是可以拿到泛型信息的

5.4、泛型擦除与反射 Map map

5.5、Java 编译器具体是如何擦除泛型的?

  1. 检查泛型类型,获取目标类型;

  2. 擦除类型变量,并替换为限定类型;

  • 如果泛型类型的类型变量没有限定,则用 Object 作为原始类型;

  • 如果有限定,则用 xClass 作为原始类型;

  • 如果有多个限定<T extends XClass1 & XClass2>,则使用第一个边界 XClass1作为原始类型;

  1. 在必要时插入类型转换以保持类型安全;

  2. 生成桥方法以在扩展时保持多态性;

6、泛型通配符

6.1、通配符类型

  • 非限定通配符

  • Plate<?> 是一个泛型类型 ?未知,等价于Plate<? extends Object>;

  • 限定通配符

6.2、? extends T

  • 上界通配符,一个能放父类以及父类派生类的泛型;

  • 只能用来赋值,赋值之后不能 set 设置也不能 get 取值;

  • 上界通配符带来的问题?

  • 编译器只能知道容器内是父类以及父类的派生类,但具体什么类型不知道,转换后这个容器就会被标记上一个占位符CAP#1,来标记捕获一个父类或者它的派生类;

6.3、? super T

  • 下界通配符,一个能放子类以及子类的父类的容器;

6.4、通配符与泛型的关系

  • 为了提高泛型转型的灵活性,增加通配符,防止转型异常

7、使用泛型会带来哪些影响?

  • 无法使用基本数据类型;

  • 无法使用 instacneof 关键字;

  • 无法在静态变量中使用,静态的泛型方法可以;

  • 因为泛型类中的泛型参数的实例化是在定义泛型类型对象的时候指定的,而静态成员是不需要使用对象来调用的,所以对象没有创建,就无法确定这个泛型参数是什么;

  • 方法冲突;

  • 无法创建泛型实例;

  • 原因:因为类型不确定;

  • 解决:通过反射的方式;

  • 没有泛型数组;

  • 原因:数组的协变(A extends B,那么 A[] 的父类就是 B[] 这就叫做数组的协变)

  • 泛型擦除之后,就不能满足数组的协变,所以泛型数组不存在;

  • 解决方案:

  •   public class GenericArrayWithTypeToken<T> {
          private T[] array;
      
          @SuppressWarnings("unchecked")
          public GenericArrayWithTypeToken(Class<T> type, int sz) {
              array = (T[]) Array.newInstance(type, sz);
          }
      
          public void put(int index, T item) {
              array[index] = item;
          }
      
          public T[] rep() {
              return array;
          }
      
          public static void main(String[] args) {
              
          }
      }
    
  • 不能实例化类型变量;

  • 静态域或者方法里不能引用类型变量;

简历润色

深度理解 Java 泛型、理解泛型擦除;

下一章预告

带你玩转Java注解、反射,手写 ButterKnife 核心实现;

欢迎三连

来都来了,点个关注、点个赞吧~~