好程式設計師Java培訓分享Java之反射技術

好程式設計師發表於2020-07-02

好程式設計師 Java 培訓分享Java 反射技術

一、前期概要

1、 什麼是反射

Java 反射機制在程式 執行時 ,對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意一個物件,都能夠呼叫它的任意一個方法和屬性。這種   動態的獲取資訊   以及   動態呼叫物件的方法   的功能稱為   java 的反射機制
反射中的反的理解:在使用的之前,提前不知道需要使用什麼型別的物件。只是在呼叫的時候,才知道要呼叫的物件型別。這種反其道而行的就是反射中反的理解。
程式執行分為編譯器和執行期,編譯時刻載入一個類就稱為靜態載入類,執行時刻載入類稱為動態載入類,
核心思想   讓你在寫程式碼的時候可以更加靈活,降低耦合,提高程式碼的自適應能力。
反射框架提供如下常用的核心功能:
1.在執行時判斷任意物件所屬的類;
2.在執行時構造任意一個類的物件;
3.在執行時判斷任意一個類所具有的成員變數和方法(透過反射甚至可以呼叫private方法);
4.在執行時呼叫任意一個物件的方法;

2、反射的主要用途

通用框架 ,很多框架都是配置化的(比如Spring透過xml配置Bean), 為了保證框架的通用性,可能需要根據不同的配置檔案載入不同的物件或者類,呼叫不同的方法,這個時候就需要反射,執行時動態載入需要載入的物件。

3、缺點

·  效能不佳 - 由於java反射動態解析型別,因此涉及掃描類路徑以查詢要載入的類的處理,從而導致效能降低。

·  安全限制 - Reflection需要執行時許可權,這些許可權可能不適用於在安全管理器下執行的系統。由於安全管理器,這可能導致應用程式在執行時失敗。

·  安全問題 - 使用反射,我們可以訪問我們不應該訪問的部分程式碼,例如,我們可以訪問類的私有欄位並更改它的值。這可能是嚴重的安全威脅,並導致您的應用程式出現異常行為。

·  高維護 - 反射程式碼很難理解和除錯,在編譯時也無法找到程式碼的任何問題,因為這些類可能不可用,使其不太靈活且難以維護。

00001.  Class 物件

00002.  類名

00003.  修飾符

00004.  包資訊

00005.  父類

00006.  實現的介面

00007.  構造器

00008.  方法

00009.  變數

00010.  註解

二、獲得 Class 物件

在執行期間,一個類,只有一個Class物件產生

1、類的靜態方法(常用) :

00001.  說明
獲取指定的類完整的路徑相關聯 介面 Class物件。

00002.  方法
// 掌握
public static Class<?> forName(String className)
// 瞭解
public static Class<?> forName(String className, boolean initialize,ClassLoader loader)

00003.  舉個例子

Class clazz = Class.forName(" ")
// 或者 
Class clazz = Class.forName(" ",initialize,this.getClass().getClassLoader)

1.  說明
任何資料型別(包括基本資料型別)都有一個“靜態”的class屬性

2.  方法
Class<?> cls = 型別.class;

3.  舉個例子
Class<String> cls = String.class;
System.out.println(cls.toString());

1.  說明
透過物件的例項來返回Class物件

2.  方法
Class<?> cls = instance.getClass()

3.  舉個例子
public class User {
}
User user = new User();
Class<? extends User> clz = user.getClass();

1.  方法
// 透過欄位名,返回一個具體的具有public屬性的成員變數(包括父類的)
Field getField(String name) 
// 透過欄位名所有已宣告的所有成員變數(私有的 預設的 共有的),但不能得到其父類的成員變數
Field getDeclaredField(String name)

2.  舉個例子
public class User {
private String name;
public String detail;
}

public static void main(String[] args) {
try {
Class<?> cls = Class.forName("com.wener.reflect.demo1.User");
/**
* 獲取類的指定名稱公開的屬性
*/
Field detail = cls.getField("detail");
System.out.println(detail);
/**
* 獲取類的指定名稱的的屬性(包括私有的屬性)
*/
Field name = cls.getDeclaredField("name");
System.out.println(name);
} catch (ClassNotFoundException | NoSuchFieldException e) {
e.printStackTrace();
}
}


1.  說明
反射非常強大,但是學習了之後,會不知道該如何使用,反而覺得還不如直接呼叫方法來的直接和方便。
但等我們後面接觸到一些框架之後才會有一些感觸

