java-泛型
泛型程序设计( Generic programming)编写的代码可以被不同类型的对象重用。
类型参数的好处
在添加泛型这个特性之前,java想要实现泛型的功能是通过 继承 来实现的。例如 ArrayList 类只维护了引用的Object 数组:
public class ArrayList {// before generic classes
private Object [ ] elementData ;
public Object get ( int i ) { . . , }
public void add ( Object o ) { . . . }
}
此种方法有两个问题:
- 获取值的时候要进行强制类型转换
- 可以向Object数组列表中添加任何类型的对象
此时,可以通过泛型引入类型参数解决上述问题,使得程序有更好的可读性和安全性。并且编译器通过泛型可以获取其参数信息,避免添加错误类型对象。
【注】类型变量使用大写形式且比较短。 在 Java 库中 , 使用变量 E 表示集合的元素类型 , K 和 V 分别表示表的关键字与值的类型 。 T ( 需要时还可以用临近的字母 U 和 S ) 表示“任意类型”。
简单泛型类
public class Pair < T >
{
private T first ;
private T second ;
public Pair ( ) { first = null ; second = null ; }
public PairfT first , T second ) { this , first = first ; this . second = second ; }
public T getFirstO { return first ; }
public T getSecondO { return second ; }
public void setFirst ( T newValue ) { first = newValue ; }
public void setSecond ( T newValue ) { second = newValue ; }
}
泛型方法
class ArrayAlg
{
public static < T > T getMiddle ( T . . . a )
{
return a [ a . length / 2 ] ;
}
}
调用下面代码时候会报错
double middle = TestGeneric . getMiddle( 3.14 , 1729 , 0 ) ;
error:
Incompatible types. Found: 'java.lang.Number & java.lang.Comparable<? extends java.lang.Number & java.lang.Comparable<?>>', required: 'double'
编译器将会自动打包参数为 1 个 Double 和 2 个 Integer 对象 , 而后寻找这些类的共同超类型 。 找到 2 个这样的超类 型 : Number 和 Comparable 接口 , 其本身也是一个泛型类型 。 在这种情况下 , 可以采取的补救措施是将所有的参数写为 double 值。
类型变量的限定
为了保证泛型 T 所属类实现了 compareTo 方法,可以使用 extends 关键字来限定(bound):
public static < T extends Coiparab 1 e > T min(T[] a) . . .
如果有多个限定则可以使用 & 符号来表示:
T extends Comparable & Serializable
逗号用来分隔类型变量 。
泛型代码和虚拟机
类型擦除
无论何时定义一个泛型类型 , 都自动提供了一个相应的原始类型 ( raw type )。 原始类型的名字就是删去类型参数后的泛型类型名。 擦除 ( erased )类型变 M , 并替换为限定类型 ( 无限定的变量用 Object)。
例如 , Pair < T > 的原始类型如下所示 :
public class Pair
{
private Object first ;
private Object second ;
public Pair ( Object first , Object second )
{
this , first = first ;
this . second = second ;
public Object getFirstO { return first ; }
public Object getSecondO { return second ; }
public void set First ( Object newValue ) { first = newValue ; }
public void setSecond ( Object newValue ) { second = newValue ; }
}
如果使用多个限定:
public class Interval < T extends Comparable & Serializable 〉 implements Serializable
{
private T lower ;
private T upper ;
public Interval ( T first , T second )
{
if ( first . compareTo ( second ) < = 0 ) { lower = first ; upper = second ; }
else { lower = second ; upper = first ; }
}
}
原始类型 Interval 如下所示 :
public class Interval implements Serializable
{
private Comparable lower ;
private Comparable upper ;
public Interval ( Comparable first , Comparable second ) { . . . }
}
原始类型用 Serializable 替换 T , 而编译器在必要时要向 Comparable 插入强制类型转换 。 为了提高效率 , 应该将标签 ( tagging ) 接口 ( 即没有方 法的接口 ) 放在边界列表的末尾 。
翻译泛型表达式
下面的代码执行时候
Pair < Employee > buddies = ...
Employee buddy = buddies.getFirst();
因为类型擦除,getFirst 的返回类型后将返回 Object 类型 。 编译器自动插人 Employee 的强制类型转换 。 也就是说,编译器把这个方法调用翻译为两条虚拟机指令 :
- 对原始方法 Pair.getFirst() 的调用。
- 将返回的 Object 类型强制转换为 Employee 类型。
同样的在 存取一个泛型域时也要插人强制类型转换。 假设 Pair 类的 first 域为 public。
Employee buddy = buddies.first;
上述代码也会在字节码中插入强制类型转换。
翻译泛型方法
public static < T extends Comparable > T min ( T [] a )
在类型擦除后只剩下一个方法:
public static Comparable T min ( Comparable[] a )
方法 在类型擦除后有两个问题:
class Datelnterval extends Pair < LocalDate >{
public void setSecond ( LocalDate second ) {
if(second.compareTo(getFirst())>=0)
super.setSecond(second);
}
...
}
这个类经过擦除后:
class Datelnterval extends Pair{//after erased
public void setSecond ( LocalDate second ) {...}
...
}
此类中存在另一个继承的 setSecond 方法:
public void setSecond ( Object second ) {...}
若使用下面代码进行调用:
Datelnterval interval = new Datelnterval ( . . . ) ;
Pair < LocalDate > pair = interval ; / / OK assignment to superclass
pair.setSecond ( aDate ) ;
上面代码希望对 setSecond 的调用具有多态性 ,并调用最合适的那个方法。 由于 pair 引用 Datelnterval 对象, 所以应该调用 Datelnterval . setSecond。问题在于类型擦除与多态发生了冲 突。编译器通过在 Datelnterval 类中增加桥方法(bridge method)方式来解决这个问题。工作流程为:
-
pair 声明为类型 Pair < LocalDate >,此类型只有一个方法为 setSecond ( Object )
-
虚拟机用 pair 引用的对象调用这个方法。 这个对象是Datelnterval 类型的 , 因而将会调用 Datelnterval.setSecond ( Object ) 方法
-
这个方法是合成的桥方法。 它调用 Datelnterval.setSecond (Date)
假设 Datelnterval 也覆盖了 getSecond()方法:
class Datelnterval extends Pair < LocalDate >
{
public LocalDate getSecond ( ) { return ( Date ) super.getSecond().clone(); }
...
}
此时在 Datelnterval 类中有两个 getSecond方法:
LocalDate getSecond ( ) // defined in Datelnterval
Object getSecond() // overrides the method defined in Pair to call the first method
在java代码中是不合法的。但是在虚拟机中,使用参数类型和返回类型确定一个方法,因此,编译器可能产生两个仅返回类型不同的方法字节码,虚拟机能够正确地处理这一情况 。
Java 泛型转换:
- 虚拟机中没有泛型 , 只有普通的类和方法
- 所有的类型参数都用它们的限定类型替换
- 桥方法被合成来保持多态
- 为保持类型安全性 , 必要时插人强制类型转换
转载自:https://juejin.cn/post/6923504121915899918