java泛型--桥方法

看桥方法之前,我们先来看看泛型中类型擦除的概念:


在《java核心卷书1》中有这样一段描述:


虚拟机没有泛型类型对象——所有对象都属于普通类。也就是说,虚拟机在执行代码的时候,都会把泛型类翻译成普通的类。

无论何时定义一个泛型类型,都自动提供了一个相应的原始类型(raw type)。原始类型的名字就是删去类型参数后的泛型类型名。擦除(erased)类型变量,并替换为限定类型(无限定的变量用Object)。


下面看一个泛型类:

class Pair {
	private T first;
	private T second;

	public Pair(T first, T second) {
		super();
		this.first = first;
		this.second = second;
	}

	public T getFirst() {
		return first;
	}

	public T getSecond() {
		return second;
	}
}

它的原始类型是(T没有限定,用Object替换):

class Pair {
	private Object first;
	private Object second;

	public Pair2(Object first, Object second) {
		super();
		this.first = first;
		this.second = second;
	}

	public Object getFirst() {
		return first;
	}

	public Object getSecond() {
		return second;
	}
}

当然了,一个类型变量或通配符可以有多个限定,例如:T extends Comparable & Serializble。这样的该怎样进行替换呢?原始类型用第一个限定的类型变量来限定。

Eg:

class Pair {
	private T first;
	private T second;

	public Pair(T first, T second) {
		super();
		this.first = first;
		this.second = second;
	}

	public T getFirst() {
		return first;
	}

	public T getSecond() {
		return second;
	}
}

替换完后就变成了(用第一个限定的类型变量Comparable作为原始类型):

class Pair implements Serializable>{
		private Comparable first;
		private Comparable second;

		public Pair(Comparable first, Comparable second) {
			super();
			this.first = first;
			this.second = second;
		}

		public Comparable getFirst() {
			return first;
		}

		public Comparable getSecond() {
			return second;
		}
}

了解了类型擦除的概念后,看看桥方法的产生:

先看一个例子:

class Father {
	public void fun(T x) {
	}
}
class Son extends Father {
	@Override
	public void fun(String x) {
	}
}

Father类和Son类经过类型擦除后变成:

class Father {
	public void fun(Object x) {
	}
}
class Son extends Father {
	@Override
	public void fun(String x) {
	}
}

很显然,子类Son是想覆盖父类Fatherfun方法,但是却出现了问题(注意重写的要求:方法名、参数个数和参数类型都必须相同)。因为Father类在编译阶段经过类型擦除后,T被替换成了Object了。也就是Father类中的fun方法变成了:

public void fun(Object x) {
}

这对于多态是个不小的麻烦,考虑下面的代码:

Father f=new Son();
Object x=null;
f.fun(x);

本来是想利用多态调用Son类的fun(Object)方法,但是Son类的却是fun(String),没有fun(Object)方法。这就导致类型擦除和多态产生了冲突。

下面来看看编译器是怎么解决这个问题的:

编译器在Son类中生成了一个桥方法(bridgemethod),我们可以通过javap反编译Son.class文件,看一下:

java泛型--桥方法_第1张图片

可以看到确实有两个fun方法:fun(Object)fun(String)

在生成的桥方法中是怎么操作的呢:

public void fun(Object x){
	fun((String)x);
}

现在再来看一下f.fun(Object),由于f引用的Son类对象,所以根据多态性质,会调用Son类的fun(Object)方法,这个方法是合成的桥方法。它会去调用Son类的fun(String)方法,这正是我们所期望的结果。


桥方法解决了多态和类型擦除之间的冲突,但是假如Father类中还有一个方法:

public T gun(){
	///
}

Son中也想覆盖这个方法呢?

就会发现在Son类中出现了两个方法签名一样的方法(方法名和参数都相同):

public Object gun()
public String gun()

当然,我们是不能这样编写代码的,因为不能有两个名字相同,参数类型也相同却返回不同类型值的方法。

但是,在虚拟机中却是可以的(很神奇),虚拟机用参数类型和返回类型确定一个方法(《java核心卷术1》)。编译器可能产生两个仅返回值不同的方法字节码,虚拟机能够正确的处理这一情况。

 

桥方法不仅用于泛型类,可以看一个例子:

class Person implements Cloneable {
	@Override
	public Person clone() throws CloneNotSupportedException {
		return (Person) super.clone();
	}
}

在一个方法覆盖另一个方法时,可以指定一个更加严格的返回类型。

实际上Person类也有两个clone方法:

Employee clone()
Object clone()//合成的桥方法,会调用新定义的方法。


总之,需要记住有关Java泛型转换的事实:

1.虚拟机中没有泛型,只有普通的类和方法;

2.所有的类型参数都用他们的限定类型替换;

3.桥方法被用来保持多态;









你可能感兴趣的:(javaSE)