茫茫網海中的冷日
         
茫茫網海中的冷日
發生過的事,不可能遺忘,只是想不起來而已!
 恭喜您是本站第 1671862 位訪客!  登入  | 註冊
主選單

Google 自訂搜尋

Goole 廣告

隨機相片
IMG_0110.jpg

授權條款

使用者登入
使用者名稱:

密碼:


忘了密碼?

現在就註冊!

爪哇咖啡屋 : [轉貼]Java 反射機制

發表者 討論內容
冷日
(冷日)
Webmaster
  • 註冊日: 2008/2/19
  • 來自:
  • 發表數: 15771
[轉貼]Java 反射機制

一、Java反射機制概述

Java放射機制是指在==執行狀態==中,對於任意一個類,都能知道這個類的所有屬性和方法;對於任意一個物件,都能呼叫它的任意一個方法和屬性;這種動態獲取資訊及動態呼叫方法的功能成為Java的反射機制

二、反射的作用

  1. 利用Java機制,在Java程式中可以動態的去呼叫一些protected甚至是private的方法或類,這樣就可以在很大程度上滿足一些特殊需求。
  2. Android SDK的原始碼中,很多類或方法中經常加上了“@hide”註釋標記,它的作用是使這個方法或類在生成SDK時不可見,所以程式可能無法編譯通過,而且在最終釋出的時候,就可能存在一些問題。對於這種問題,第一種方法就是自己去掉原始碼中的“@hide”標記,然後在重新編譯生成一個SDK。另一種方法就是使用Java反射機制,利用反射機制可以訪問存在訪問許可權的方法或修改其訪問域。
  3. 可能有人會有疑問,明明直接new物件就好了,為什麼非要用反射呢?程式碼量不是反而增加了?其實反射的初衷不是方便你去建立一個物件,而是讓你在寫程式碼的時候可以更加靈活,降低耦合,提高程式碼的自適應能力。

怎麼樣降低耦合度,提高程式碼的自適應能力?

通過介面實現,但是介面如果需要用到new關鍵字,這時候耦合問題又會出現

舉個例子:



/**
/**
* @ClassName: TestReflection
* @Model : (所屬模組名稱)
* @Description: 使用反射降低耦合度(通過介面來實現)
* @author Administrator
* @date 2017年6月2日 下午5:09:38
*/
public class TestReflection {
/**
/**
* main (這裡用一句話描述這個方法的作用)
* @param args
* void
* @ModifiedPerson Administrator
* @date 2017年6月2日 下午5:09:38
*/
public static void main(String[] args) {
//普通寫法,使用New 關鍵字
ITest iTest = createITest();
iTest.testReflect();
ITest iTest2 = createITest();
iTest2.testReflect();
}
/**
* createITest 普通寫法,使用New關鍵字,但是假設有1000個不同ITest需要建立,那你打算寫1000個 if語句來返回不同的ITest物件?
* @param name
* @return
* ITest
* @ModifiedPerson Administrator
* @date 2017年6月2日 下午5:32:21
*/
public static ITest createITest(String name){
)) {
return new ITestImpl1();
} )){
return new ITestImpl2();
}
return null;
}
}
interface ITest{
public void testReflect();
}
class ITestImpl1 implements ITest{
/* (non-Javadoc)
* <p>Title: test</p>
* <p>Description: </p>
* @see ITest#test()
*/
@Override
public void testReflect
() {
System.out.println();
}
}
class ITestImpl2 implements ITest{
/* (non-Javadoc)
* <p>Title: testReflect</p>
* <p>Description: </p>
* @see ITest#testReflect()
*/
@Override
public void testReflect
() {
System.out.println();
}
}

假設有1000個不同ITest需要建立,那你打算寫1000個 if語句來返回不同的ITest物件?

如果使用反射機制呢?



