Java泛型方法重写问题

Author Avatar
lucky 2020年04月20日
  • 在其它设备中阅读本文章

java 中的泛型是采用类型擦除的方式来实现,也即编译后所有原始类型的泛型类都共享同一份目标代码,例如这里的 A <T> 编译器编译为 A,那么编译器对于引用类中泛型的方法,也即泛型方法进行类型擦除操作时是如何实现的呢?答案是采用最左边类型(当前 T 的初始具体父类型)来代替。如下面代码经过编译器后生成:
编译前的源代码

class A<T> {

    T get(final T t) {
        return t;
    }
}

编译后的 class

A extends java.lang.Object{
    A();
    java.lang.Object get(java.lang.Object);
}

从上面的代码中可以看出,T 被 Object 代替,也即 T 的当前父类为 Object。如果 A <T extends Comparable>, 那么编译后就会用 Comparable 代替 Object。那进行下面代码分析,B 继承了泛型类 A <T> 时重写泛型类中的泛型方法,此时重写是用 Object 来作为返回值和参数,编译器产生了错误,看下面代码。

class A<T> {

    T get(final T t) {
        return t;
    }
}
/**
 * 通过一个类继承了一个泛型类,重写方法时的问题.<br>
 * @since JDK 1.5.0
 */
class B extends A<String> {

    /**
     *@ERROR: Compile-error
     * 条件:
     *      1、这两个方法(B.get和A.get)具有相同的名称,也即get
     *      2、A<String>.get(String)方法B是可以访问得到
     *      3、B.get(Object)和A<String>.get(String)的签名signature不是父子关系
     *      4、这两个方法具有相同的擦出记号?没明白什么意思....
     * @param obj
     * @return
     */
    Object get(final Object obj) {
        return obj;
    }
}

错误消息为名称冲突:类型 B 的方法 get(Object)与类型 A 的 get(T)具有相同的擦除记号,但是未将它覆盖。乍一看完全不明白这什么意思,因为从 A.class 来看,擦除泛型后代码中包含的是 Object get(Object), 此时在 B 中定义个相同方法,并应该是符合 java 中的重写语义的吗?从 jls 中翻阅可以得吃 override 条件是满足的,那么编译器为什么不能通过编译?
暂时将问题放一边,将代码进行稍加修改后,编译器编译成功,测试通过,为什么它可以呢?javap B 或者 javap -verbose B 输出指令集进行查看编译结果。

class B extends A<String> {

    /**
     *@ERROR: Compile-error
     * 条件:
     *      1、这两个方法(B.get和A.get)具有相同的名称,也即get
     *      2、A<String>.get(String)方法B是可以访问得到
     *      3、B.get(Object)和A<String>.get(String)的签名signature不是父子关系
     *      4、这两个方法具有相同的擦出记号?没明白什么意思....
     * @param obj
     * @return
     */
    String get(final String str) {
        return str;
    }
}
B extends A{
    B();
    java.lang.String get(java.lang.String);
    java.lang.Object get(java.lang.Object);
}
//具体get方法
java.lang.Object get(java.lang.Object);
  Code:
   Stack=2, Locals=2, Args_size=2
   0:   aload_0
   1:   aload_1
   2:   checkcast       #19; //class java/lang/String
   5:   invokevirtual   #21; //Method get:(Ljava/lang/String;)Ljava/lang/String;
   8:   areturn
  LineNumberTable:
   line 1: 0

}

在 B 中只有 String get(String),那么 Object get(Object)从何而来。这是编译器自动生成的一段代码,那么它的目的是什么?从它的内部指定可以看出,调用 String get(String)来实现功能,那么它为什么要调用 String get(String)呢?这又得从 java 泛型擦除说起,B 继承了 A <String>, 那么从编程角度来讲,A<String> 中的泛型方法 get 应该就是 String get(String),此时 B 中的 String get(String)就是重写 A 中的 String get(String),A<String> 是如何来实现的呢?就是通过产生一个桥接方法 Object get(Object)来实现,从这里看出 java 泛型中利用的桥接模式。

总结
写到这里,我想大家应该清楚在 B 中写入一个 Object get(Object)方法是为什么会有 complie-error 了,就是因为编译器产生一个 Object get(Object),此时源代码中又有一个 Object get(Object),一个 class 中同一个方法,具有相同签名和名称的方法重复定义是不符合 java 语法规定,所以有 complie-error。

class A<T> {

    T get(final T t) {
        return t;
    }
}

interface C<E> {
    E get(final E e);
}
class D extends A<String> implements C<Integer> {

    /**
     * 名称冲突:类型 C<E> 的方法 get(E)与类型 A<T> 的 get(T)具有相同的擦除记号,但是未将它覆盖
     * {@inheritDoc}
     * @see s8_4.C#get(java.lang.Object) 这里是C接口中的实现
     */
    public Integer get(final Integer t) {
        return null;
    }

    /**
     * 名称冲突:类型 C<E> 的方法 get(E)与类型 A<T> 的 get(T)具有相同的擦除记号,但是未将它覆盖
     * {@inheritDoc}
     * @see s8_4.A#get(java.lang.Object) 这里是A类的get重写
     */
    public String get(final String str) {
        return null;
    }
} 

这个错误原因也是一样。

原文地址:https://blog.csdn.net/qq_26971305/article/details/53938993
JVM 为什么要使用桥方法 https://www.cnblogs.com/wuqinglong/p/9456193.html

评论已关闭