前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java 泛型(擦除,转换,桥方法)

Java 泛型(擦除,转换,桥方法)

作者头像
挨踢小子部落阁
发布2023-03-15 21:03:41
9100
发布2023-03-15 21:03:41
举报

类型擦除

  • 编译器在编译期间所以的泛型信息都会被擦除

编译 .java 生成的 .class (字节码文件)中数据类型必须是确定好的。 如果一个 class 是泛型类,或者含有泛型方法,那么编译器在编译时会将其中的类型变量去掉,生成一个与泛型类同名的原始类。在 原始类class文件 中的是其真正的类型(原始类型)。

注:

<>所修饰的部分(例:< T >)直接被擦除,而之后的用到的类型变量( T )会被原始类型代替。

原始类型:类型限界(无类型限界为Object)

定义泛型类Generic1和Generic2

代码语言:javascript
复制
class Generic1<T> {
    T value;

    public T getValue() {
        return value;
    }

    public void setValue(T value) {
        this.value = value;
    }
}

class Generic2<T extends A> {    // A为类型限界
    T value;

    public T getValue() {
        return value;
    }

    public void setValue(T value) {
        this.value = value;
    }
}

// 自定义的类
class A {
    String aName = "A";
}

class B extends A {
    String bName = "B";
}

Generic1和Generic2类分别经过类型擦除后生产的(原始类)字节码如下

代码语言:javascript
复制
/*
 * 原始类型为:Object
 */
class Generic1{
    Object value;

    public Object getValue() {
        return value;
    }

    public void setValue(Object value) {
        this.value = value;
    }
}

/*
 * 原始类型为:A
 */
class Generic2 {
    A value;

    public A getValue() {
        return value;
    }

    public void setValue(A value) {
        this.value = value;
    }
}

总结:

  • 如果泛型类型的类型变量没有限定() ,用Object作为原始类型;
  • 如果有限定(<T extends A>),用A作为原始类型;
  • 如果有多个限定(<T extends A1 & A2>),用第一个边界的类型变量A1类作为原始类型;
类型转换
  • 当一个具有擦除返回类型的泛型方法被调用时会进行强制类型转换

Generic1中的

代码语言:javascript
复制
public T getValue() {
    return value;
}

调用getValue方法时

代码语言:javascript
复制
Generic1<String> g1 = new Generic1<>();
g1.setValue("Hello world!");
String s1 = g1.getValue();// 这里编译器编译时处理成:String s1 = (String) g1.getValue()

由于原始类型是Object,返回的value的类型是Object,但是在调用这个方法的地方会根据类型变量进行强转(做了一个checkcast()操作,即检查< String >中的类型并强转) ArrayList中的

代码语言:javascript
复制
E elementData(int index) {
    return (E) elementData[index];
}

public E get(int index) {
    rangeCheck(index);

    return elementData(index);// 调用elementData方法
}

调用get方法时

代码语言:javascript
复制
ArrayList<Date> list = new ArrayList<Date>();
list.add(new Date());
Date myDate = list.get(0);

由于原始类型是Object,方法返回值是Object,但是在调用时会进行强制类型转换。 以上需要注意的是:不是在方法里强转,是在调用的地方强转。

桥方法
  • 为了避免类型变量擦除所带来的多态问题

定义一个父类

代码语言:javascript
复制
class Generic<T> {
    T value;

    public void setValue(T value) {
        this.value = value;
    }

    public T getValue() {
        return value;
    }
}

定义一个子类,重写父类方法

代码语言:javascript
复制
class MyGeneric extends Generic<String> {

    @Override
    public void setValue(String value) {
        super.setValue(value);
    }

    @Override
    public String getValue() {
        return super.getValue();
    }
}

子类重写父类的方法,但是

代码语言:javascript
复制
// 继承父类的已经类型擦除后的方法
public void setValue(Object value){...}
public T getValue(){...}
// 子类新增(重写)的方法
public void setValue(String value){...}
public String getValue(){...}

由于参数类型不同这并不能算是重写,为了达到重写的目的,编译器使用桥方法解决。 编译器在 MyGeneric 类中生成了两个桥方法(这两个桥方法会调用子类新增的方法)

代码语言:javascript
复制
public void setValue(Object value) {
    setValue((String) value);// 子类新增的setValue(String value)方法
}

public Object getValue() {
    return getValue();// 子类新增的getValue()方法(返回的 String 类型的 getValue 方法)
}

也就是说在重写方法后的子类中通过编译器会变成如下情形

代码语言:javascript
复制
class MyGeneric extends Generic<String> {

    public void setValue(String value){...} // 自己定义(重写)的方法
    public void setValue(Object value){...} // 编译器生成的桥方法

    public String getValue(){...} // 自己定义(重写)的方法
    public Object getValue(){...} // 编译器生成的桥方法
}

值得注意的是getValue方法: 编译器允许在同一个类中出现方法签名相同的多个方法吗? 方法签名(方法名+参数列表)用来确定一个方法; 人为是不能在同一个类中编写出方法签名一样的多个方法,否则编译器会报错; 但是,编译器自己能够创建出方法签名一样而返回类型不同的方法,JVM会用参数类型和返回类型来确定一个方法。

本文参与?腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-11-18,如有侵权请联系?cloudcommunity@tencent.com 删除

本文分享自 挨踢小子 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与?腾讯云自媒体分享计划? ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 类型擦除
    • 类型转换
      • 桥方法
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
      http://www.vxiaotou.com