/**
/**
* @ClassName: TestReflection
* @Model : (所屬模組名稱)
* @Description: 使用反射降低耦合度(通過介面來實現)
* @author Administrator
* @date 2017年6月2日 下午5:09:38
*/
public class TestReflection {
/**
/**
* main (這裡用一句話描述這個方法的作用)
* @param args
* void
* @ModifiedPerson Administrator
* @date 2017年6月2日 下午5:09:38
*/
public static void main(String[] args) {
//普通寫法,使用New 關鍵字
ITest iTest = createITest();
iTest.testReflect();
ITest iTest2 = createITest();
iTest2.testReflect();
//使用反射機制
ITest iTest3 = createITest2();
iTest3.testReflect();
ITest iTest4 = createITest2();
iTest4.testReflect();
}
/**
* createITest 普通寫法,使用New關鍵字,但是假設有1000個不同ITest需要建立,那你打算寫1000個 if語句來返回不同的ITest物件?
* @param name
* @return
* ITest
* @ModifiedPerson Administrator
* @date 2017年6月2日 下午5:32:21
*/
public static ITest createITest(String name){
)) {
return new ITestImpl1();
} )){
return new ITestImpl2();
}
return null;
}
/**
* createITest2 使用反射機制:當有1000個不同ITest需要建立時,不用針對每個建立ITest物件
* @param name
* @return
* ITest
* @ModifiedPerson Administrator
* @date 2017年6月2日 下午5:34:55
*/
public static ITest createITest2(String name){
try {
Class<?> class1 = Class.forName(name);
ITest iTest = (ITest) class1.newInstance();
return iTest;
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
}
interface ITest{
public void testReflect();
}
class ITestImpl1 implements ITest{
/* (non-Javadoc)
* <p>Title: test</p>
* <p>Description: </p>
* @see ITest#test()
*/
@Override
public void testReflect
() {
System.out.println();
}
}
class ITestImpl2 implements ITest{
/* (non-Javadoc)
* <p>Title: testReflect</p>
* <p>Description: </p>
* @see ITest#testReflect()
*/
@Override
public void testReflect
() {
System.out.println();
}
}

利用反射機制進行解耦的原理:就是利用反射機制"動態"的建立物件:向createITest()方法傳入Hero類的包名.類名 通過載入指定的類,然後再例項化物件.

三、理解Class類和類型別

要想理解反射,首先要理解Class類,因為Class類是反射實現的基礎。

3.1 類是物件嗎

在物件導向的世界裡,萬物皆物件,物件是一個類的例項,所以類是java.lang.Class類的例項物件,而Class是所有類的類(This is a class named Class),Class類是類型別,即類的型別。

3.2 Class的物件的獲取

對於普通的物件,我們都是直接通過new來建立一個物件。

Student sdutent = new Student();

但是Class的物件是類,不能通過new來建立。Class的原始碼描述如下:


/*
* Private constructor. Only the Java Virtual Machine creates Class objects.
* This constructor is not used and prevents the default constructor being
* generated.
*/
private Class(ClassLoader loader) {
// Initialize final field for classLoader. The initialization value of non-null
// prevents future JIT optimizations from assuming this final field is null.
classLoader = loader;
}

構造器是私有的,只有Java虛擬機器(JVM)可以建立Class的物件,不能像普通類一樣new一個Class物件。只能是通過已有的類來得到一個Class物件(三種方法):

第一種方法

forName(包名.類名)
Student s1 = (Student)cls.newInstance();

  1. 通過JVM查詢並載入指定的類
  2. 呼叫newInstance()方法讓載入完的類在記憶體中建立對應的例項,並把例項賦值給s1
第二種方法

Student s = new Student;
Class<?> cls = s.getClass;
Student s2 = (Student)cls.newInstance();

  1. 在記憶體中新建一個Student的例項,物件s對這個記憶體地址進行引用
  2. 物件s呼叫getClass()方法返回物件s所對應的Class物件
  3. 呼叫newInstance()方法讓Class物件在記憶體中建立物件的例項,並讓s2引用例項的記憶體地址
第三種方法

Class<?> cls = Student.Class();
Student s3 = (Student)cls.newInstance();

  1. 獲取指定型別的Class物件,這裡是Student
  2. 呼叫newInstance()方法讓Class物件在記憶體中建立對應例項,並讓s3引用例項的記憶體地址
注意:
  • cls.newInstance()方法返回的是一個泛型T,我們要強轉成Student類
  • cls.newInstance()預設返回的是Student類的無引數構造物件
  • 被反射機制載入的類必須有無引數構造方法,否者執行會丟擲異常

通過類型別建立類和通過new建立類的不同之處是:類型別建立的是動態載入類。

四、動態載入類

程式執行分為編譯器和執行期,編譯時刻載入一個類就稱為靜態載入類,執行時刻載入類稱為動態載入類,下面通過一個例項來講解:

現在拋開IDE工具,用記事本手寫類,這是為了方便我們利用cmd命令列手動編譯和執行一個類,從而更好理解動態載入類和靜態載入類的區別。

首先寫First.java


class First
{
public static void main(String[] args)
{
.equals(args[0]))
{
// 靜態載入類,在編譯時載入
Word w = new Word();
w.start();
}
.equals(args[0]))
{
Excel e = new Excel();
e.start();
}
}
}

然後進入cmd編譯First.java

由於我們new的兩個類Word和Excel沒有編譯,所以報錯了,這就是靜態載入類的缺點,即必須在編譯時期就載入所有可能用到的類,而我們希望實現的是執行時用到哪個類就載入哪個類,下面通過動態載入類來加以改進。

改進以後的類:FirstBetter.java


class FirstBetter
{
public static void main(String[] args)
{
try
{
// 動態載入類,在執行時載入
Class c = Class.forName(args[0]);
// 通過類型別,建立該類物件
FirstAble oa = (FirstAble)c.newInstance();
oa.start();
}
catch (Exception e)
{
e.printStackTrace();
}
}
}

這裡動態載入了名為args[0]的類,而args[0]是在執行期輸入給main方法的第一個引數,如果你輸入Word那麼就會載入Word.java,這時候就需要在與FirstBetter.java相同路徑下面建立Word.java;同理,如果你輸入Excel就需要載入Excel.java了。 其中FirstAble是一個介面,上面動態載入的類如Word、Excel就是實現了FirstAble,體現了多型的思想,這種動態載入和多型的思想可以使具體功能和程式碼解耦,也就是隨時想新增某個功能(如Word和Excel都不要了,我要PPT)都能動態新增,而不改動原來的程式碼。

其中FirstAble介面如下:


interface FirstAble
{
public void start();
}

Word類:


class Word implements FirstAble
{
public void start()
{
System.out.println();
}
}

按順序編譯、執行上面的類。

五、獲取類的資訊

一個類中通常包含屬性和方法,通過反射獲取類的構造方法、成員方法、成員變數、修飾(方法和變數的)等。

5.1 獲取類的建構函式

建構函式中都包括什麼:建構函式引數

類的成建構函式是一個物件,它是java.lang.reflect.Constructor的一個物件,所以我們通過java.lang.reflect.Constructor裡面封裝的方法來獲取這些資訊。

1、單獨獲取某個建構函式

