public class Pair<T> {
private T first;
private T second;
public Pair() {
first = null;
second = null;
}
public Pair(T first, T second){
this.first = first;
this.second = second;
}
public T getFirst(){
return first;
}
public T getSecond(){
return second;
}
public void setFirst(T first) {
this.first = first;
}
public void setSecond(T second) {
this.second = second;
}
}
public class Pair<T, U> {....}
注: 类型变量的定义需要一定的规范:
(1) 类型变量使用大写形式,并且要比较短;
(2)常见的类型变量特别代表一些意义:变量E 表示集合类型,K和V表示关键字和值的类型;T、U、S表示任意类型;
例如: private T first;
例如: Pair<String> 代表将上述所有的T 都替换成了String
public class TestUtils {
public static <T> T getMiddle(T... a){
return a[a.length / 2];
}
}
String middle = TestUtils.<String>getMiddle("a", "b", "c");
如果上图这种情况其实可以省略
String middle = TestUtils.getMiddle("a", "b", "c");
但是如果是以下调用可能会有问题:
如图:可以看到变意思没有办法确定这里的类型,因为此时我们入参传递了一个Double3.14
两个Integer1729
和0
编译器认为这三个不属于同一个类型;
此时有一种解决办法就是把整型写成Double类型
计算数组中最下的元素
compareTo
这个函数,所以这么能让编译器相信我们这个T是一定会有compareTo
呢?<T extends Comparable>
这里的意思是T一定是继承Comparable
的类Comparable
是一定有compareTo
这个方法,所以T一定有compareTo
方法,于是编译器就不会报错了min
这个方法也只有继承了Comparable
的类才可以调用;extends
关键字并用&
分割如:T extends Comparable & Serializable
&
分割的,逗号来分割多个类型变量<T extends Comparable & Serializable , U extends Comparable>
不论什么时候定义一个泛型类型,虚拟机都会提供一个相应的原始类型(raw type)。原始类型的名字就是删掉类型参数后的泛型类型。擦除类型变量,并替换限定类型(没有限定类型的变量用Object)
列如: Pair
的原始类型如下所示
public class Pair {
private Object first;
private Object second;
public Pair() {
first = null;
second = null;
}
public Pair(Object first, Object second){
this.first = first;
this.second = second;
}
public Object getFirst(){
return first;
}
public Object getSecond(){
return second;
}
public void setFirst(Object first) {
this.first = first;
}
public void setSecond(Object second) {
this.second = second;
}
}
public class Interval<T extends Comparable & Serializable> implements Serializable{
private T lower;
private T upper;
}
public class Interval implements Serializable{
private Comparable lower;
private Comparable upper;
}
例如:
Pair<Employee> buddies = ...;
Employee buddy = buddies.getFirst();
我们可以反编译验证一下
关键的字节码有以下两条
9: invokevirtual #4 // Method com/canglang/Pair.getFirst:()Ljava/lang/Object;
12: checkcast #5 // class com/canglang/model/Employee
虚拟机指令含义如下:
- invokevirtual:虚函数调用,调用对象的实例方法,根据对象的实际类型进行派发,支持多态;
- checkcast:用于检查类型强制转换是否可以进行。如果可以进行,checkcast指令不会改变操作数栈,否则它会抛出ClassCastException异常;
由此我们可以验证了上述的结论,在反编译后的字节码中看到,当对泛型表达式调用时,虚拟机操作如下:
类型擦除也会出现在泛型方法里面
public static <T extends Comparable> T min(T[] a)
类型擦除后
public static Comparable min(Comparable[] a)
此时可以看到类型参数T已经被擦除了,只剩下限定类型Comparable;
方法的类型擦除带来了两个复杂的问题,看下面的示例:
public class DateInterval extends Pair<LocalDate> {
public void setSecond(LocalDate second){
System.out.println("DateInterval: 进来这里了!");
}
}
此时有个问题,从Pair继承的setSecond方法类型擦除后为
public void setSecond(Object second)
这个和DateInterval的setSecond明显是两个不同的方法,因为他们有不同的类型的参数,一个是Object,一个LocalDate;
那么看下面一个列子
public class Test {
public static void main(String[] args) {
DateInterval interval = new DateInterval();
Pair<LocalDate> pair = interval;
pair.setSecond(LocalDate.of(2020, 5, 20));
}
}
Pair引用了DateInterval对象,所以应该调用DateInterval.setSecond。
我们看一下运行结果
但是看了反编译的字节码可能发现一个问题:
17: invokestatic #4 // Method java/time/LocalDate.of:(III)Ljava/time/LocalDate;
20: invokevirtual #5 // Method com/canglang/Pair.setSecond:(Ljava/lang/Object;)V
这里可以看到此处字节码调用的是Pair.setSecond
这里有个重要的概念就是桥方法
引用Oracle中对于这个现象的解释
为了解决此问题并在类型擦除后保留通用类型的?多态性,
Java编译器生成了一个桥接方法,以确保子类型能够按预期工作。
对于DateInterval类,编译器为setSecond生成以下桥接方法:
public class DateInterval extends Pair {
// Bridge method generated by the compiler
//
public void setSecond(Object second) {
setSecond((LocalDate)second);
}
public void setSecond(LocalDate second){
System.out.println("DateInterval: 进来这里了!");
}
}
那么我们如何验证是否生成这个桥方法呢?我们可以反编译一下DateInterval.java看一下字节码;
public void setSecond(java.lang.Object);
descriptor: (Ljava/lang/Object;)V
flags: ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: aload_1
2: checkcast #5 // class java/time/LocalDate
5: invokevirtual #6 // Method setSecond:(Ljava/time/LocalDate;)V
8: return
我截取了部分发现在 DateInterval的字节码中的确会有一个桥方法,同时验证了上面的问题;
总结:
原文:https://www.cnblogs.com/yantt/p/12924906.html