java核心之泛型(二)剖析

——每天的寥寥几笔,坚持下去,将会是一份沉甸甸的积累。


看懂了上一篇文章,大概就会用泛型,但你不理解其内部原理,这边文章来和大家探讨写泛型核心的难点——擦除。

其实,java虚拟机是没有泛型类型对象的,在虚拟机看来所有类都是普通类,这样的好处,就是不用改动虚拟机,同时也能兼容早期没有泛型时的程序,毕竟虚拟机没改动。

但是,问题来了,你写的代码里面明明有泛型,怎么到了虚拟机那里就都一样了呢?其实,这就是编译器做的好事,它把泛型改写了一下,改写成了和没有泛型前一样的通用版本代码,这样虚拟机也能认识了。这就是传说中的擦除(erased)——删掉类型参数并用限定类型(无限定类型时用Object)替换类型变量,擦除的类型也被称为原始类型

这样的话,上一篇中的Pair<T>,就成了

public class Pair{//<T>类型参数被删掉了
private Object first;//T都被替换为Object
public Object second;
public Pair(Object first, Object second){
	this.first = first;
	this.second = second;
}
public Object getFirst() {
	return first;
}
public void setFirst(Object first) {
	this.first = first;
}
public Object getSecond() {
	return second;
}
public void setSecond(Object second) {
	this.second = second;
}
}
如果Pair<T>改成Pair<T extends Number>那么就不用Object替换T,而是用Number替换T

如果Pair<T>改成Pair<T extends Number&Comparable>那么就不用Object替换T,而是用多限定的第一个限定Number替换T


2.擦出过后又是如何找回原有类型?让虚拟机不是Object也不是Number而是某一特定的子类的呢?

举例:设上面的取Pair<T extends Number>

public static void main(String[] args) {
	Pair<Integer> pair = new Pair(1,2);
	Integer obj = pair.getFirst();//安全通过
}
由于擦除,那么必然调用pair.getFirst()会返回Number类型,可又是如何不用强转直接赋给Integer的obj引用的呢?

原因是:编译器帮忙执行了两条指令,其一就是调用getFirst()方法返回Number类型,但由于擦除前,有对Pair<Integer> pair中的Integer类型做了记录,因此,编译器会帮忙将Number类型转成Integer类型,因此,我们最后调用pair.getFirst(),得到的就是Integer类型,所以上面的代码安全通过了编译和运行。


3.上面是编译器翻译泛型表达式,然后在翻译泛型方法时就有些麻烦了。

举例:假设上面的取Pair<T>,然后ConcretePair继承Pair<String>

public class ConcretePair extends Pair<String> {
	public ConcretePair(String first, String second) {
		super(first, second);
	}
	@Override
	public String getFirst() {
		return super.getFirst();
	}
	@Override
	public void setFirst(String first) {
		super.setFirst(first);
	}
	@Override
	public String getSecond() {
		return super.getSecond();
	}
	@Override
	public void setSecond(String second) {
		super.setSecond(second);
	}
}
根据擦除原理,Pair<T>类变成Pair类,同时用Object替换所有T。而对于Concrete类则变成了继承Pair——<String>被擦除了,但是Concrete类里面的String还是保留了下来。

这样就出现了一个问题,拿getFirst()方法来说,它是覆写了父类的方法,@override标记可以作证,但是由于擦除,父类成了Pair,而Pair类显然没有String getFirst(),那又怎么能说ConcretePair里的String getFirst()覆写了父类方法呢?出现矛盾了。那又是怎么解决的呢?

的确,这里的getFirst()方法由于返回类型是String,已经称不上是父类Pair中Object getFirst()的覆写了,@override注解也是形同虚设,不起作用了,也就是说这个getFirst()方法成了ConcretePair子类自己新添的方法,如果要调用必须把Pair<String>的父类型向下转型成ConcretePair,这样原来覆写的多态性被打破了。

即:

Pair<String> pair = new ConcretePair("sd","w");
pair.getFirst();//出错:本来由于多态,可以调用,但现在该方法成了ConcretePair自己新建的类而无法像这样调用。
((Concrete)pair).getFirst();//这样虽然可以调用,但不是我们的本意
那这种情况怎么解决呢?

编译器的解决方案是给你在ConcretePair类里生成你所想要的覆写方法Object getFirst()等,这些编译器自己加的方法被称为桥方法。也就是如下的情况:

public class ConcretePair extends Pair<String> {
	public ConcretePair(String first, String second) {
		super(first, second);
	}
	@Override
	public String getFirst() {
		return super.getFirst();
	}
	@Override
	public void setFirst(String first) {
		super.setFirst(first);
	}
	@Override
	public String getSecond() {
		return super.getSecond();
	}
	@Override
	public void setSecond(String second) {
		super.setSecond(second);
	}
	public Object getFirst() {//重名报错
		return getFirst();
	}
	public void setFirst(Object first) {
		setFirst((String)first);
	}
	public Object getSecond() {
		return getSecond();
	}
	public void setSecond(Object second) {
		setSecond((String)second);
	}
}
当然,生成的四个方法我们是看不到的,也不会像上面那样(重名等各种报错)。但我们可以通过反编译字节码来看到,javap -c ConcretePair.class   【篇幅有限,我就贴多出来的四个方法了】。

  public void setSecond(java.lang.String);
    Code:
       0: aload_0
       1: aload_1
       2: invokespecial #6                  // Method Pair.setSecond:(Ljava/lang/Object;)V
       5: return

  public void setSecond(java.lang.Object);
    Code:
       0: aload_0
       1: aload_1
       2: checkcast     #3                  // class java/lang/String
       5: invokevirtual #7                  // Method setSecond:(Ljava/lang/String;)V
       8: return

  public java.lang.Object getSecond();
    Code:
       0: aload_0
       1: invokevirtual #8                  // Method getSecond:()Ljava/lang/String;
       4: areturn

  public void setFirst(java.lang.Object);
    Code:
       0: aload_0
       1: aload_1
       2: checkcast     #3                  // class java/lang/String
       5: invokevirtual #9                  // Method setFirst:(Ljava/lang/String;)V
       8: return

  public java.lang.Object getFirst();
    Code:
       0: aload_0
       1: invokevirtual #10                 // Method getFirst:()Ljava/lang/String;
       4: areturn
}

很清楚,的确多出来了四个覆写的方法,这样的话多态性就不会被打破,

Pair<String> pair = new ConcretePair("sd","w");
pair.getFirst();//这里调用的实际是编译器为我们生成的方法,然后该方法内部再调用我们所谓新建的String getFirst()方法,最后的返回类型就成了Object
                (编译生成的方法),但类型转换不会出错,上面讲过了
ok,一切清楚。


4.延生(边边角角)

(1)实现Cloneable方法时,这里也用到了桥方法

public class Employee implements Cloneable{
    public Employee clone(){...}
    //实际上
    //Employee clone()  defined above
    //Object clone()    bridge method,overrides Object.clone,因为所有类都是Object的子类
 }

(2)协变覆写(上面cloneable的例子):允许子类覆写函数的返回类型是父类被覆写函数返回类型的子类。即覆写了父类Object中的clone方法,而且返回类型Employee是Object的子类。


世界晚安,,西安晚安。。。。。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值