通過Class類的以下方法實現:

  • public Constructor getDeclaredConstructor(Class<?>... parameterTypes) // 根據建構函式的引數,返回一個具體的建構函式(不分public和非public屬性)
  • public Constructor getConstructor(Class<?>... parameterTypes) // 根據建構函式的引數,返回一個具體的具有public屬性的建構函式
  • 引數parameterTypes為建構函式引數類的類型別列表。

例如類A有如下一個建構函式:


public A(String a, int b) {
// code body
}

那麼就可以通過:


Constructor constructor = a.getDeclaredConstructor(String.class, int.class);

來獲取這個建構函式。

2、獲取所有的建構函式

通過Class類的以下方法實現:

  • Constructor getDeclaredConstructors() 返回該類中所有的建構函式陣列(不分public和非public屬性)
  • Constructor getConstructors() 返回所有具有public屬性的建構函式陣列

可以通過以下步驟實現:

1、已知一個物件,獲取其類的類型別


Class c = obj.getClass();

2、獲取該類的所有建構函式,放在一個陣列中


Constructor[] constructors = c.getDeclaredConstructors();

3、遍歷建構函式陣列,獲得某個建構函式constructor


for (Constructor constructor : constructors)

4、得到建構函式引數型別的類型別陣列


Class[] paramTypes = constructor.getParameterTypes();

5、遍歷引數類的類型別陣列,得到某個引數的類型別class1


for (Class class1 : paramTypes)

6、得到該引數的型別名


String paramName = class1.getName();

例子:


forName2GetConstructor() {
try {
//第一種方法:forName()
Class<?> class1 = Class.forName();
Constructor<?>[] constructors = class1.getDeclaredConstructors();
for (Constructor<?> constructor : constructors) {
System.out.print();
Class<?>[] parameterTypes = constructor.getParameterTypes();
for (Class<?> class2 : parameterTypes) {
System.out.print(class2.getName()+);
}
System.out.print();
}
//呼叫構造方法
Constructor<?> constructor = class1.getDeclaredConstructor(String.class, String.class, int.class);
StudentInfo instance = (StudentInfo) constructor.newInstance(, 18);
System.out.println(+instance.getScore());
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

5.2 獲取類的成員方法

成員方法中都包括什麼:返回值型別+方法名+引數型別

在Java中,類的成員方法也是一個物件,它是java.lang.reflect.Method的一個物件,所以我們通過java.lang.reflect.Method裡面封裝的方法來獲取這些資訊.

1、單獨獲取某一個方法
  • Method getMethod(String name, Class[] params) 根據方法名和引數,返回一個具體的具有public屬性的方法
  • Method getDeclaredMethod(String name, Class[] params) 根據方法名和引數,返回一個具體的方法(不分public和非public屬性)
  • 兩個引數分別是方法名和方法引數類的類型別列表。

例如類A有如下一個方法:


print(String a, int b) {
// code body
}

現在知道A有一個物件a,那麼就可以通過:


Class c = a.getClass();
Method method = c.getDeclaredMethod(, String.class, int.class);

來獲取這個方法。

如何呼叫獲取到的方法

那得到方法以後如何呼叫這個方法呢,通過Method類的以下方法實現:


public Object invoke(Object obj, Object… args)

兩個引數分別是這個方法所屬的物件和這個方法需要的引數,還是用上面的例子來說明,通過:


, 10);

和通過普通呼叫:


, 10);

效果完全一樣,這就是方法的反射,invoke()方法可以反過來將其物件作為引數來呼叫方法,完全跟正常情況反了過來。

2、獲取類中所有成員方法的資訊
  • Method[] getMethods() 返回所有具有public屬性的方法陣列
  • Method[] getDeclaredMethods() 返回該類中的所有的方法陣列(不分public和非public屬性)
注意:

getMethods():用於獲取類的所有的public修飾域的成員方法,包括從父類繼承的public方法和實現介面的public方法;

getDeclaredMethods():用於獲取在當前類中定義的所有的成員方法和實現的介面方法,不包括從父類繼承的方法。

大家可以查考一下開發文件的解釋:


for the class C represented by this Class.
&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp; Methods may be declared in the superclasses of C.
&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp; The elements in no particular order.
getDeclaredMethods() - Returns a Method object which represents the method matching the specified name and parameter types
&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp; that is declared by the class represented by this Class.

因此在示例程式碼的方法get_Reflection_Method(...)中,ReflectionTest類繼承了Object類,實現了actionPerformed方法,並定義如下成員方法:

image
通過這兩個語句執行後的結果不同:

  a、Method[] methods = temp.getDeclaredMethods()執行後結果如下:

   

image


  b、Method[] methods = temp.getMethods()執行後,結果如下:

  

image
   

Method類:


public invoke( obj,                       ... args)

對帶有指定引數的指定物件呼叫由此 Method物件表示的底層方法。個別引數被自動解包,以便與基本形參相匹配,基本引數和引用引數都隨需服從方法呼叫轉換。

  • 如果底層方法是靜態的,那麼可以忽略指定的 obj引數。該引數可以為 null。

  • 如果底層方法所需的形引數為 0,則所提供的 args陣列長度可以為 0 或 null。

  • 如果底層方法是例項方法,則使用動態方法查詢來呼叫它,這一點記錄在 Java Language Specification, Second Edition 的第 15.12.4.4 節中;在發生基於目標物件的執行時型別的重寫時更應該這樣做。

  • 如果底層方法是靜態的,並且尚未初始化宣告此方法的類,則會將其初始化。

  • 如果方法正常完成,則將該方法返回的值返回給呼叫者;如果該值為基本型別,則首先適當地將其包裝在物件中。但是,如果該值的型別為一組基本型別,則陣列元素不被包裝在物件中;換句話說,將返回基本型別的陣列。如果底層方法返回型別為 void,則該呼叫返回 null。

