深入分析Java反射(三)-泛型
前提
Java反射的API在JavaSE1.7的時候已經基本完善,但是本文編寫的時候使用的是Oracle JDK11,因為JDK11對於sun包下的原始碼也上傳了,可以直接通過IDE檢視對應的原始碼和進行Debug。
本文主要介紹反射中一個比較難的問題-泛型。
泛型的簡介
泛型是在2004年JavaSE 5.0(JDK1.5)版本中新增到Java程式語言中的泛型程式設計工具。泛型的設計是為了應用在Java的型別系統,提供"用型別或者方法操作各種型別的物件從而提供編譯期的型別安全功能(原文:a type or method to operate on objects of various types while providing compile-time type safety)"。但是在2016年的一些研究表明,泛型並不是在所有的情況下都能保證編譯期的型別安全,例如切面(Aspect)程式設計的編譯期型別安全並沒有完全實現。
泛型的一個最大的優點就是:提供編譯期的型別安全。舉個很簡單的例子,在引入泛型之前,ArrayList
內部只維護了一個Object陣列引用,這種做法有兩個問題:
- 從陣列列表獲取一個元素的時候必須進行型別的強轉。
- 向陣列列表中可以新增任何型別的物件,導致無法得知陣列列表中存放了什麼型別的元素。
引入泛型之後,我們可以通過型別引數明確定義ArrayList
:
ArrayList<String> list = new ArrayList<String>(); // JavaSE 7以後的版本中建構函式可以省略型別,編譯器可以推匯出實際型別 ArrayList<String> list = new ArrayList<>();
下面先列舉出Java中泛型的一些事實:
- Java虛擬機器中不存在泛型,只有普通的類和方法,但是位元組碼中存放著泛型相關的資訊。
- 所有的型別引數都使用它們的限定型別替換。
- 橋方法(Bridge Method)由編譯器合成,用於保持多型(Java虛擬機器利用方法的引數型別、方法名稱和方法返回值型別確定一個方法)。
- 為了保持型別的安全性,必要時需要進行型別的強制轉換。
理解型別擦除
型別擦除是什麼
型別擦除(或者更多時候喜歡稱為"泛型擦除")的具體表現是:無論何時定義一個泛型型別,都自動提供一個相應的原始型別(Raw Type,這裡的原始型別並不是指int、boolean等基本資料型別),原始型別的類名稱就是帶有泛型引數的類刪去泛型引數後的型別名稱,而原始型別會擦除(Erased)型別變數,並且把它們替換為限定型別(如果沒有指定限定型別,則擦除為Object型別),舉個例子Pair<T>
public class Pair<T>{
private T first;
private T second;
public Pair(T first,T second){
this.first = first;
this.second = second;
}
public T getFirst(){
return first;
}
public T getSecond(){
return second;
}
}
擦除型別後的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 getFirst(){
return first;
}
public Object getSecond(){
return second;
}
}
舉個更復雜的例子,如果泛型引數型別是有上限的,變數會擦除為上限的型別:
public class Interval<T extends Comparable & Serializable> implements Serializable {
private T lower;
private T upper;
public Interval(T lower, T upper) {
this.lower = lower;
this.upper = upper;
}
//省略其他方法
}
型別擦除後的Interval<T extends Comparable & Serializable>
原始型別:
public class Interval implements Serializable {
private Comparable lower;
private Comparable upper;
public Interval(Comparable lower, Comparable upper) {
this.lower = lower;
this.upper = upper;
}
//省略其他方法
}
像上面這種多個泛型上限的型別,應該儘量把標識介面上限型別放在邊界列表的尾部,這樣做可以提高效率。
為什麼需要擦除型別
在JDK1.5之前,也就是在泛型出現之前,所有的型別包括基本資料型別(int、byte等)、包裝型別、其他自定義的型別等等都可以使用類檔案(.class)位元組碼對應的java.lang.Class
描述,也就是java.lang.Class
類的一個具體例項物件就可以代表任意一個指定型別的原始型別。這裡把泛型出現之前的所有型別暫時稱為"歷史原始型別"。
在JDK1.5之後,資料型別得到了擴充,出歷史原始型別擴充了四種泛型型別:引數化型別(ParameterizedType)、型別變數型別(TypeVariable)、限定符型別(WildcardType)、泛型陣列型別(GenericArrayType)。歷史原始型別和新擴充的泛型型別都應該統一成各自的位元組碼檔案型別物件,也就應該把泛型型別歸併進去java.lang.Class
中。但是由於JDK已經迭代了很多版本,泛型並不屬於當前Java中的基本成分,如果JVM中引入真正的泛型型別,那麼必須涉及到JVM指令集和位元組碼檔案的修改(這個修改肯定不是小的修改,因為JDK當時已經迭代了很多年,而型別是程式語言的十分基礎的特性,引入泛型從專案功能迭代角度看可能需要整個JVM專案做迴歸測試),這個功能的代價十分巨大,所以Java沒有在Java虛擬機器層面引入泛型。
Java為了使用泛型,於是使用了型別擦除的機制引入了"泛型的使用",並沒有真正意義上引入和實現泛型。Java中的泛型實現的是編譯期的型別安全,也就是泛型的型別安全檢查是在編譯期由編譯器(常見的是javac)實現的,這樣就能夠確保資料基於型別上的安全性並且避免了強制型別轉換的麻煩(實際上,強制型別轉換是由編譯器完成了,只是不需要人為去完成而已)。一旦編譯完成,所有的泛型型別都會被擦除,如果沒有指定上限,就會擦除為Object型別,否則擦除為上限型別。
既然Java虛擬機器中不存在泛型,那麼為什麼可以從JDK中的一些類庫獲取泛型資訊?這是因為類檔案(.class)或者說位元組碼檔案本身儲存了泛型的資訊,相關類庫(可以是JDK的類庫,也可以是第三方的類庫)讀取泛型資訊的時候可以從位元組碼檔案中提取,例如比較常用的位元組碼操作類庫ASM就可以讀取位元組碼中的資訊甚至改造位元組碼動態生成類。例如前面提到的Interval<T extends Comparable & Serializable>
類,使用javap -c -v
命令檢視其反編譯得到的位元組碼資訊,可以看到其簽名如下:
Signature: #22 // <T::Ljava/lang/Comparable;:Ljava/io/Serializable;>Ljava/lang/Object;Ljava/io/Serializable;
這裡的簽名信息實際上是儲存在常量池中的,關於位元組碼檔案的解析將來會出一個系列文章詳細展開。
Type體系
前文提到了在JDK1.5中引入了四種新的泛型型別java.lang.reflect.ParameterizedType
、java.lang.reflect.TypeVariable
、java.lang.reflect.WildcardType
、java.lang.reflect.GenericArrayType
,包括原來存在的java.lang.Class
,一共存在五種型別。為了程式的擴充套件性,引入了java.lang.reflect.Type
類作為這五種型別的公共父介面,這樣子就可以使用java.lang.reflect.Type
型別引數去接收以上五種子型別的實參或者返回值,由此從邏輯上統一了泛型相關的型別和原始存在的java.lang.Class
描述的型別。Type體系如下:
注意:
- ParameterizedType、TypeVariable、WildcardType、GenericArrayType都是介面,它們位於
java.lang.reflect
包中。 - ParameterizedTypeImpl、TypeVariableImpl、WildcardTypeImpl、GenericArrayTypeImpl是四種泛型型別的實現,位於
sun.reflect.generics.reflectiveObjects
包中。
Type體系雖然看似很美好解決了泛型相關的型別和原始存在的java.lang.Class
描述的型別的統一問題,但是引入了新的問題:如果一個方法返回值為java.lang.reflect.Type
型別,或者一個方法的入參型別為java.lang.reflect.Type
型別,這兩種情況下,可能需要對java.lang.reflect.Type
型別的物件做子型別判斷,因為它的子型別有可能是上面提到的五種型別中的其中一種,這一點提高了編碼的複雜性。
ParameterizedType
ParameterizedType,parameterized type,也就是引數化型別,註釋裡面說到ParameterizedType
表示一個引數化型別,例如Collection<String>
,實際上只要帶有引數化(泛型)標籤<ClassName>
的引數或者屬性,都屬於ParameterizedType。例如下面的型別都是ParameterizedType:
Set<String> set;
Class<Integer> clazz;
MyClass<String> myClass;
List<String> list;
class MyClass<V>{
}
而像下面的忽略泛型引數或者基本資料型別和基本資料型別的包裝類都不是ParameterizedType:
String name = "throwbale";
int age = 25;
Set set;
List list;
public String method(int age,String name){
}
java.lang.reflect.ParameterizedType
介面繼承自java.lang.reflect.Type
介面,實現類是sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl
,其實,必要的時候,我們也可以自行實現ParameterizedType,像一些Json解析工具都是自行實現ParameterizedType的。ParameterizedType介面的方法如下:
public interface ParameterizedType extends Type {
Type[] getActualTypeArguments();
Type getRawType();
Type getOwnerType();
}
- Type[] getActualTypeArguments():返回這個ParameterizedType型別的引數的實際型別Type陣列,Type數組裡面的元素有可能是Class、ParameterizedType、TypeVariable、GenericArrayType或者WildcardType之一。值得注意的是,無論泛型符號
<>
中有幾層<>
巢狀,這個方法僅僅脫去最外層的<>
,之後剩下的內容就作為這個方法的返回值。 - Type getRawType():返回的是當前這個ParameterizedType的原始型別,從ParameterizedTypeImpl的原始碼看來,原始型別rawType一定是一個
Class<?>
例項。舉個例子,List<Person>
通過getRawType()
獲取到的Type例項實際上是Class<?>
例項,和List.class等價。 - Type getOwnerType():獲取原始型別所屬的型別,從ParameterizedTypeImpl的原始碼看來,就是呼叫了原始型別rawType的
getDeclaringClass()
方法,而像rawType為List<T>
、Map<T>
這些型別的getOwnerType()實際上就是呼叫List.class.getDeclaringClass(),Map.class.getDeclaringClass(),返回值都是null。
舉個關於ParameterizedType的簡單使用例子:
public class Main13 {
public static void main(String[] args) throws Exception {
Class<Sub> subClass = Sub.class;
Type genericSuperclass = subClass.getGenericSuperclass();
if (genericSuperclass instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;
//獲取父類泛型型別陣列
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
for (Type type : actualTypeArguments) {
System.out.println(type + " is ParameterizedType -> " + (type instanceof ParameterizedType));
}
}
Field field = subClass.getDeclaredField("clazz");
Type genericType = field.getGenericType();
System.out.println(genericType + " is ParameterizedType -> " + (genericType instanceof ParameterizedType));
}
public static class Person {
}
public static abstract class Supper<T, E> {
}
public static class Sub extends Supper<String, List<Person>> {
}
}
輸出結果:
class java.lang.String is ParameterizedType -> false
java.util.List<org.throwable.inherited.Main13$Person> is ParameterizedType -> true
java.lang.Class<?> is ParameterizedType -> true
TypeVariable
TypeVariable,type variable,也就是型別變數,它是各種型別變數的公共父介面,它主要用來表示帶有上界的泛型引數的資訊,它和ParameterizedType不同的地方是,ParameterizedType表示的引數的最外層一定是已知具體型別的(如List<String>
),而TypeVariable面向的是K、V、E等這些泛型引數字面量的表示。常見的TypeVariable的表示形式是<T extends KnownType-1 & KnownType-2>
。TypeVariable介面原始碼如下:
public interface TypeVariable<D extends GenericDeclaration> extends Type {
//獲得泛型的上限,若未明確宣告上邊界則預設為Object
Type[] getBounds();
//獲取宣告該型別變數實體(即獲得類、方法或構造器名)
D getGenericDeclaration();
//獲得名稱,即K、V、E之類名稱
String getName();
//獲得註解型別的上限,若未明確宣告上邊界則預設為長度為0的陣列
AnnotatedType[] getAnnotatedBounds()
}
- Type[] getBounds():獲得該型別變數的上限(上邊界),若無顯式定義(extends),預設為Object,型別變數的上限可能不止一個,因為可以用&符號限定多個(這其中有且只能有一個為類或抽象類,且必須放在extends後的第一個,即若有多個上邊界,則第一個&之後的必為介面)。
- D getGenericDeclaration:獲得宣告(定義)這個型別變數的型別及名稱,會使用泛型的引數字面量表示,如
public void club.throwable.Main.query(java.util.List<club.throwable.Person>)
。 - String getName():獲取泛型引數的字面量名稱,即K、V、E之類名稱。
- AnnotatedType[] getAnnotatedBounds():Jdk1.8新增的方法,用於獲得註解型別的上限,若未明確宣告上邊界則預設為長度為0的陣列。
舉個關於TypeVariable的簡單使用例子:
public class Main14 {
public static void main(String[] args) throws Exception {
Class<Supper> subClass = Supper.class;
TypeVariable<Class<Supper>>[] typeParameters = subClass.getTypeParameters();
for (TypeVariable<Class<Supper>> typeVariable : typeParameters) {
System.out.println("getBounds --> " + Arrays.toString(typeVariable.getBounds()));
System.out.println("getGenericDeclaration --> " + typeVariable.getGenericDeclaration());
System.out.println("getName --> " + typeVariable.getName());
AnnotatedType[] annotatedBounds = typeVariable.getAnnotatedBounds();
StringBuilder stringBuilder = new StringBuilder("getAnnotatedBounds --> ");
for (AnnotatedType annotatedType : annotatedBounds) {
java.lang.annotation.Annotation[] annotations = annotatedType.getAnnotations();
for (java.lang.annotation.Annotation annotation : annotations) {
stringBuilder.append(annotation).append(",");
}
}
System.out.println(stringBuilder.toString());
System.out.println("===================");
}
}
@Target(ElementType.TYPE)
public @interface Annotation {
}
interface InterFace {
}
public static class Person {
}
public static abstract class Supper<T extends Person & InterFace, E extends Annotation> {
}
}
輸出結果:
getBounds --> [class org.throwable.inherited.Main14$Person, interface org.throwable.inherited.Main14$InterFace]
getGenericDeclaration --> class org.throwable.inherited.Main14$Supper
getName --> T
getAnnotatedBounds -->
===================
getBounds --> [interface org.throwable.inherited.Main14$Annotation]
getGenericDeclaration --> class org.throwable.inherited.Main14$Supper
getName --> E
getAnnotatedBounds -->
===================
WildcardType
WildcardType用於表示萬用字元(?)型別的表示式的泛型引數,例如<? extends Number>
等。根據WildcardType註釋提示:現階段萬用字元表示式僅僅接受一個上邊界或者下邊界,這個和定義型別變數時候可以指定多個上邊界是不一樣。但是為了保持擴充套件性,這裡返回值型別寫成了陣列形式。實際上現在返回的陣列的大小就是1。WildcardType介面原始碼如下:
public interface WildcardType extends Type {
Type[] getUpperBounds();
Type[] getLowerBounds();
}
- Type[] getUpperBounds():獲取泛型萬用字元的上限型別Type陣列,實際上目前該陣列只有一個元素,也就是說只能有一個上限型別。
- Type[] getLowerBounds():獲取泛型萬用字元的下限型別Type陣列,實際上目前該陣列只有一個元素,也就是說只能有一個下限型別。
舉個關於WildcardType的簡單使用例子:
public class Main16 {
public static void main(String[] args) {
Class<Main16> clazz = Main16.class;
Method[] methods = clazz.getMethods();
for (Method method : methods) {
if ("print".equals(method.getName())) {
Type[] genericParameterTypes = method.getGenericParameterTypes();
for (Type type : genericParameterTypes) {
if (type instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) type;
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
for (Type actualType : actualTypeArguments) {
if (actualType instanceof WildcardType) {
WildcardType wildcardType = (WildcardType) actualType;
System.out.println("WildcardType --> " + wildcardType + " getUpperBounds--> "
+ Arrays.toString(wildcardType.getUpperBounds()) + " getLowerBounds--> " + Arrays.toString(wildcardType.getLowerBounds()));
} else {
System.out.println("Not WildcardType --> " + actualType);
}
}
}
}
}
}
}
interface Person {
}
public static void print(List<? extends Number> list, Set<? super Person> persons) {
}
}
輸出結果:
WildcardType --> ? extends java.lang.Number getUpperBounds--> [class java.lang.Number] getLowerBounds--> []
WildcardType --> ? super org.throwable.inherited.Main16$Person getUpperBounds--> [class java.lang.Object] getLowerBounds--> [interface org.throwable.inherited.Main16$Person]
這裡注意的是List<? extends Number> list
這個引數整體來看是ParameterizedType型別,剝掉第一次List之後的? extends Number
是WildcardType型別。
GenericArrayType
GenericArrayType,generic array type,也就是泛型陣列,也就是元素型別為泛型型別的陣列實現了該介面。它要求元素的型別是ParameterizedType或TypeVariable(實際中發現元素是GenericArrayType也是允許的)。舉個例子:
List<String>[] listArray; //是GenericArrayType,元素是List<String>型別,也就是ParameterizedType型別
T[] tArray; //是GenericArrayType,元素是T型別,也就是TypeVariable型別
Person[] persons; //不是GenericArrayType
List<String> strings; //不是GenericArrayType
GenericArrayType介面的原始碼如下:
public interface GenericArrayType extends Type {
Type getGenericComponentType();
}
- Type getGenericComponentType():獲取泛型陣列中元素的型別。注意無論從左向右有幾個
[]
並列,這個方法僅僅脫去最右邊的[]
之後剩下的內容就作為這個方法的返回值。
舉個關於GenericArrayType的簡單使用例子:
public class Main15<T> {
public static void main(String[] args) throws Exception {
Method[] methods = Main15.class.getMethods();
for (Method method : methods) {
if ("method".equals(method.getName())) {
Type[] genericParameterTypes = method.getGenericParameterTypes();
for (Type type : genericParameterTypes) {
if (type instanceof GenericArrayType) {
System.out.println("GenericArrayType --> " + type + " getGenericComponentType --> "
+ ((GenericArrayType) type).getGenericComponentType());
} else {
System.out.println("Not GenericArrayType --> " + type);
}
}
}
}
}
public static <T> void method(String[] strings, List<String> ls, List<String>[] lsa, T[] ts, List<T>[] tla, T[][] tts) {
}
}
輸出結果:
Not GenericArrayType --> class [Ljava.lang.String;
Not GenericArrayType --> java.util.List<java.lang.String>
GenericArrayType --> java.util.List<java.lang.String>[] getGenericComponentType --> java.util.List<java.lang.String>
GenericArrayType --> T[] getGenericComponentType --> T
GenericArrayType --> java.util.List<T>[] getGenericComponentType --> java.util.List<T>
GenericArrayType --> T[][] getGenericComponentType --> T[]
這裡分析一下:
String[] strings
:陣列是Class型別。List<String> ls
:列表是ParameterizedType型別。List<String>[] lsa
:陣列是GenericArrayType型別,呼叫getGenericComponentType後返回的型別是java.util.List<java.lang.String>
,也就是陣列元素是ParameterizedType型別。T[] ts
:s陣列是GenericArrayType型別,呼叫getGenericComponentType後返回的型別是T,也就是陣列元素是TypeVariable型別。List<T>[] tla
:陣列是GenericArrayType型別,呼叫getGenericComponentType後返回的型別是java.util.List<T>
,也就是陣列元素是ParameterizedType型別。T[][] tts
:陣列是GenericArrayType型別,呼叫getGenericComponentType後返回的型別T[],也就是陣列元素是GenericArrayType型別。
泛型的約束
使用Java泛型的時候需要考慮一些限制,這些限制大多數是由泛型型別擦除引起的。
- 1、不能用基本型別例項化型別引數,也就是8種基本型別不能作為泛型引數,例如
Pair<int>
是非法的,會導致編譯錯誤,而Pair<Integer>
是合法的。 - 2、執行時的型別查詢只能適用於原始型別(非引數化型別)。
//下面的兩種做法是錯誤的
if(a instanceof Pair<String>) //Error
if(a instanceof Pair<T>) //Error
// 正確做法
if(a instanceof Pair) //Right
- 3、不能建立引數化型別的陣列,例如
Pair<String>[] arr = new Pair<String>[10]
是非法的。 - 4、不能例項化型別變數或者型別變數陣列,例如
T t = new T()
或者T[] arr = new T[10]
都是非法的。 - 5、Varargs警告,這是因為第4點原因導致的,一般會發生在泛型型別變數作為可變引數的情況,例如
public static <T> addAll(Collection<T> con,T ... ts)
,第二個引數實際上就是泛型型別變數陣列,但是這種情況是合法的,不過會受到編譯器的警告,可以通過@SuppressWarnings("unchecked")
註解或者@SafeVarargs
註解標註該方法以消除警告。 - 6、不能在靜態域或者方法中引用型別變數,例如
private static T singleInstance;
這樣是非法的。 - 7、不能丟擲或者丟擲或者捕獲泛型型別變數,但是如果在異常規範中使用泛型型別變數則是允許的,舉兩個例子仔細品味一下:
// 反例
public static <T extends Throwable> void doWork(Class<T> t) {
try{
}catch(T t){ //Error
}
}
// 正例
public static <T extends Throwable> void doWork(T t) throws T{
try{
}catch(Throwable e){
throw e;
}
}
- 8、通過使用
@SuppressWarnings("unchecked")
註解可以消除Java型別系統的部分基本限制,一般使用在強制轉換原始型別為泛型型別(只是在編譯層面告知編譯器)的情況,如:
// 不加此註解會收到編譯器的警告
@SuppressWarnings("unchecked")
public static <T extends Throwable> void throwAs(Throwable e){
throw (T) e;
}
其實還有泛型的繼承規則和萬用字元規則(可以看下前面介紹的Type的子型別)等等,這裡不詳細展開。
再議泛型陣列的問題
在Java泛型約束中,無法例項化引數化型別陣列,例如Pair<Integer>[] table = new Pair<Integer>[10];
是非法的。根本原因在於泛型型別的擦除和陣列會記錄元素型別的特性。舉個例子,假設可以例項化引數化型別陣列:
Pair<String>[] table = new Pair<String>[10];
上面的引數化型別陣列在泛型擦除之後,陣列例項table的型別為Pair[]
,陣列元素型別為Pair
,可以強轉為Object[]
型別陣列:
Object[] objArray = table;
基於泛型擦除,陣列objArray可以任意賦值Pair<AnyType>
的泛型化例項,例如:
objArray[0] = new Pair<Integer>();
objArray[1] = new Pair<Long>();
objArray[2] = new Pair<String>();
....
這樣子能夠通過陣列儲存元素的檢查,後續運算元組元素隨時會出現ClassCastException。基於以上的原因,Java從編譯層面直接拒絕建立引數化型別陣列。
另外,型別變數陣列的例項化也是非法的,如T[] tt = new T[10];
,這是因為型別變數僅僅是編譯期的字面量,其實和Java的型別體系是不相關的。
但是要注意一點:引數化型別陣列和型別變數陣列可以作為方法入參變數或者類的成員變數。例如下面的做法是合法的:
public class Pair<T> {
private Pair<T>[] attr;
private T[] ts;
public static <T> void method(Pair<T> pair) {
}
public static <T> void method(T[] ts) {
}
}
最後一點,可以檢視前一篇文章,其實可以使用反射建立泛型陣列。
無限定萬用字元
泛型中支援無限定萬用字元<?>
,使用無限定萬用字元型別的例項有以下限制:
- 所有的Getter方法只能返回Object型別的值。
- 所有的Setter方法只能賦值null,其他型別的值的設定都是非法的。
無限定萬用字元型別可以看做原始型別的一個影子型別,它遮蔽了除了null之外的設值操作,所有獲取值的方法只能返回Object型別結果,這種特性使得通過無限定萬用字元型別進行一些簡單的操作變得十分方便,例如:
public static boolean hasNulls(Pair<?> p){
return p.getFirst() == null || p.getSecond() == null;
}
如果反射用得比較熟的話,java.lang.Class
也有類似的用法:
Class<?> clazz = ...;
Object instance = class.newInstance();
橋方法(Bridge Method)
先說明一下什麼是橋方法,看下面的程式碼:
// 父類
public interface Supper<T> {
void method(T t);
}
// 其中一個子類
public class Sub implements Supper<Integer> {
@Override
public void method(Integer value) {
System.out.println(value);
}
}
父類Supper<T>
在泛型擦除後原始型別是:
public interface Supper{
void method(Object t);
}
子類Sub
雖然實現了父類Supper
,但是它只實現了void method(Integer value)
而沒有實現父類中的void method(Object t)
,這個時候,編譯期編譯器會為子類Sub
建立此方法,也就是子類Sub
會變成這樣:
public class Sub implements Supper<Integer> {
@Override
public void method(Integer value) {
System.out.println(value);
}
public void method(Object value) {
this.method((Integer) value);
}
}
如果你直接這樣編寫一個子類Sub
是會編譯報錯,而上面這裡編譯器生成的void method(Object value)
方法就是橋方法。可以用反射驗證一下:
public static void main(String[] args) throws Exception {
Method[] declaredMethods = Sub.class.getDeclaredMethods();
List<Method> methods = new ArrayList<>();
for (Method method : declaredMethods) {
if (method.getName().equals("method")) {
methods.add(method);
}
}
for (Method method : methods) {
System.out.println(String.format("name=%s,paramTypes=%s,isBridge=%s", method.getName(),
Arrays.toString(method.getParameterTypes()), method.isBridge()));
}
}
//輸出結果
name=method,paramTypes=[class java.lang.Integer],isBridge=false
name=method,paramTypes=[class java.lang.Object],isBridge=true
橋方法的定義比較模糊,因此這裡只考慮它出現的情況,不做盲目的定義。不單只是子類實現帶有泛型引數的父類會產生橋方法,還有一種比較常見的情況是在方法覆蓋的時候指定一個更加"嚴格的"返回值型別的時候,也會產生橋方法,例如:
public Employee implements Cloneable{
public Employee clone() throws CloneNotSupportedException{
//...
}
}
// 這裡實際上,Employee覆蓋了Object的clone()方法,因此實際上編譯後Employee如下
public Employee implements Cloneable{
public Employee clone() throws CloneNotSupportedException{
//...
}
// 這個是橋方法
public Object clone() throws CloneNotSupportedException{
//...
}
}
這是因為:
- 編譯的時候Java的方法簽名是方法名稱加上方法引數型別列表,也就是方法名和引數型別列表確定一個方法的簽名(這樣就可以很好理解方法過載,還有Java中的引數都是形參,所以引數名稱沒有實質意義,只有引數型別才是有意義的)。
- Java虛擬機器定義一個方法的簽名是由方法名稱、方法返回值型別和方法引數型別列表組成,所以JVM認為返回值型別不同,而方法名稱和引數型別列表一致的方法是不相同的方法。
仔細看,其實兩種情況都是由於繼承才導致橋方法出現。
JDK中操作泛型的API
這裡列舉一下JDK中筆者所知的操作泛型的相關API(可以會有遺漏),這些API主要和反射操作相關:
java.lang.Class
中的相關方法:
方法 | 功能 |
---|---|
Type[] getGenericInterfaces() | 返回類例項的介面的泛型型別 |
Type getGenericSuperclass() | 返回類例項的父類的泛型型別 |
java.lang.reflect.Constructor
中的相關方法:
方法 | 功能 |
---|---|
Type[] getGenericExceptionTypes() | 返回構造器的異常的泛型型別 |
Type[] getGenericParameterTypes() | 返回構造器的方法引數的泛型型別 |
java.lang.reflect.Method
中的相關方法:
方法 | 功能 |
---|---|
Type[] getGenericExceptionTypes() | 返回方法的異常的泛型型別 |
Type[] getGenericParameterTypes() | 返回方法引數的泛型型別 |
Type getGenericReturnType() | 返回方法返回值的泛型型別 |
java.lang.reflect.Field
中的相關方法:
方法 | 功能 |
---|---|
Type getGenericType() | 返回屬性的泛型型別 |
如果在使用上面的方法得到的返回值和期望的返回值不相同,請加深對泛型型別擦除的認識。
小結
參考資料:
個人認為,泛型其實是JDK迭代過程中妥協和相容歷史的產物,它是一種沒有實現的泛型,當然,提供編譯期型別安全這一點可以讓開發者避免型別轉換出現人為錯誤,也就是說:Java中的泛型使得程式或者程式碼的可讀性和安全性提高,這是它的最大優勢。
- 《Java核心技術卷I-基礎知識》
- 維基百科-Generics in Java
個人部落格
- Throwable's Blog
(本文完 e-a-20181205