關於這幾種建立對象方式的詳解,請看這篇文章 java建立對象的五種方式java
接下來主要詳細介紹反射相關知識shell
反射之中包含了一個「反」字,因此想要解釋反射就必須先從「正」開始解釋。
通常狀況下,咱們使用某個類時一定知道它是什麼類,是用來作什麼的。因而咱們直接對這個類進行實例化,以後使用這個類對象進行操做。數據庫
Apple apple = new Apple(); //直接初始化,「正射」
apple.setPrice(4);
複製代碼
上面這樣子進行類對象的初始化,咱們能夠理解爲「正」。
而反射則是一開始並不知道我要初始化的類對象是什麼,天然也沒法使用 new 關鍵字來建立對象了。
這時候,咱們使用 JDK 提供的反射 API 進行反射調用:編程
Class clz = Class.forName("com.eft.reflect.Apple");
Method method = clz.getMethod("setPrice", int.class);
Constructor constructor = clz.getConstructor();
Object object = constructor.newInstance();
method.invoke(object, 4);
複製代碼
上面兩段代碼的執行結果,實際上是徹底同樣的。可是其思路徹底不同,第一段代碼在未運行時(編譯時)就已經肯定了要運行的類(Apple),而第二段代碼則是在運行時經過字符串值才得知要運行的類(com.eft.reflect.Apple)。api
因此說什麼是反射?數組
反射就是在運行時才知道要操做的類是什麼,而且能夠在運行時獲取類的完整構造,並調用對應的方法。緩存
JAVA反射機制是在運行狀態中,對於任意一個類,都可以知道這個類的全部屬性和方法;對於任意一個對象,都可以調用它的任意一個方法和屬性;這種動態獲取的信息以及動態調用對象的方法的功能稱爲java語言的反射機制。安全
反射機制很重要的一點就是「運行時」,其使得咱們能夠在程序運行時加載、探索以及使用編譯期間徹底未知的
.class
文件。換句話說,Java 程序能夠加載一個運行時才得知名稱的.class
文件,而後獲悉其完整構造,並生成其對象實體、或對其 fields(變量)設值、或調用其 methods(方法)。bash
反射就是讓你能夠經過名稱來獲得對象的信息(如類,屬性,方法等) 的技術數據結構
Java反射機制是Java語言被視爲「準動態」語言的關鍵性質。Java反射機制的核心就是容許在運行時經過Java Reflection APIs來取得已知名字的class類的內部信息(包括其modifiers(諸如public, static等等)、superclass(例如Object)、實現interfaces(例如Serializable),也包括fields和methods的全部信息),動態地生成此類,並調用其方法或修改其域(甚至是自己聲明爲private的域或方法)
類加載的流程:
要想使用反射,首先須要得到待操做的類所對應的 Class 對象。Java 中,不管生成某個類的多少個對象,這些對象都會對應於同一個 Class 對象。這個 Class 對象是由 JVM 生成的,經過它可以獲悉整個類的結構。因此,java.lang.Class 能夠視爲全部反射 API 的入口點。
反射的本質就是:在運行時,把 Java 類中的各類成分映射成一個個的 Java 對象。
經過前面引言-使用反射建立對象的例子,咱們瞭解了使用反射建立一個對象的步驟:
獲取類的 Class 對象實例
Class clz = Class.forName("com.eft.reflect.Person");
複製代碼
根據 Class 對象實例獲取 Constructor 對象
Constructor constructor = clz.getConstructor();
複製代碼
使用 Constructor 對象的 newInstance 方法獲取反射類對象
Object personObj = constructor.newInstance();
複製代碼
而若是要調用某一個方法,則須要通過下面的步驟:
Method setNameMethod = clz.getMethod("setName", String.class);
複製代碼
setNameMethod.invoke(personObj, "酸辣湯");
複製代碼
經過反射調用方法的測試代碼:
Class clz = Person.class;
Method setNameMethod = clz.getMethod("setName", String.class);
// Person person= (Person) clz.getConstructor().newInstance();
Person person= (Person) clz.newInstance();
setNameMethod.invoke(person, "酸辣湯888");//調用setName方法,傳入參數
Method getNameMethod = clz.getMethod("getName", null);
String name= (String) getNameMethod.invoke(person,null);//調用getName方法,獲取返回值
System.out.println("name:" +name);
運行結果:
name:酸辣湯888
複製代碼
到這裏,咱們已經可以掌握反射的基本使用。但若是要進一步掌握反射,還須要對反射的經常使用 API 有更深刻的理解
在 JDK 中,反射相關的 API 能夠分爲下面3個方面:
每一種類型(如:String,Integer,Person...)都會在初次使用時被加載進虛擬機內存的『方法區』中,包含類中定義的屬性字段,方法字節碼等信息。Java 中使用類 java.lang.Class 來指向一個類型信息,經過這個 Class 對象,咱們就能夠獲得該類的全部內部信息。
Class沒有公共構造方法。Class對象是在加載類時由Java虛擬機以及經過調用類加載器中的defineClass方法自動構造的。
獲取一個 Class 對象的方法主要有如下三種:
表格兩邊等價:
boolean.class | Boolean.TYPE |
---|---|
char.class | Character.TYPE |
byte.class | Byte.TYPE |
short.class | Short.TYPE |
int.class | Integer.TYPE |
long.class | Long.TYPE |
float.class | Float.TYPE |
double.class | Double.TYPE |
void.class | Void.TYPE |
這種方式最直接,但僅能獲取到我已知的類的Class對象,也就是工程內用過的類的對象均可以經過類.class方式獲取其Class對象,可是這種方式有一個不足就是對於未知的類,或者說不可見的類是不能獲取到其Class對象的。
如:person.getClass() Java中的祖先類 Object提供了一個方法getClass() 來獲取當着實例的Class對象,這種方式是開發中用的最多的方式,一樣,它也不能獲取到未知的類,好比說某個接口的實現類的Class對象。
API:
public final native Class<?> getClass();
複製代碼
這是一個native方法(一個Native Method就是一個java調用非java代碼的接口),而且不容許子類重寫,因此理論上全部類型的實例都具備同一個 getClass 方法。
使用:
Integer integer = new Integer(12);
Class clz=integer.getClass();
複製代碼
如:Class.forName("com.eft.xx.Person")
這種方式最經常使用,能夠獲取到任何類的Class對象,前提是該類存在,不然會拋出ClassNotFoundException異常。經過這種方式,咱們只須要知道類的全路徑(徹底限定名)便可獲取到其Class對象(若是存在的話).
API:
//因爲方法區 Class 類型信息由類加載器和類全限定名惟一肯定,
//因此想要去找這麼一個 Class 就必須提供類加載器和類全限定名,
//這個forName的重載方法容許你傳入類加載器和類全限定名來匹配方法區類型信息
//參數說明 name:class名,initialize是否加載static塊
public static Class<?> forName(String name, boolean initialize,
ClassLoader loader)
throws ClassNotFoundException{
..
}
// 這個 forName 方法默認使用調用者的類加載器,將類的.class文件加載到jvm中
//這裏傳入的initialize爲true,會去執行類中的static塊
public static Class<?> forName(String className)
throws ClassNotFoundException {
return forName0(className, true, ClassLoader.getCallerClassLoader());
}
複製代碼
使用:
Class clz = Class.forName("com.eft.reflect.Person");
複製代碼
public class InstanceofDemo {
public static void main(String[] args) {
ArrayList arrayList = new ArrayList();
if (arrayList instanceof List) {
System.out.println("ArrayList is List");
}
if (List.class.isInstance(arrayList)) {
System.out.println("ArrayList is List");
}
}
}
//Output:
//ArrayList is List
//ArrayList is List
複製代碼
被不一樣加載器加載過的類不屬於同一種類(即時包名、類名相同),所建立出的對象所屬的類也不相同,以下:
ClassLoader myLoader = new ClassLoader() {
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
try {
String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
InputStream is = getClass().getResourceAsStream("./bean/" + fileName);
if (is == null) {
return super.loadClass(name);//返回父 類加載器
}
byte[] b = new byte[is.available()];
is.read(b);
return defineClass(name, b, 0, b.length);
} catch (Exception e) {
throw new ClassNotFoundException();
}
}
};
Object obj = null;
Class clz=myLoader.loadClass("eft.reflex.bean.Person");
System.out.println("person被自定義類加載器加載了");
obj = clz.newInstance();
System.out.println(obj instanceof Person);
運行結果:
person被自定義類加載器加載完成
person的靜態塊被調用了
false
複製代碼
原理應該是:jvm會根據instanceof右邊的操做符用默認的類加載器去加載該類到方法區,而後根據左操做符對象的對象頭中類引用地址信息去查找方法區對應的類,若是找到的類是剛剛加載的類,則結果爲true,不然爲false。對於這個例子而言,obj對象指向的類在建立對象以前就已經加載到了方法區,而進行instanceof運算時,因爲方法區中已經存在的該類並不是用此時的默認加載器進行加載,所以jvm認爲該類尚未加載,因此右側操做符指向的類此時纔會加載,因此這個例子的結果爲false。 若是將括號中的類名改成測試類的類名,結果也是相似的,只不過測試類會在main方法執行以前就會被加載。
經過反射建立類對象主要有兩種方式:經過 Class 對象的 newInstance() 方法、經過 Constructor 對象的 newInstance() 方法。
第一種:經過 Class 對象的 newInstance() 方法。
第二種:經過 Constructor 對象的 newInstance() 方法
這個操做涉及到的幾個api以下:
注意: 1.getConstructors和getDeclaredConstructors獲取的構造器數組無序,因此不要經過索引來獲取指定的構造方法 2.getXXXX 與getDeclaredXXXX 區別是,帶Declared的方法不會返回父類成員,但會返回私有成員;不帶Declared的方法剛好相反下面相似的方法不贅述
使用舉例:
Class p = Person.class;
Constructor constructor1 = p.getConstructor();//獲取沒有任何參數的構造函數
Constructor constructor = p.getConstructor(String.class,int.class);//獲取Person(String name,int age)這個構造函數
複製代碼
使用舉例:
Class p = Person.class;
Constructor constructor = p.getDeclaredConstructor(String.class,int.class);//獲取Person(String name,int age)這個構造函數
複製代碼
//關閉訪問檢查,須要先將此設置爲true纔可經過反射訪問不可見的構造器
//但編譯器不容許使用普通的代碼該字段,由於僅適用於反射
public void setAccessible(boolean flag) //建立對象,使用可變長度的參數,可是在調用構造函數時必須爲每個參數提供一個準確的參量. public T newInstance(Object ... initargs) 使用舉例: //假設Person有個 private Person(String name){}的構造方法 Constructor constructor = Person.class.getConstructor(String.class);
constructor.setAccessible(true);
Person person = (Person)constructor.newInstance("酸辣湯");
複製代碼
使用Constructor建立對象的完整例子:詳見上面的使用反射建立對象-調用類對象的構造方法
使用反射能夠獲取Class對象的一系列屬性和方法,接下來列舉下Class類中相關的API
那麼以上三者區別是?舉個栗子
普通類名:
Class clz=Person.class;
System.out.println(clz);
System.out.println(clz.toString());
System.out.println(clz.getName());
System.out.println(clz.getCanonicalName());
System.out.println(clz.getSimpleName());
運行結果:
class reflex.Person class reflex.Person//Class裏面重寫了toString方法,而且在裏面調用了getName()方法 reflex.Person reflex.Person Person 複製代碼
數組:
Class clz=Person[][].class;
System.out.println(clz.getName());
System.out.println(clz.getCanonicalName());
System.out.println(clz.getSimpleName());
運行結果:
[[Lreflex.Person;
reflex.Person[][]
Person[][]
複製代碼
修飾符被包裝進一個int內,每個修飾符都是一個標誌位(置位或清零)。可使用java.lang.reflect.Modifier類中的如下方法來檢驗修飾符:
Modifier.isAbstract(int mod)
Modifier.isFinal(int mod)
Modifier.isInterface(int mod)
Modifier.isNative(int mod)
Modifier.isPrivate(int mod)
Modifier.isProtected(int mod)
Modifier.isPublic(int mod)v
Modifier.isStatic(int mod)
Modifier.isStrict(int mod)//若是mod包含strictfp(strict float point (精確浮點))修飾符,則爲true; 不然爲:false。
Modifier.isSynchronized(int mod)
Modifier.isTransient(int mod)
Modifier.isVolatile(int mod)
複製代碼
使用舉例:
Class clz= Person.class;
int modifier = clz.getModifiers();
System.out.println("修飾符是否爲public:" + Modifier.isPublic(modifier));
運行結果:
true
複製代碼
Modifier內部用&運算作判斷,如
public static boolean isPublic(int var0) {
return (var0 & 1) != 0;
}
複製代碼
從Package對象中你能夠訪問諸如名字等包信息。您還能夠訪問類路徑上這個包位於JAR文件中Manifest這個文件中指定的信息。例如,你能夠在Manifest文件中指定包的版本號。能夠在java.lang.Package中瞭解更多包類信息。
父類的Class對象和其它Class對象同樣是一個Class對象,能夠繼續使用反射
爲了獲得一個給定的類實現接口的完整列表,須要遞歸訪問類和其超類
使用舉例:
Class clz = Person.class;
Field field = clz.getDeclaredField("name");
System.out.println("獲取字段名稱:" + field.getName());
System.out.println("獲取字段類型:" +field.getType());
運行結果:
獲取字段名稱:name
獲取字段類型:class java.lang.String 複製代碼
注意:
使用舉例:
Person person= (Person) clz.newInstance();
field.setAccessible(true);//設置爲可訪問
field.set(person, "酸辣湯");//經過set方法設置字段值
System.out.println("經過get獲取的值:"+field.get(person));
運行結果:
經過get獲取的值:酸辣湯
複製代碼
看以下例子:
public class FinalTest {
public static void main(String[] args )throws Exception {
Field nameField = OneCity.class.getDeclaredField("name");
nameField.setAccessible(true);
nameField.set(null, "Shenzhen");
System.out.println(OneCity.getName());
}
}
class OneCity {
private static final String name = new String("Beijing");
public static String getName() {
return name;
}
}
複製代碼
輸出結果:
Exception in thread "main" java.lang.IllegalAccessException: Can not set static final java.lang.String field eft.reflex.OneCity.name to java.lang.String
複製代碼
那麼該如何用反射來修改它的值?
這時候咱們要作一個更完全的反射 — 對 Java 反射包中的類進行自我反射。Field 對象有個一個屬性叫作 modifiers, 它表示的是屬性是不是 public, private, static, final 等修飾的組合。這裏把這個 modifiers 也反射出來,進而把 nameField 的 final 約束也去掉了,回到了上面的情況了。完整代碼是這樣的:
public class FinalTest {
public static void main(String[] args )throws Exception {
Field nameField = OneCity.class.getDeclaredField("name");
Field modifiersField = Field.class.getDeclaredField("modifiers"); //①
modifiersField.setAccessible(true);
modifiersField.setInt(nameField, nameField.getModifiers() & ~Modifier.FINAL); //②
nameField.setAccessible(true); //這個一樣不能少,除非上面把 private 也拿掉了,可能還得 public
nameField.set(null, "Shenzhen");
System.out.println(OneCity.getName()); //輸出 Shenzhen
}
}
class OneCity {
private static final String name = new String("Beijing");
public static String getName() {
return name;
}
}
複製代碼
在 ① 處把 Field 的 modifiers 找到,它也是個私有變量,因此也要 setAccessible(ture)。接着在 ② 處把 nameField 的 modifiers 值改掉,是用的按位取反 ~ 再按位與 ~ 操做把 final 從修飾集中剔除掉,其餘特性如 private, static 保持不變。再想一下 modifierField.setInt() 能夠把 private 改成 public, 如此則修改 name 時無需 setAccessible(true) 了。
經過把屬性的 final 去掉, 就成功把 name 改爲了 Shenzhen。
注意上面爲什麼把 OneCity 的 name 賦值爲 new String(「Beijing」), 這是爲了避免讓 Java 編譯器內聯 name 到 getName() 方法中,而使 getName() 的方法體爲 return 「Beijing」,形成 getName() 永遠輸出 」Beijing」 。
null
做爲參數類型數組,或者不傳值)null
做爲參數類型數組,或者不傳值)使用舉例:
Person類裏有這麼一個方法:
private void testMethod(String param){
System.out.println("調用了testMethod方法,參數是:"+param);
}
//經過反射調用方法
Class clz = Person.class;
Method method=clz.getDeclaredMethod("testMethod",String.class);
method.setAccessible(true);
method.invoke(clz.newInstance(),"我是具體的參數值");
運行結果:
調用了testMethod方法,參數是:我是具體的參數值
複製代碼
關於Method更多API自行查看源碼
使用Java反射能夠在運行時檢查類的方法並調用它們。這能夠用來檢測一個給定的類有哪些get和set方法。能夠經過掃描一個類的全部方法並檢查每一個方法是不是get或set方法。
下面是一段用來找到類的get和set方法的代碼:
public static void printGettersSetters(Class aClass){
Method[]methods = aClass.getMethods();
for(Methodmethod : methods){
if(isGetter(method))System.out.println("getter: " + method);
if(isSetter(method))System.out.println("setter: " + method);
}
}
public staticboolean isGetter(Method method){
if(!method.getName().startsWith("get")) return false;
if(method.getParameterTypes().length!= 0) return false;
if(void.class.equals(method.getReturnType())return false;
return true;
}
public staticboolean isSetter(Method method){
if(!method.getName().startsWith("set"))return false;
if(method.getParameterTypes().length!= 1) return false;
return true;
}
複製代碼
關於註解,下回詳解
數組:定義多個類型相同的變量
咱們都知道,數組是一種特殊的類型,它本質上由虛擬機在運行時動態生成,因此在反射這種類型的時候會稍有不一樣。
由於數組類直接由虛擬機運行時動態建立,因此你不可能從一個數組類型的 Class 實例中獲得構造方法,編譯器根本沒機會爲類生成默認的構造器。因而你也不能以常規的方法經過 Constructor 來建立一個該類的實例對象。
若是你非要嘗試使用 Constructor 來建立一個新的實例的話,那麼運行時程序將告訴你沒法匹配一個構造器。像這樣:
Class<String[]> cls = String[].class;
Constructor constructor = cls.getConstructor();
String[] strs = (String[]) constructor.newInstance();
複製代碼
程序會拋出 NoSuchMethodException的異常,告訴你Class 實例中根本找不到一個無參的構造器
那咱們要怎麼動態建立一個數組??
Java 中有一個類 java.lang.reflect.Array 提供了一些靜態的方法用於動態的建立和獲取一個數組類型
補充下,Class類中獲取組件類型的API:
一維數組實例
//用反射來定義一個int類型,3長度的數組
int[] intArray = (int[]) Array.newInstance(int.class, 3);
Array.set(intArray, 0, 123);
Array.set(intArray, 1, 456);
Array.set(intArray, 2, 789);
System.out.println("intArray[0] = " + Array.get(intArray, 0));
System.out.println("intArray[1] = " + Array.get(intArray, 1));
System.out.println("intArray[2] = " + Array.get(intArray, 2));
//獲取類對象的一個數組
Class stringArrayClass = Array.newInstance(int.class, 0).getClass();
System.out.println("is array: " + stringArrayClass.isArray());
//獲取數組的組件類型
String[] strings = new String[3];
Class stringArrayClass2 = strings.getClass();
Class stringArrayComponentType = stringArrayClass2.getComponentType();
System.out.println(stringArrayComponentType);
運行結果:
intArray[0] = 123
intArray[1] = 456
intArray[2] = 789
is array: true
class java.lang.String 複製代碼
多維數組:
// 建立一個三維數組,每一個維度長度分別爲5,10,15
int[] dims = new int[] { 5, 10,15 };
Person[][][] array = (Person[][][]) Array.newInstance(Person.class, dims); // 可變參數,也能夠這樣寫:Object array = Array.newInstance(Integer.TYPE, 5,10,15);
Class<?> classType0 = array.getClass().getComponentType(); // 返回數組元素類型
System.out.println("三維數組元素類型:"+classType0); // 三維數組的元素爲二維數組
Object arrayObject = Array.get(array, 2);// 得到三維數組中索引爲2的元素,返回的是一個二維數組
System.out.println("二維數組元素類型:"+arrayObject.getClass().getComponentType());
Object oneObject = Array.get(arrayObject, 0);// 得到二維數組中索引爲0的數組,返回的是一個一維數組
System.out.println("一維數組元素類型:"+oneObject.getClass().getComponentType());
Array.set(oneObject,14,new Person("酸辣湯",18));//設置覺得數組索引爲3的位置的元素
System.out.println("未被設置元素的位置:"+array[0][0][0]);
System.out.println("已被設置元素的位置:"+array[2][0][14]);
運行結果:
三維數組元素類型:class [[Left.reflex.bean.Person;
二維數組元素類型:class [Left.reflex.bean.Person;
一維數組元素類型:class eft.reflex.bean.Person
person的靜態塊被調用了
未被設置元素的位置:null
已被設置元素的位置:Person{name='酸辣湯', age=18}
複製代碼
泛型是 Java 編譯器範圍內的概念,它可以在程序運行以前提供必定的安全檢查,而反射是運行時發生的,也就是說若是你反射調用一個泛型方法,實際上就繞過了編譯器的泛型檢查了。咱們看一段代碼:
ArrayList<Integer> list = new ArrayList<>();
list.add(23);
//list.add("fads");編譯不經過
Class<?> cls = list.getClass();
Method add = cls.getMethod("add",Object.class);
add.invoke(list,"hello");
for (Object obj:list){
System.out.println(obj);
}
運行結果:
23
hello
複製代碼
最終你會發現咱們從整型容器中取出一個字符串,由於虛擬機只管在運行時從方法區找到 ArrayList 這個類的類型信息並解析出它的 add 方法,接着執行這個方法。它不像通常的方法調用,調用以前編譯器會檢測這個方法存在不存在,參數類型是否匹配等,因此沒了編譯器的這層安全檢查,反射地調用方法更容易遇到問題。
在實際應用中,爲了得到和泛型有關的信息,Java就新增了幾種類型來表明不能被歸一到Class類中的類型,但又和基本數據類型齊名的類型,一般使用的是以下兩個:
爲何要引入這兩種呢,實際上,在經過反射得到成員變量時,Field類有一個方法是getType,能夠得到該字段的屬性,可是這種屬性若是是泛型就獲取不到了,因此才引入了上面兩種類型。
實例:
public class Person {
...
private Map<String,Integer> map;
...
}
Class<Person> clazz = Person.class;
Field f = clazz.getDeclaredField("map");
//經過getType方法只能得到普通類型
System.out.println("map的類型是:" + f.getType()); //打印Map
//1. 得到f的泛型類型
Type gType = f.getGenericType();
//2.若是gType是泛型類型對像
if(gType instanceof ParameterizedType)
{
ParameterizedType pType = (ParameterizedType)gType;
//獲取原始類型
Type rType = pType.getRawType();
System.out.println("原始類型是: " + rType);
//得到泛型類型的泛型參數
Type[] gArgs = pType.getActualTypeArguments();
//打印泛型參數
for(int i=0; i < gArgs.length; i ++)
{
System.out.println("第"+ i +"個泛型類型是:" + gArgs[i]);
}
}
else {
System.out.println("獲取泛型信息失敗");
}
運行結果:
map的類型是:interface java.util.Map
原始類型是: interface java.util.Map
第0個泛型類型是:class java.lang.String
第1個泛型類型是:class java.lang.Integer
複製代碼
只列舉個別方法的源碼,其餘的有興趣能夠自行查看源碼(大部分都是native方法)
獲取到Method對象以後,調用invoke方法的流程以下:
能夠看到,調用Method.invoke以後,會直接去調MethodAccessor.invoke。MethodAccessor就是上面提到的全部同名method共享的一個實例,由ReflectionFactory建立。建立機制採用了一種名爲inflation的方式(JDK1.4以後):若是該方法的累計調用次數<=15,會建立出NativeMethodAccessorImpl,它的實現就是直接調用native方法實現反射;若是該方法的累計調用次數>15,會由java代碼建立出字節碼組裝而成的MethodAccessorImpl。(是否採用inflation和15這個數字均可以在jvm參數中調整)
以調用MyClass.myMethod(String s)爲例,生成出的MethodAccessorImpl字節碼翻譯成Java代碼大體以下:
public class GeneratedMethodAccessor1 extends MethodAccessorImpl {
public Object invoke(Object obj, Object[] args) throws Exception {
try {
MyClass target = (MyClass) obj;
String arg0 = (String) args[0];
target.myMethod(arg0);
} catch (Throwable t) {
throw new InvocationTargetException(t);
}
}
}
複製代碼
至於native方法的實現,因爲比較深刻本文就不探討了
public static void main(String[] args) throws Exception {
// directCall();//直接調用
reflectCall();//反射調用
}
public static void target(int i) {
}
//直接調用
private static void directCall() {
long current = System.currentTimeMillis();
for (int i = 1; i <= 2_000_000_000; i++) {
if (i % 100_000_000 == 0) {
long temp = System.currentTimeMillis();
System.out.println(temp - current);
current = temp;
}
MethodTest.target(128);
}
}
//反射調用同一個方法
private static void reflectCall() throws Exception {
Class<?> klass = Class.forName("eft.reflex.MethodTest");
Method method = klass.getMethod("target", int.class);
long current = System.currentTimeMillis();
for (int i = 1; i <= 2_000_000_000; i++) {
if (i % 100_000_000 == 0) {
long temp = System.currentTimeMillis();
System.out.println(temp - current);
current = temp;
}
method.invoke(null, 128);
}
}
運行結果:
直接調用結果:
...
121
126
105
115
100 (取最後5個值,做爲預熱後的峯值性能)
反射調用結果:
...
573
581
593
557
594 (取最後5個值,做爲預熱後的峯值性能)
複製代碼
結果分析:普通調用做爲性能基準,大約100多秒,經過反射調用的耗時大約爲基準的4倍
先看下使用反射調用的字節碼文件:
63: aload_1 // 加載Method對象
64: aconst_null // 靜態方法,反射調用的第一個參數爲null
65: iconst_1
66: anewarray // 生成一個長度爲1的Object數組
69: dup
70: iconst_0
71: sipush 128
74: invokestatic Integer.valueOf // 將128自動裝箱成Integer
77: aastore // 存入Object數組
78: invokevirtual Method.invoke // 反射調用
複製代碼
能夠看出反射調用前的兩個動做
上述兩個步驟會帶來性能開銷和GC
經測試,峯值性能:280.4ms,爲基準耗時的2.5倍
private static void reflectCall() throws Exception {
Class<?> klass = Class.forName("eft.reflex.MethodTest");
Method method = klass.getMethod("target", int.class);
// 在循環外構造參數數組
Object[] arg = new Object[1];
arg[0] = 128;
long current = System.currentTimeMillis();
for (int i = 1; i <= 2_000_000_000; i++) {
if (i % 100_000_000 == 0) {
long temp = System.currentTimeMillis();
System.out.println(temp - current);
current = temp;
}
method.invoke(null, 128);
}
}
複製代碼
字節碼:
80: aload_2 // 加載Method對象
81: aconst_null // 靜態方法,反射調用的第一個參數爲null
82: aload_3
83: invokevirtual Method.invoke // 反射調用,無anewarray指令
複製代碼
經測試,峯值性能:312.4ms,爲基準耗時的2.8倍
// -Djava.lang.Integer.IntegerCache.high=128
// -Dsun.reflect.noInflation=true
public static void main(String[] args) throws Exception {
Class<?> klass = Class.forName("eft.reflex.MethodTest");
Method method = klass.getMethod("target", int.class);
// 關閉權限檢查
method.setAccessible(true);
long current = System.currentTimeMillis();
for (int i = 1; i <= 2_000_000_000; i++) {
if (i % 100_000_000 == 0) {
long temp = System.currentTimeMillis();
System.out.println(temp - current);
current = temp;
}
method.invoke(null, 128);
}
}
複製代碼
峯值性能:186.2ms,爲基準耗時的1.7倍
1.增長程序的靈活性,避免將程序寫死到代碼裏。
例:定義了一個接口,實現這個接口的類有20個,程序裏用到了這個實現類的地方有好多地方,若是不使用配置文件手寫的話,代碼的改動量很大,由於每一個地方都要改並且不容易定位,若是你在編寫以前先將接口與實現類的寫在配置文件裏,下次只需改配置文件,利用反射(java API已經封裝好了,直接用就能夠用 Class.newInstance())就可完成
2.代碼簡潔,提升代碼的複用率,外部調用方便
既然小偷能夠訪問和搬走私有成員傢俱,那封裝成防盜門還有意義麼?這是同樣的道理,而且Java從應用層給咱們提供了安全管理機制——安全管理器,每一個Java應用均可以擁有本身的安全管理器,它會在運行階段檢查須要保護的資源的訪問權限及其它規定的操做權限,保護系統免受惡意操做攻擊,以達到系統的安全策略。因此其實反射在使用時,內部有安全控制,若是安全設置禁止了這些,那麼反射機制就沒法訪問私有成員。
1.反射大概比直接調用慢50~100倍,可是須要你在執行100萬遍的時候纔會有所感受
2.判斷一個函數的性能,你須要把這個函數執行100萬遍甚至1000萬遍
3.若是你只是偶爾調用一下反射,請忘記反射帶來的性能影響
4.若是你須要大量調用反射,請考慮緩存。
5.你的編程的思想纔是限制你程序性能的最主要的因素
工廠模式:Factory類中用反射的話,添加了一個新的類以後,就不須要再修改工廠類Factory了,以下例子
數據庫JDBC中經過Class.forName(Driver).來得到數據庫鏈接驅動
開發通用框架 - 反射最重要的用途就是開發各類通用框架。不少框架(好比 Spring)都是配置化的(好比經過 XML 文件配置 JavaBean、Filter 等),爲了保證框架的通用性,它們可能須要根據配置文件加載不一樣的對象或類,調用不一樣的方法,這個時候就必須用到反射——運行時動態加載須要加載的對象。
動態代理 - 在切面編程(AOP)中,須要攔截特定的方法,一般,會選擇動態代理方式。這時,就須要反射技術來實現了。
註解 - 註解自己僅僅是起到標記做用,它須要利用反射機制,根據註解標記去調用註解解釋器,執行行爲。若是沒有反射機制,註解並不比註釋更有用。
可擴展性功能 - 應用程序能夠經過使用徹底限定名稱建立可擴展性對象實例來使用外部的用戶定義類。
使用反射的工廠模式舉例:
//用反射機制實現工廠模式:
interface fruit{
public abstract void eat();
}
class Apple implements fruit{
public void eat(){
System.out.println("Apple");
}
}
class Orange implements fruit{
public void eat(){
System.out.println("Orange");
}
}
class Factory{
public static fruit getInstance(String ClassName){
fruit f=null;
try{
f=(fruit)Class.forName(ClassName).newInstance();
}catch (Exception e) {
e.printStackTrace();
}
return f;
}
}
客戶端:
class hello{
public static void main(String[] a){
fruit f=Factory.getInstance("Reflect.Apple");
if(f!=null){
f.eat();
}
}
}
複製代碼
內省(自省):
內省基於反射實現,也就是對反射的再次包裝,主要用於操做JavaBean,經過內省 能夠獲取bean的getter/setter
通俗地說:javaBean 具備的自省機制能夠在不知道javaBean都有哪些屬性的狀況下,設置他們的值。核心也是反射機制
通常在開發框架時,當須要操做一個JavaBean時,若是一直用反射來操做,顯得很麻煩;因此sun公司開發一套API專門來用來操做JavaBean
內省是 Java 語言對 Bean 類屬性、事件的一種缺省處理方法。例如類 A 中有屬性 name, 那咱們能夠經過 getName,setName 來獲得其值或者設置新的值。經過 getName/setName 來訪問 name 屬性,這就是默認的規則。 Java 中提供了一套 API 用來訪問某個屬性的 getter/setter 方法,經過這些 API 可使你不須要了解這個規則(但你最好仍是要搞清楚),這些 API 存放於包 java.beans 中。
通常的作法是經過類 Introspector 來獲取某個對象的 BeanInfo 信息,而後經過 BeanInfo 來獲取屬性的描述器( PropertyDescriptor ),經過這個屬性描述器就能夠獲取某個屬性對應的 getter/setter 方法,而後咱們就能夠經過反射機制來調用這些方法。下面咱們來看一個例子,這個例子把某個對象的全部屬性名稱和值都打印出來:
package introspector;
//這些api都是在java.beans下(rt.jar包下)
import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
public class IntrospectorDemo{
String name;
public static void main(String[] args) throws Exception{
IntrospectorDemo demo = new IntrospectorDemo();
// 若是不想把父類的屬性也列出來的話,
//那 getBeanInfo 的第二個參數填寫父類的信息
BeanInfo bi = Introspector.getBeanInfo(demo.getClass(), Object. class );//Object類是全部Java類的根父類
PropertyDescriptor[] props = bi.getPropertyDescriptors();//得到屬性的描述器
for ( int i=0;i<props.length;i++){
System.out.println("獲取屬性的Class對象:"+props[i].getPropertyType());
props[i].getWriteMethod().invoke(demo, "酸辣湯" );//得到setName方法,並使用invoke調用
System.out.println("讀取屬性值:"+props[i].getReadMethod().invoke(demo, null ));
}
}
public String getName() {
return name;
}
public void setName(String name) {
this .name = name;
}
}
運行結果:
獲取屬性的Class對象:class java.lang.String 讀取屬性值:酸辣湯 複製代碼
JDK內省類庫:PropertyDescriptor類:
PropertyDescriptor類表示JavaBean類經過存儲器導出一個屬性。主要方法:
1. getPropertyType(),得到屬性的Class對象;
2. getReadMethod(),得到用於讀取屬性值的方法;(如上面的獲取getName方法)
3.getWriteMethod(),得到用於寫入屬性值的方法;(如上面的獲取setName方法)
4.hashCode(),獲取對象的哈希值;
5. setReadMethod(Method readMethod),設置用於讀取屬性值的方法;
6. setWriteMethod(Method writeMethod),設置用於寫入屬性值的方法。
Apache開發了一套簡單、易用的API來操做Bean的屬性——BeanUtils工具包。