引數:

obj- 從中呼叫底層方法的物件

args- 用於方法呼叫的引數

返回:

使用引數 args在 obj上指派該物件所表示方法的結果   如果想要獲得類中所有而非單獨某個成員方法的資訊,可以通過以下幾步來實現:

1、已知一個物件,獲取其類的類型別


Class c = obj.getClass();

2、獲取該類的所有方法,放在一個陣列中


Method[] methods = c.getDeclaredMethods();

3、遍歷方法陣列,獲得某個方法method


for (Method method : methods)

4、得到方法返回值型別的類型別


returnType = method.getReturnType();

5、得到方法返回值型別的名稱


returnType.getName();

6、得到方法的名稱


String methodName = method.getName();

7、得到所有引數型別的類型別陣列


Class[] paramTypes = method.getParameterTypes();

8、遍歷引數類的類型別陣列,得到某個引數的類型別class1


for (Class class1 : paramTypes)

9、得到該引數的型別名


String paramName = class1.getName();

例子:


getClass2GetMethod(){
StudentInfo studentInfo = new StudentInfo();
Class<? extends StudentInfo> class1 = studentInfo.getClass();
Method[] methods = class1.getDeclaredMethods();
for (Method method : methods) {
System.out.print(+method.getName());
Class<?>[] parameterTypes = method.getParameterTypes();
for (Class<?> class2 : parameterTypes) {
System.out.print(+class2.getName());
}
Class<?> returnType = method.getReturnType();
System.out.println(returnType.getName());
}
try {
//1、呼叫非靜態方法
Method method1 = class1.getDeclaredMethod(, String.class);
Method method2 = class1.getDeclaredMethod();
method1.invoke(studentInfo, );
String name = (String) method2.invoke(studentInfo);
System.out.println(+name);
//2、呼叫靜態方法:將invoke的第一個引數設定為null
System.out.println();
Method method3 = class1.getDeclaredMethod();
method3.invoke(null);
Method method4 = class1.getDeclaredMethod(, String.class);
method4.invoke(null, );
//3、呼叫私有方法:方法呼叫之前,需要將方法setAccessible
Method method5 = class1.getDeclaredMethod();
method5.setAccessible(true);
System.out.println(+method5.invoke(studentInfo));
//4、呼叫包含物件引數的方法(多個引數):
Method method6 = class1.getDeclaredMethod(, String.class,int.class,MsgClass.class);
//Method method6 = class1.getDeclaredMethod(, new Class[]{String.class,int.class,MsgClass.class});
//method6.invoke(studentInfo, new Object[]{,28,new MsgClass()});
method6.invoke(studentInfo,,28,new MsgClass());
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

5.3 獲取類的成員變數

成員變數中都包括什麼:成員變數型別+成員變數名

類的成員變數也是一個物件,它是java.lang.reflect.Field的一個物件,所以我們通過java.lang.reflect.Field裡面封裝的方法來獲取這些資訊。

1、單獨獲取某個成員變數
  • Field getField(String name) 根據變數名,返回一個具體的具有public屬性的成員變數
  • Field getDeclaredField(String name) 根據變數名,返回一個成員變數(不分public和非public屬性)
  • 引數是成員變數的名字。

例如一個類A有如下成員變數:


private int n;

如果A有一個物件a,那麼就可以這樣得到其成員變數:


Class c = a.getClass();
Field field = c.getDeclaredField();

2、獲取所有的成員變數
  • Field[] getFields() 返回具有public屬性的成員變數的陣列
  • Field[] getDelcaredField() 返回所有成員變數組成的陣列(不分public和非public屬性)

同樣,如果想要獲取所有成員變數的資訊,可以通過以下幾步:

1、已知一個物件,獲取其類的類型別


Class c = obj.getClass();

2、獲取該類的所有成員變數,放在一個陣列中


Field[] fields = c.getDeclaredFields();

3、遍歷變數陣列,獲得某個成員變數field


for (Field field : fields)

4、得到成員變數型別的類型別


Class fieldType = field.getType();

5、得到成員變數的型別名


typeName = fieldType.getName();

6、得到成員變數的名稱


String fieldName = field.getName();

例子:


class2GetField() {
Class<?> class1 = StudentInfo.class;
Field[] fields = class1.getDeclaredFields();
for (Field field : fields) {
System.out.print(+field.getName());
Class<?> type = field.getType();
System.out.println(+type.getName());
}
try {
//訪問非私有變數
StudentInfo studentInfo = new StudentInfo();
Field field = class1.getDeclaredField();
System.out.println(+field.get(studentInfo));
Field field2 = class1.getDeclaredField();
System.out.println(+field2.get(studentInfo));
//訪問私有變數
Field field3 = class1.getDeclaredField();
field3.setAccessible(true);
System.out.println(+field3.get(studentInfo));
field3.set(studentInfo, 18);
System.out.println(+field3.get(studentInfo));
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

5.4 獲取類、方法、屬性的修飾域

類Class、Method、Constructor、Field都有一個public方法int getModifiers()。該方法返回一個int型別的數,表示被修飾物件( Class、 Method、 Constructor、 Field )的修飾型別的組合值。

  在開發文件中,可以查閱到,Modifier類中定義了若干特定的修飾域,每個修飾域都是一個固定的int數值,列表如下:

image


    

  該類不僅提供了若干用於判斷是否擁有某中修飾域的方法boolean isXXXXX(int modifiers),還提供一個String toString(int modifier)方法,用於將一個表示修飾域組合值的int數轉換成描述修飾域的字串。

image

5.5 通過反射獲取私有成員變數和私有方法

Person類


public class Person {
private String name = ;
private String age;
public String getName
() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
//main函式
Person person = new Person();
//列印沒有改變屬性之前的name值
System.out.println());
person.setName();
//列印修改之後的name值
System.out.println());
/**
* 通過反射獲取私有的成員變數
*
* @param person
* @return
*/
private Object getPrivateValue(Person person, String fieldName) {
try {
Field field = person.getClass().getDeclaredField(fieldName);
// 引數值為true,開啟禁用訪問控制檢查
//true) 並不是將方法的訪問許可權改成了public,而是取消java的許可權控制檢查。
//所以即使是public方法,其accessible 屬相預設也是false
field.setAccessible(true);
return field.get(person);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}

執行結果:

image

獲取私有方法的方式類似獲取私有成員變數的方式 Filed類,Method類等詳細檢視開發者文件: developer.android.com/intl/zh-cn/…

六、關於反射的一些高階話題

如果說前面那些屬於Java反射的基本知識,那麼在文章的最後,我們來探討一下反射的一些高階話題。另外,本文對基礎知識的講解僅屬於抓主幹,具體的一些旁支可以自己參看文件。需要提一下的是,Java反射中對陣列做過單獨的優化處理,具體可檢視java.lang.reflect.Array類;還有關於泛型的支援,可檢視java.lang.reflect.ParameterizedType及相關資料。

暫時想到的高階話題有三個,由於對Java反射理解的也不算深入,所以僅僅從思路上進行探討,具體實現上,大家可以參考其他相關資料,做更深入研究。

Android編譯期問題

Android的安全許可權問題我把它簡單的劃分成三個層次,最不嚴格的一層就是僅僅騙過編譯器的“@hide”標記。對於一款開源的作業系統而言,這個標記本身並不具備安全上的限制。不過,從上次Google過來的負責Android工程師的說法來看,這個標記的作用更多的是方便硬體廠商做閉源的二次開發。這樣解釋倒也說得過去。

不過這並不影響我們使用反射機制以繞過原生Android的第一層安全措施。如果你熟悉原始碼的話,會發現這可以應用到很多地方。並且最關鍵的是你並不需要放在原始碼中編譯,而是像普通應用程式的開發過程一樣。

具體使用範圍我不能一一列舉了,例如自定義視窗、安裝程式等等。簡單的說,在Android上使用反射技術,你才會對Android系統有更深的理解和更高的控制權。


軟體的解耦合

我們在架構程式碼的時候,經常提到解耦合、弱耦合。其實,解耦和不僅僅只能在程式碼上做文章。我們可以考慮這樣一種情況:軟體的功能需求不可能一開始就完全確定,有一些功能在軟體開發的後期甚至是軟體已經發布出去之後才想到要加入或者去掉。 按我們慣有的思維,這種情況就得改動原始碼,重新編譯。如果軟體已經發布出去,那麼就得讓客戶重新安裝一次軟體。反思一下,我們是否認為軟體和程式是同一回事呢?事實上,如果你能將軟體和程式分開來理解,那麼你會發現,為了應對以上的情況,我們還有其他的解決辦法。

我國有一個很重要但是很麻煩的制度,那就是戶籍制度。它的本意是為了更好的管理人口事宜。每當一個孩子出生,我們就需要在戶籍管理的地方去給他辦理戶籍入戶;而每當一個人去世,我們也需要在相應的地方銷去他的戶籍。既然我們可以視類為生命,那麼我們能否通過學習這樣的戶籍管理制度來動態地管理類呢?

事實上這樣的管理是可行的,而且Java虛擬機器本身正是基於這樣的機制來執行程式的。因此我們是否可以這樣來架構軟體框架。首先,我們的軟體有一個配置檔案,配置檔案其實是一個文字,裡面詳細描述了,我們的軟體核心部分執行起來後還需要從什麼路徑載入些什麼類需要何時呼叫什麼方法等。這樣當我們需要加或減某些功能時,我們只需要簡單地修改配置文字檔案,然後刪除或者新增相應的.class檔案就可以了。

如果你足夠敏感,你或許會發現,這種方式形成的配置檔案幾乎可以相當於一門指令碼語言了。而且這個指令碼的直譯器也是我們自己寫的,另外關鍵是它是開發的,你可以為它動態地加入一些新的類以增加它的功能。

不要以為這僅僅是一個設想,雖然要開發成一門完備的指令碼語言確實比較麻煩。但是在一些網路端的大型專案中,通過配置檔案 + ClassLoader + 反射機制結合形成的這種軟體解耦和方式已經用得比較普遍了。 所以,在此我不是在提出一種設想,而是在介紹業界處理此類問題的一種解決方案。

反射安全


文章讀到這裡,我想你應該由衷地感嘆,Java反射機制實在是太強大了。但是,如果你有一些安全意識的話,就會發現Java這個機制強大得似乎有些過頭了。前面我們提到,Java反射甚至可以訪問private方法和屬性。為了讓大家對Java反射有更全面的瞭解,樹立正確的人生觀價值觀,本小節將對Java的安全問題做一個概要性的介紹。

相對於C++來說,Java算是比較安全的語言了。這與它們的執行機制有密切的關係,C++執行於本地,也就是說幾乎所有程式的許可權理論上都是相同的。而Java由於是執行於虛擬機器中,而不直接與外部聯絡,所以實際上Java的執行環境是一個“沙盒”環境。 Java的安全機制其實是比較複雜的,至少對於我來說是如此。作為Java的安全模型,它包括了:位元組碼驗證器、類載入器、安全管理器、訪問控制器等一系列的元件。之前文中提到過,我把Android安全許可權劃分為三個等級:第一級是針對編譯期的“@hide”標記;第二級是針對訪問許可權的private等修飾;第三級則是以安全管理器為託管的Permission機制。

Java反射確實可以訪問private的方法和屬性,這是繞過第二級安全機制的方法(之一)。它其實是Java本身為了某種目的而留下的類似於“後門”的東西,或者說是為了方便除錯?不管如何,它的原理其實是關閉訪問安全檢查。

如果你具有獨立鑽研的精神的話,你會發現之前我們提到的Field、Method和Constructor類,它們都有一個共同的父類AccessibleObject 。AccessibleObject 有一個公共方法:void setAccessible(boolean flag)。正是這個方法,讓我們可以改變動態的開啟或者關閉訪問安全檢查,從而訪問到原本是private的方法或域。另外,訪問安全檢查是一件比較耗時的操作,關閉它反射的效能也會有較大提升。

不要認為我們繞過了前兩級安全機制就沾沾自喜了,因為這兩級安全並不是真正為了安全而設定的。它們的作用更多的是為了更好的完善規則。而第三級安全才是真正為了防止惡意攻擊而出現的。在這一級的防護下,你甚至可能都無法完成反射(ReflectPermission),其他的一切自然無從說起。

七、通過反射了解集合泛型的本質

首先下結論:


Java中集合的泛型,是防止錯誤輸入的,只在編譯階段有效,繞過編譯到了執行期就無效了。 下面通過一個例項來驗證:


package com.Soul.reflect;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
/**
* 集合泛型的本質
* @description
* @author Soul
* @date 2016年4月2日上午2:54:11
*/
public class Generic {
public static void main(String[] args) {
List list1 = new ArrayList(); // 沒有泛型
List<String> list2 = new ArrayList<String>(); // 有泛型
/*
* 1.首先觀察正常新增元素方式,在編譯器檢查泛型,
* 這個時候如果list2新增int型別會報錯
*/
list2.add();
// list2.add(20); // 報錯!list2有泛型限制,只能新增String,新增int報錯
System.out.println( + list2.size()); // 此時list2長度為1
/*
* 2.然後通過反射新增元素方式,在執行期動態載入類,首先得到list1和list2
* 的類型別相同,然後再通過方法反射繞過編譯器來呼叫add方法,看能否插入int
* 型的元素
*/
Class c1 = list1.getClass();
Class c2 = list2.getClass();
System.out.println(c1 == c2); // 結果:true,說明類型別完全相同
// 驗證:我們可以通過方法的反射來給list2新增元素,這樣可以繞過編譯檢查
try {
Method m = c2.getMethod(, Object.class); // 通過方法反射得到add方法
m.invoke(list2, 20); // 給list2新增一個int型的,上面顯示在編譯器是會報錯的
System.out.println( + list2.size()); // 結果:2,說明list2長度增加了,並沒有泛型檢查
} catch (Exception e) {
e.printStackTrace();
}
/*
* 綜上可以看出,在編譯器的時候,泛型會限制集合內元素型別保持一致,但是編譯器結束進入
* 執行期以後,泛型就不再起作用了,即使是不同型別的元素也可以插入集合。
*/
}
}

輸出結果

list2的長度是:1 true list2的長度是:2


原文出處:Java反射機制 | IT人
冷日
(冷日)
Webmaster
  • 註冊日: 2008/2/19
  • 來自:
  • 發表數: 15771
[轉貼]JVM - 反射

每個程序員都該瞭解的JVM - 反射

March 04, 2020

介紹

反射 指的是在程序運行期間動態的去操作類物件的原數據(meta-data) 包括類別名稱 方法名稱等等

讀書要會抓重點 上面那句話的重點是 動態 知道重點不稀奇 解釋得好懂才難 待會我就來解釋解釋為什麼需要動態的去操作一個類別

反射的靈魂是類物件 要搞懂反射之前 要先問自己四個問題

How What Why Where

How: 如何得到類物件

使用反射有一個前提 就是你的類物件需要已經被類加載器創建好並且存在heap裡

大前提成立的情況下 有四種方法可以使用得到類物件

1.由物件的引用得到 objectReference.getClass()

直接上代碼


();
// java.lang.String
};
();
// [I

在Java裡面表達一個class的名稱的方式跟存在常量池的方法一樣 [代表陣列 I代表int (請參閱 類文件結構)

2. Class.forName(classname)


{
);
// java.lang.String
{
);
}

注意要丟進forName方法的參數要是類別的完全限定名 第二種方式不能對primitive type使用(int, byte, long…) 但可以對Array of primitive使用 用法如下


{
);
// java.lang.String
);
// [Ljava.lang.String;
);
// [D
);
// [I
{
);
}

