Android面试题详解之泛型是什么,泛型擦除呢?

泛型是什么,泛型擦除呢?

详细讲解

享学课堂移动互联网系统课程:架构师筑基必备技能《架构设计中必不可少的泛型-Java泛型的定义与原理》

这道题想考察什么?

泛型

考察的知识点

泛型的特点和优缺点以及泛型擦除

考生应该如何回答

泛型就是一种就是一种不确定的数据类型。在Java中有着重要的地位,在面向对象编程及各种设计模式中都有非常广泛的应用。

泛型的优点

我们为什么需要使用泛型:

  1. 适用于多种数据类型执行相同的代码,例如两个数据相加:
public int addInt(int x,int y){
    
    
    return x+y;
}

public float addFloat(float x,float y){
    
    
    return x+y;
}

不同的类型,我们就需要增加不同的方法,但是使用泛型那我们的代表将变为:

public <T> T addInt(T x,T y){
    
    
    return x+y;
}
  1. 编译检查,例如下面代码
List<String> list = new ArrayList();
list.add(10);//①
list.add("享学");
String name = list.get(2);//②

因为我们指定了List泛型类型为String,因此在代码1处编译时会报错。而在代码2处,不再需要做类型强转。

泛型的缺点

  1. 静态域或者方法里不能引用泛型变量,因为泛型是在new对象的时候才知道,而类的构造方法是在静态变量之后执行。
  2. 不能捕获泛型类对象

泛型擦除

Jdk中实现的泛型实际上是伪泛型,例如泛型类 Fruit<T> ,编译时 T 会被擦除,成为 Object。但是泛型擦除会带来一个复杂的问题:

桥方法

有如下代码:

public class Parent<T> {
    
    
    
    public void setSrc(T src){
    
    
       
    }
}
public class Child extends Parent<String>{
    
    
    @Override
    public void setSrc(String src) {
    
    
        super.setSrc(src);
    }
}

Parent类是一个泛型类,在经过编译时泛型擦除后其中setSrc(T) 将会变为setSrc(Object);而Child类继承与Parent并且指定了泛型类型为String。那么经过编译后这两个类应该变为:

public class Parent {
    
    
    
    public void setSrc(Object src){
    
    
       
    }
}
public class Child extends Parent{
    
    
    @Override
    public void setSrc(String src) {
    
    
        super.setSrc(src);
    }
}

父类存在setSrc(Object),而子类则是setSrc(String)。这明显是两个不同的方法,按照Java的重写规则,子类并没有重写父类的方法,而是重载。

所以实际上子类中存在两个setSrc方法。一个自己的,一个是继承自父类的:

public void setSrc(String src)
public void setSrc(Object src)

那么当我们:

Parent o = new Child();
o.setSrc("1");

此时o实际类型是Child,静态类型是Parent。按照Java规则,会调用父类中的setSrc(Object),如:

public class A{
    
    
    public void setValue(Object value){
    
    
		System.out.println("Object");
	}
}
public class B extends A{
    
    
        public void setValue(String value){
    
    
            System.out.println("String");
        }
}

public static void main(String[] args) {
    
    
	A a = new  B();
	a.setValue("1");
	a.setValue(11);
}

上诉代码会输出两次”Object“。然而在泛型中却不符合此规则,因为 Java 编译器帮我们处理了这种情况,在泛型中引入了"Bridge Method"—桥方法。通过查看Child.class的字节码文件 :

public void setSrc(java.lang.String);
    descriptor: (Ljava/lang/String;)V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=2, args_size=2
         0: aload_0
         1: aload_1
         2: invokespecial #2                  // Method Parent.setSrc:(Ljava/lang/Object;)V
         5: return
      LineNumberTable:
        line 4: 0
        line 6: 5

public void setSrc(java.lang.Object);
    descriptor: (Ljava/lang/Object;)V
    flags: ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
    Code:
      stack=2, locals=2, args_size=2
         0: aload_0
         1: aload_1
         2: checkcast     #3                  // class java/lang/String
         5: invokevirtual #4                  // Method setSrc:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 1: 0
}

可以看到 Child 类中有两个 setSrc方法,一个参数为 String 类型,一个参数为 Object 类型,参数为 Object 类型。而参数为Object的setSrc方法可以在flags中看到ACC_BRIDGEACC_SYNTHETIC 。其中ACC_BRIDGE用于说明这个方法是由编译生成的桥接方法,ACC_SYNTHETIC说明这个方法是由编译器生成,并且不会在源代码中出现。

setSrc(Object)桥方法可以看到实际上会使用checkcast先进行类型转换检查,然后执行invokevirtual调用setSrc(String)方法,这样就避免了我们还能调用到父类的方法。

更多面试题详解可以扫描二维码免费领取!

猜你喜欢

转载自blog.csdn.net/Misdirection_XG/article/details/131229055