2.  測試類
package com.wener.reflect.demo2; public class ReflectDemo1 { public void say() { System.out.println("ReflectDemo1"); } } public class ReflectDemo2 { public void say() { System.out.println("ReflectDemo2"); } }

3.  配置檔案reflect.properties
class=ReflectDemo2. method=say

4.  測試程式碼
public static void main(String[] args) { //從spring.txt中獲取類名稱和方法名稱 File springConfigFile = new File("/Users/zhangwei/work/IdeaProjects/JavaExample/ReflectExample/src/reflect.properties"); Properties properties = new Properties(); try { properties.load(new FileInputStream(springConfigFile)); String className = (String) properties.get("class"); String methodName = (String) properties.get("method"); //根據類名稱獲取類物件 Class cls = Class.forName(className); //根據方法名稱,獲取方法物件 Method m = cls.getMethod(methodName); //根據構造器,例項化出物件 Object service = cls.newInstance(); //呼叫物件的指定方法 m.invoke(service); } catch (IOException | InvocationTargetException | NoSuchMethodException | IllegalAccessException | ClassNotFoundException | InstantiationException e) { e.printStackTrace(); } }

5.  優點
當需要從呼叫第一個類的方法,切換到呼叫第二類的方法的時候,不需要修改一行程式碼

1、透過配置檔案動態切換
六、綜合案例
返回值 方法說明 String getName()獲取方法的名稱int getModifiers()獲取方法的修飾符 Class<?> getReturnType()   Type getGenericReturnType 返回方法的返回值型別Class<?>[] getParameterTypes() Type[] getGenericParameterTypes()返回方法的引數(列表)Class<?>[] getExceptionTypes() Type[] getGenericExceptionTypes()返回方法的異常資訊 4、其它API

1.  舉個例子
public class User { private String name = "木木"; public String detail = "hello"; private int age; private BigDecimal balance; public void increment() { this.age++; System.out.println(age); } public BigDecimal getBalance() { return balance; } public void setBalance(BigDecimal balance) { this.balance = balance; } private void say(int num) { System.out.println(num + "號技師"); } @Override public String toString() { return "User{" + "name='" + name + '\'' + ", detail='" + detail + '\'' + '}'; } } public class TestReflectUser { public static void main(String[] args) { reflectMethod(); } public static void reflectMethod() { try { // 1.例項化class物件 Class<?> cls = Class.forName("com.wener.reflect.demo1.User"); // 2.例項化User物件 Object o = cls.newInstance(); // 3.獲取set方法 Method method = cls.getMethod("increment"); // 4 執行方法 method.invoke(o); // 有引數無返回值 Method setBalance = cls.getMethod("setBalance", BigDecimal.class); Object methodSet = setBalance.invoke(o, new BigDecimal(100.00)); System.out.println(methodSet); // 有返回值值無引數 Method methodGet = cls.getMethod("getBalance"); Object invoke = methodGet.invoke(o); System.out.println(invoke); // 獲取私有的方法 Method say = cls.getDeclaredMethod("say", int.class); // 執行時取消訪問許可權檢測機制 say.setAccessible(true); // 執行方法 say.invoke(o, 1); } catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) { e.printStackTrace(); } } }

使用引數 args 在 obj 上指派該物件所表示方法的結果 

1.  方法
Object invoke(Object obj, Object... args)

2.  引數說明

·  obj - 從中呼叫底層方法的物件,必須是例項化的物件

·  args - 用於方法呼叫的引數,是個Object陣列,因為引數有可能有多個

 

1.  返回值

3、呼叫方法

1.  方法
// 返回類或介面宣告的所有方法,包括公共、保護、預設(包)訪問和私有方法,但不包括繼承的方法。 public Method[] getDeclaredMethods() throws SecurityException
// 返回某個類的所有公用(public)方法,包括其繼承類的公用方法。 public Method[] getMethods() throws SecurityException

2.  舉個例子
public class TestReflectUser { public static void main(String[] args) { reflectMethod(); } public static void reflectMethod() { try { // 1.例項化class物件 Class<?> cls = Class.forName("com.wener.reflect.demo1.User"); // 2.例項化User物件 Object o = cls.newInstance(); // 獲取所有的共有的方法(包括父類的方法) Method[] methods = cls.getMethods(); for (Method method1 : methods) { System.out.println(method1.getName()); } // 獲取所有的方法(包括私有的,共有的,預設的) Method[] declaredMethods = cls.getDeclaredMethods(); for (Method declaredMethod : declaredMethods) { System.out.println(declaredMethod.getName()); } } catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) { e.printStackTrace(); } } }

2、獲取所有的方法

1.  方法
// 方法返回一個特定的方法,其中第一個引數為方法名稱,後面的引數為方法的引數對應Class的物件 public Method getMethod(String name, Class<?>... parameterTypes)

2.  舉個例子
public class User { private String name = "木木"; public String detail = "hello"; private int age; private BigDecimal balance; public void increment() { this.age++; System.out.println(age); } public BigDecimal getBalance() { return balance; } public void setBalance(BigDecimal balance) { this.balance = balance; } private void say(int num) { System.out.println(num + "號技師"); } @Override public String toString() { return "User{" + "name='" + name + '\'' + ", detail='" + detail + '\'' + '}'; } } public class TestReflectUser { public static void main(String[] args) { reflectMethod(); } public static void reflectMethod() { try { // 1.例項化class物件 Class<?> cls = Class.forName("com.wener.reflect.demo1.User"); // 2.例項化User物件 Object o = cls.newInstance(); // 3.獲取increment方法 Method method = cls.getMethod("increment"); // 有引數無返回值 Method setBalance = cls.getMethod("setBalance", BigDecimal.class); // 有返回值值無引數 Method methodGet = cls.getMethod("getBalance"); // 獲取私有的方法 Method say = cls.getDeclaredMethod("say", int.class); } catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) { e.printStackTrace(); } } }

1、獲取單個方法
五、方法操作

1.  方法
// 將指定物件引數上的此Field物件表示的欄位設定為指定的新值 field.set(Object obj,Object value)

2.  引數說明

·  Object obj: 欄位所在的類的例項物件

·  Object value : 新值

 

1.  注意
// 如果要給私有變數賦值必須取消許可權的訪問控制 field.setAccessible(true);

2.  舉個例子
public static void main(String[] args) { try { // 1 例項化Class物件 Class<?> cls = Class.forName("com.wener.reflect.demo1.User"); /** * 獲取類的指定名稱公有的屬性 */ Field detail = cls.getField("detail"); System.out.println(detail); /** * 獲取類的所有的屬性(包括私有 預設的 公有的) */ Field name = cls.getDeclaredField("name"); // 2.建立物件 Object o = cls.newInstance(); // 3 透過欄位的set方法設定 name.set(o, "嬌嬌"); System.out.println(o.toString()); } catch (ClassNotFoundException | NoSuchFieldException | InstantiationException | IllegalAccessException e) { e.printStackTrace(); } }

4、欄位賦值

1.  方法
// 獲取所有的”公有欄位” Field[] getFields() // 獲取所有欄位(私有、受保護、預設、公有) Field[] getDeclaredFields()

2.  舉個例子
public class TestReflectUserField { public static void main(String[] args) { try { Class<?> cls = Class.forName("com.wener.reflect.demo1.User"); Field[] fields = cls.getFields(); for (Field field : fields) { System.out.println("型別: " + field.getType() + "方法名: " + field.getName()); } Field[] declaredFields = cls.getDeclaredFields(); for (Field declaredField : declaredFields) { System.out.println("型別: " + declaredField.getType() + "方法名: " + declaredField.getName()); } } catch (ClassNotFoundException | NoSuchFieldException | InstantiationException | IllegalAccessException e) { e.printStackTrace(); } } }

3、獲取所有成員欄位

2、獲取單個成員欄位
類的成員變數也是一個物件,它是java.lang.reflect.Field的一個物件,所以我們透過java.lang.reflect.Field裡面封裝的方法來獲取這些資訊並且操作這些屬性
1、說明
四、屬性操作

1.  cls.newInstance()方法返回的是一個泛型T,我們要強轉成自定義類

2.  cls.newInstance()預設返回的是類的無引數構造物件

3.  被反射機制載入的類必須有無引數構造方法,否者執行會丟擲異常

1.4、注意
三、建立例項

1.  常用的是 類的靜態方法 ,

2.  getClass()的話一般在繼承的時候用的比較多一點,比如Android裡的註解框架

3.  .class 靜態語法: 需要匯入類的包,依賴太強,不導包就拋編譯錯誤

4、總結

3、使用類物件的 getClass()

2、使用 .class 靜態語法。

00001.  區別
第一個預設進行初始化操作,
第二個可以指定是否進行初始化操作。當 initialize=false  不進行初始化操作,即不會執行靜態程式碼塊。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69913864/viewspace-2701845/,如需轉載,請註明出處,否則將追究法律責任。

相關文章