3. type.class


;
// int
;
// java.lang.String
;
// [[D
;
//void

天阿 void.class 是什麼鬼東西? 別懷疑 void也是primitive type的一種 當然可以操作類物件 待會再來講用途

4. BP.TYPE第四種方法只有Boxed Primitive可以用 Boxed Primitive就是把一個primitive包裝成物件 比如說Integer類別或是Long類別 對於這種類別直接call TYPE就可以操作類物件

LONG.TYPE = long.class

What: 怎麼使用反射

拿到clazz了 那我們可以怎麼用它呢?


()
()
()
()
()
()

這些只是最基本的 讓我們可以知道一個類別的mata-data 我們還可以直接把類別的方法當作一個變數來呼叫

直上例子 最快了解 先來隨便來一個類別Person


{
;
;
;
{
}
{
;
;
;
}
{
;
}
{
);
}
}

主程式如下


{
{
);
;
;
{
();
{
);
{
);
}
// Person
{
());
()));
{
);
}
}
{
);
}
);
);
());
);
);
());
);
);
}
}

執行Reflection 給定JVM參數 -verbose:class 執行結果如下


Before loading Person class
[Loaded Person from file:/Users/bchiang/Downloads/javaWeakRefernceExample/out/production/main/]
Person
Method name: func1
Method modifier: public
Method name: func2
Method modifier: protected static final
Method's return type is void
Constructor: Person, # of parameters: 0
Constructor: Person, # of parameters: 3
Result of calling func1: true
Result of invoking func1: true
[Loaded java.lang.Long$LongCache from /Library/Java/JavaVirtualMachines/jdk1.8.0_121.jdk/Contents/Home/jre/lib/rt.jar]
abc

