Java泛型方法重写问题

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;
    }
} 

这个错误原因也是一样。

  • 8
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
掌握集合的概念、体系结构、分类及使用场景 2)了解Set接口及主要实现类(HashSet、TreeSet) 3)了解List接口及主要实现类(ArrayList、LinkedList、Vector) 4)了解Map接口及主要实现类(HashMap、TreeMap、HashTable) 二、实验内容及步骤 1、编写程序练习将以下5个Person类的对象放在一个HashSet中。 姓名:张三 身份证号:178880001 姓名:王五 身份证号:178880002 姓名:李四 身份证号:178880003 姓名:王五 身份证号:178880002 姓名:李四 身份证号:178880004 注意:因为Person类是自定义类,需要重写hashCode()方法和equals()方法,并规定只有姓名和身份证号都相等,则对象相等。 其中计算哈希码的算法:(31 + ((name == null) ? 0 : name.hashCode()))*31 + id (注:name:Person对象的姓名,id:Person对象的身份证号) 主方法中作如下测试: 1)创建一个可放置Person类对象的HashSet; 2)依次添加上述5个对象到HashSet中; 3)把集合中的元素打印出来(使用迭代器Iterator) 2、编写程序练习List集合的基本使用: 1) 创建一个只能容纳String对象名为names的ArrayList集合; 2)按顺序往集合中添加5个字符串对象:"张三"、"李四"、"王五"、"马六"、"赵七"; 3)对集合进行遍历,分别打印集合中的每个元素的位置与内容; 4)打印集合的大小,然后删除集合中的第3个元素,并显示删除元素的内容,然后再打印目前集合中第3个元素的内容,并再次打印集合的大小。 3、编写程序练习Map集合的基本使用: 1)创建一个只能容纳String对象的person的HashMap集合; 2)往集合中添加5个"键-值"对象: "id"-"1"; "name"-"张三"; "sex"-"男"; "age"-"25"; "hobby"-"爱学Java" 3)对集合进行遍历,分别打印集合中的每个元素的键与值; 4)打印集合的大小,然后删除集合中的键为age的元素,并显示删除元素的内容,并再次打印集合的大小。 四、思考题 1、集合中的List、Set、Map有哪些不同? 2、为什么使用集合框架,而尽可能少用数组作为存储结构? 3、如何使用TreeSet实现第一题?
Java泛型是为了实现类型安全而引入的特性。泛型变量只能调用Object类继承或重写的方法是因为在编译时,泛型的实际类型是未知的。编译器无法确定泛型变量的具体类型,所以只能把泛型变量当作Object类型来对待。 Object类是所有类的基类,所以泛型变量默认继承了Object类的方法,包括toString()、hashCode()和equals()等常用方法。因此,无论泛型变量的实际类型是什么,都可以通过Object类的方法来进行操作。这样可以保证在编译时不会出现类型错误,提高了代码的稳定性和可靠性。 如果泛型变量可以调用任意方法,那么在使用时就无法进行类型检查。这样可能会导致运行时错误,破坏了类型安全性。为了保证类型安全性,Java只允许泛型变量调用Object类继承或重写的方法。 为了解决泛型类型的不确定性,Java提供了通配符(Wildcard)和上下界限定(Upper Bound、Lower Bound)等机制,可以对泛型类型进行限制和约束,从而提供更灵活、更安全的泛型编程方式。使用通配符和边界限定,可以实现对特定类型的操作,避免了类型转换错误和运行时异常的风险。 总之,Java泛型变量只能调用Object类继承或重写的方法,是为了保证类型安全性和提高代码的稳定性。通过泛型的类型擦除机制和通配符、边界限定等特性,可以实现更安全、更灵活的泛型编程。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值