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

1 篇文章 0 订阅

类型擦除

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

编译 .java 生成的 .class (字节码文件)中数据类型必须是确定好的。
如果一个 class 是泛型类,或者含有泛型方法,那么编译器在编译时会将其中的类型变量去掉,生产一个与泛型类同名的原始类。在 原始类class文件 中的是其真正的类型(原始类型)。
注:
<>所修饰的部分(例:< T >)直接被擦除,而之后的用到的类型变量( T )会被原始类型代替。
原始类型:类型限界(无类型限界为Object)

定义泛型类Generic1和Generic2

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类分别经过类型擦除后生产的(原始类)字节码如下

/*
 * 原始类型为: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作为原始类型;
如果有限定(),用A作为原始类型;
如果有多个限定(<T extends A1 & A2>),用第一个边界的类型变量A1类作为原始类型;

类型转换

  • 当一个具有擦除返回类型的泛型方法被调用时会进行强制类型转换

Generic1中的

    public T getValue() {
        return value;
    }

调用getValue方法时

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

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

    E elementData(int index) {
        return (E) elementData[index];
    }
    
    public E get(int index) {
        rangeCheck(index);

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

调用get方法时

        ArrayList<Date> list = new ArrayList<Date>();
        list.add(new Date());
        Date myDate = list.get(0);

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

桥方法

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

定义一个父类

class Generic<T> {
	T value;
	
	public void setValue(T value) {
		this.value = value;
	}
	
	public T getValue() {
		return value;
	}
}

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

class MyGeneric extends Generic<String> {

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

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

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

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

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

    public void setValue(Object value) {
        setValue((String) value);// 子类新增的setValue(String value)方法
    }

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

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

class MyGeneric extends Generic<String> {

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

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

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

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值