範例程式應該是簡單易懂 相信聰明的你能夠讀得懂 但有幾點特別值得提出來說明

1.只有在需要初始化的時候 才會去加載一個類別 在這個程式裡面的

;

就是符合類別主動使用的第六個時機 所以JVM去加載了Person類別 把Person類別存在堆中後 再回傳給clazz

2.newInstance()這個方法

();

可能拋出兩種異常

InstantiationException 指的是不能生出實例 可能是找不到相對應的建構子 newInstance() 沒給任何參數就是呼叫沒參數的建構子 如你所知 在編譯時期你可是無法知道有沒有你要的建構子的 所以你必須處理這種異常

IllegalAccessException 指的就是你沒有權限碰到這個建構子 比如說你把 Person(){}前面加上個private 就會拋出這個異常

3.這個迴圈就是我們跑遍每個方法的方式

()) 

這讓我們可以去看每個方法的回傳值 描述符等等

4.這裡我們用的是第二個建構子

);

5.這裡你還看到了兩種方式來呼叫一個方法


()
);

兩種方式呼叫同一個方法fun1 回傳結果都一樣

至於要呼叫一個帶有參數的方法 用法如下:

);

看懂這段程式 反射就學的差不多了

Why:為什麼我們需要反射

學完了用法 再來一個重要的問題就是為什麼需要這麼做

