背景和問題
我們需要通過方法的參數(shù)類型,創(chuàng)建參數(shù)的實(shí)例膨报。本地開發(fā)測試正常磷籍,部署測試環(huán)境提示反射異常适荣。
為便于理解,改為學(xué)生與學(xué)校的關(guān)系表示院领。
代碼:
interface IStudent {
void learn();
}
interface ISchool<R extends IStudent> {
void add(R r);
}
class MiddleStudent implements IStudent{
@Override
public void learn() {
//TODO
}
}
class MiddleSchool implements ISchool<MiddleStudent> {
@Override
public void add(MiddleStudent middleStudent) {
/TODO
}
}
public class Test {
public static void main(String[] args) throws IllegalAccessException, InstantiationException {
//所有方法
Method[] methods = MiddleSchool.class.getMethods();
for (Method method : methods) {
System.out.println(method.getName());
//找add方法
if("add".equals(method.getName())){
//取第一個(gè)參數(shù)的class
Class clazz = method.getParameterTypes()[0];
//創(chuàng)建實(shí)例
clazz.newInstance();
}
}
}
}
異常
Exception in thread "main" java.lang.InstantiationException: IStudent
at java.lang.Class.newInstance(Class.java:427)
at Test.main(Test.java:44)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)
Caused by: java.lang.NoSuchMethodException: IStudent.<init>()
at java.lang.Class.getConstructor0(Class.java:3082)
at java.lang.Class.newInstance(Class.java:412)
... 6 more
通過debug弛矛,發(fā)現(xiàn)add同名方法有兩個(gè),參數(shù)分別是MiddleStudent和IStudent比然,而IStudent是接口丈氓,不能實(shí)例化。解決方式:
//判斷class類型是接口强法,返回
if(clazz.isInterface()){
continue;
}
為什么會(huì)有兩個(gè)add方法呢万俗?
javap -verbose MiddleSchool.class
public void add(MiddleStudent);
descriptor: (LMiddleStudent;)V
flags: ACC_PUBLIC
Code:
stack=0, locals=2, args_size=2
0: return
LineNumberTable:
line 29: 0
LocalVariableTable:
Start Length Slot Name Signature
0 1 0 this LMiddleSchool;
0 1 1 middleStudent LMiddleStudent;
public void add(IStudent);
descriptor: (LIStudent;)V
flags: ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: aload_1
2: checkcast #2 // class MiddleStudent
5: invokevirtual #3 // Method add:(LMiddleStudent;)V
8: return
LineNumberTable:
line 24: 0
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 this LMiddleSchool;
編譯后已經(jīng)是兩個(gè)add方法了,其中add(IStudent)的flags值A(chǔ)CC_BRIDGE饮怯,表示橋接方法(橋接方法實(shí)際是調(diào)用了實(shí)際的泛型方法)
為什么要生成橋接方法呢闰歪?
對(duì)于明確或不明確的泛型類,保證兼容性蓖墅。如List類:
public static void main(String[] args) {
//明確泛型
List<String> listString = new ArrayList<String>();
listString.add("abc");
//不明確泛型
List list = new ArrayList<>();
list.add(new Object());
//結(jié)果為true库倘,可見class是同一個(gè)
System.out.println(listString.getClass() == list.getClass());
}
泛型在編譯時(shí)編譯器會(huì)檢查往集合中添加的對(duì)象的類型是否匹配泛型類型,如果不正確會(huì)在編譯時(shí)就會(huì)發(fā)現(xiàn)錯(cuò)誤论矾,而不必等到運(yùn)行時(shí)才發(fā)現(xiàn)錯(cuò)誤教翩。因?yàn)榉盒褪窃?.5引入的,為了向前兼容拇囊,所以會(huì)在編譯時(shí)去掉泛型(泛型擦除)迂曲,但是我們還是可以通過反射API來獲取泛型的信息,在編譯時(shí)可以通過泛型來保證類型的正確性寥袭,而不必等到運(yùn)行時(shí)才發(fā)現(xiàn)類型不正確路捧。由于java泛型的擦除特性,如果不生成橋接方法传黄,那么與1.5之前的字節(jié)碼就不兼容了杰扫。
知識(shí)點(diǎn):
- 泛型類型擦除 type erasure
- 橋接方法
感謝前人栽樹