現在我們有了兩種方式來生一個字串

()

或是


;
()

其實做到的事是一樣的 用反射的好處當然有一些

1.在運行時期判斷一個對象所屬的類別

2.在運行時創建一個對象 這個對象的類別在編譯時期還不需要確定

3.在運行時存取或調用一個對象的所有成員以及方法

但是如果可以不要用反射 就不要用反射 理由如下

1.因為是動態才決定要做哪些事情 加載哪些類別 所以很多虛擬機的最佳化都無法執行

2.會透露很多你原本封裝好的資訊 因為你可以存取所有私有的方法跟變數

順便教你一下怎麼存取 好孩子不要學 我們剛剛的例子如果func1是private 是無法invoke的(當然也不能直接call) 但你只要偷偷加上一行


);
//evil
);
());

就可以呼叫私有了 是不是覺得反射破壞了不少Java引以為傲的特性? 所以反射 能不用就不用

Where: 哪裡我們用到反射

最常見的用途就是IDE的自動完成的提示

Alt text

實作方式就是運行期間去得到這個類別所有的方法 然後把signature列出來 非常實用

第二常見的用途是Annotation 你可以在運行時期去看每一個方法的annotation來決定不同的行為

比如說JUnit test framework 它就使用Reflection去看每個有@Test的方法 並且是以test開頭的方法 並只執行那些符合條件的方法

哪些方法可以用

存取類別方法


()
)
()
)

Declared代表說這個類別自己宣告的方法 反之呢就是所有方法 包含繼承而來的

Class[] params是什麼呢 用法也很有趣 要把Array of class給進getMethod


];
;
;
;
);
());

Output如下

method = public void Person.func2(int,long,java.lang.String)

存取類別字段


()
)
)
()

存取構造器


)
()
()
)

用法在剛剛的範例程式都有

存取類加載器

()

如果是bootstrap加載器 那會回傳null 小心不要直接存取

());

會拋出NullPointerException

反射 vs 類別自識

要特別注意的是 反射跟類別自識(Type introspection)不同 類別自識指的是可以在執行期間查看一個物件是屬於什麼類別 你就可以知道這個物件做得到什麼事 但反射指的是除了知道以外 還可以動態修改

所以reflection比introspection強上太多

為什麼反射叫做反射

反射快到講到尾聲 你們有沒有想過為什麼反射要叫做反射 我來稍微說一下我的想法

一般我們在寫程式的時候 是先有類別 然後藉由創建物件來取得實例化的對象 這是比較為人知的用法 反射就是相反 先得到一個物件之後 從這個物件存取類物件 然後再從類物件進而得知這個類別的訊息

所以用法跟一般的用法相反 一般用法叫入射(incidence) 相反用法叫反射(reflection)

總結

再看一次反射的定義

反射 指的是在程序運行期間動態的去操作類物件的原數據(meta-data) 包括類別名稱 方法名稱等等

經過本篇文章的教學 你知道你可以寫一個函數 這個函數給入一個物件 你可以在運行期間分析這個物件 來決定程式要做什麼

比如說 如果這個傳入的物件的類別有兩個constructor 就 a++

如果這個傳入的物件的類別有兩個static variable 就 b += 2

等等用途 主要是可以讓我們

1.在運行時判斷任意一個對象所屬的類

2.在運行時構造任意一個類的對象(即使這個類別在編譯時期仍未給定)

3.在運行時判斷任意一個類所具有的成員變量和方法(透過反射甚至可以呼教private方法)


4.在運行時調用任意一個對象的方法


原文出處: 每個程序員都該瞭解的JVM - 反射 · jyt0532's Blog
冷日
(冷日)
Webmaster
  • 註冊日: 2008/2/19
  • 來自:
  • 發表數: 15771
[轉貼]Java 反射機制的引入、使用和優缺點

Java反射機制的引入、使用和優缺點

Java反射機制的引入、使用和優缺點

2019-02-15 254
  • 反射的引入

  1. Java編碼時知道類和物件的具體資訊,此時直接對類和物件進行操作即可,無需反射
  2. 如果編碼時不知道類或者物件的具體資訊,此時應該使用反射來實現
比如類的名稱放在XML檔案中,屬性和屬性值放在XML檔案中,需要在執行時讀取XML檔案,動態獲取類的資訊
在編譯時根本無法知道該物件或類可能屬於哪些類,程式只依靠執行時資訊來發現該物件和類的真實資訊
反射的作用:建立物件、操作屬性、呼叫方法

在JDK中,主要由以下四個類來實現Java反射機制,這些類都位於java.lang.reflct包中

Class類:代表一個類,是Java反射機制的起源和入口
用於獲取與類相關的各種資訊
提供了獲取類資訊的相關方法

Class類繼承自Object類

Class類是所有類的共同的圖紙
每個類有自己的物件,同時每個類也看做是一個物件,有共同的圖紙Class,存放類的結構資訊,能夠通過相應方法取出相應的資訊:類的名字、屬性、方法、構造方法、父類和介面

Class類常用方法


獲取類的Class資訊

  • 使用反射技術

動態建立物件
方法1:通過Class的newInstance()方法
該方法要求該Class物件的對應類有無參構造方法
執行newInstance()實際上就是執行無參構造方法來建立該類的例項
Object obj=clazz.newInstance();
//相當於執行語句:
Student stu = new Student();

方法2:通過Constructor的newInstance()方法
先使用Class物件獲取指定的Constructor物件
再呼叫Constructor物件的newInstance()建立Class物件對應類的物件
通過該方法可選擇使用指定構造方法來建立物件
Constructor cons = clazz.getConstructor(String.class,
int.class, float.class );
Object obj = cons.newInstance( "lkl", 32, 56.5f );
//相當於執行語句:
Student stu=new Student("lkl",32,56.5f);
obj = clazz.getConstructor().newInstance();
//相當於執行語句:
Student stu = new Student();

動態修改查詢屬性值

通過Class物件的getFields()或者getField()方法可以獲得該類所包括的全部Field屬性或指定Field屬性。Field類提供了以下方法來訪問屬性


Object obj = clazz.newInstance();
// 呼叫getDeclaredField("name")方法取得name屬性對應的Field物件,可以類獲取私有屬性
Field f = clazz.getDeclaredField("name");
// 取消屬性的訪問許可權控制,即使private屬性也可以進行訪問。
f.setAccessible(true);
// 呼叫get()方法取得對應屬性值。
System.out.println(f.get(obj));
// 呼叫set()方法給對應屬性賦值。
f.set(obj, "lkl");
// 呼叫get()方法取得對應屬性修改後的值。
System.out.println(f.get(obj));

動態執行方法
通過Class物件的getMethods()方法可以獲得該類所包括的全部方法,返回值是Method[]
通過Class物件的getMethod()方法可以獲得該類所包括的指定方法,返回值是Method
每個Method物件對應一個方法,獲得Method物件後,可以呼叫其invoke()來呼叫對應方法
 // 建立該類的一個物件
Class clazz = InvokeMethod.class;
Object obj = clazz.newInstance();
// 呼叫該物件的add方法
Method amethod = clazz.getMethod("add", new Class[] { int.class,int.class });
Object result = amethod.invoke(obj, new Object[] { 5, 7 });
System.out.println(result);
// 呼叫該物件的shout方法
Method smethod = clazz.getMethod("shout", new Class[] { String.class });
smethod.invoke(obj, new Object[] { "lkl" });
//相當於如下語句
InvokeMethod im=new InvokeMethod();
int sum=im.add(5, 7);
System.out.println(sum);
im.shout("lkl");

  • 反射技術優缺點

  • 優點
  1. 反射提高了Java程式的靈活性和擴充套件性,降低耦合性,提高自適應能力。它允許程式建立和控制任何類的物件,無需提高硬編碼目標類
  2. 反射是其他一些常用語言,如C、C++、Fortran或者Pascal等不具備的
  3. Java反射技術應用領域很廣,如軟體測試、JavaBean等
  4. 許多流行的開源框架例如Struts、Hibernate、Spring在實現過程中都採用了該技術
  • 缺點
  1. 效能問題:使用反射基本上是一種解釋操作哦,用於欄位和方法接入時要遠慢於直接程式碼。因此Java反射機制只要應用在對;靈活性和擴充套件性要求很高的系統框架上,普通程式不建議使用
  2. 使用反射會模糊程式內部邏輯:程式設計師希望在程式碼中看到程式的邏輯,反射等繞過了原始碼的技術,因而會帶來維護問題。反射程式碼比相應的直接程式碼更復雜

原文出處:Java反射機制的引入、使用和優缺點 - IT閱讀
前一個主題 | 下一個主題 | 頁首 | | |



Powered by XOOPS 2.0 © 2001-2008 The XOOPS Project|