java反射模式_Java反射机制详解
對于一般的開發者,很少需要直接使用Java反射機制來完成功能開發,但是反射是很多框架譬如 Spring, Mybatis 實現的核心,反射雖小,能量卻很大。
本文主要介紹反射相關的概念以及API的使用,關于反射的應用將在下一篇文章中介紹
反射的介紹
反射(Reflection) 是 Java 在運行時(Run time)可以訪問、檢測和修改它本身狀態或行為的一種能力,它允許運行中的 Java 程序獲取自身的信息,并且可以操作類或對象的內部屬性。
Class 類介紹:Java虛擬機為每個類型管理一個Class對象,包含了與類有關的信息,當通過 javac 編譯Java類文件時,生成的同名 .class 文件保存著該類的 Class 對象,JVM 加載一個類即是加載該 .class 文件。
Class 和 java.lang.reflect 一起對反射提供了支持,java.lang.reflect 包中最常用的幾個類的關系如下:
其中最主要的三個類 Field、Method 和 Constructor 分別用于描述類的域、方法和構造器,它們有一個共同的父類 AccessibleObject,它提供了訪問控制檢查的功能。
Field :描述類的域(屬性),可以使用 get() 和 set() 方法讀取和修改 Field 對象關聯的字段;
Method :描述類的方法,可以使用 invoke() 方法調用與 Method 對象關聯的方法;
Constructor :描述類的構造器,可以用 Constructor 創建新的對象。
下面將通過幾個程序來學習Java反射機制。
準備兩個類用于實驗
我們特別定義兩個類,Person和Employee,其中Employee繼承自Person,且各自都有一個private,protected,public修飾的域(屬性),Employee還有private,public修飾的方法
public class Person {
public String name; // 姓名 公有
protected String age; // 年齡 保護
private String hobby; // 愛好 私有
public Person(String name, String age, String hobby) {
this.name = name;
this.age = age;
this.hobby = hobby;
}
public String getHobby() {
return hobby;
}
}
public class Employee extends Person {
public static Integer totalNum = 0; // 員工數
public int empNo; // 員工編號 公有
protected String position; // 職位 保護
private int salary; // 工資 私有
public void sayHello() {
System.out.println(String.format("Hello, 我是 %s, 今年 %s 歲, 愛好是%s, 我目前的工作是%s, 月入%s元\n", name, age, getHobby(), position, salary));
}
private void work() {
System.out.println(String.format("My name is %s, 工作中勿擾.", name));
}
public Employee(String name, String age, String hobby, int empNo, String position, int salary) {
super(name, age, hobby);
this.empNo = empNo;
this.position = position;
this.salary = salary;
Employee.totalNum++;
}
}
獲取 Class 對象
獲取 Class 對象的方式有三種:使用 Class 類的 forName 靜態方法;直接獲取某一個對象的 class;調用某個對象的 getClass() 方法
public class ClassTest {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
Class c1 = Class.forName("reflect.Employee"); // 第1種,forName 方式獲取Class對象
Class c2 = Employee.class; // 第2種,直接通過類獲取Class對象
Employee employee = new Employee("小明", "18", "寫代碼", 1, "Java攻城獅", 100000);
Class c3 = employee.getClass(); // 第3種,通過調用對象的getClass()方法獲取Class對象
if (c1 == c2 && c1 == c3) { // 可以通過 == 比較Class對象是否為同一個對象
System.out.println("c1、c2、c3 為同一個對象");
System.out.println(c1); // class reflect.Employee
}
}
}
通過反射來創建實例
通過反射來生成對象主要有兩種方式
使用Class對象的newInstance()方法來創建Class對象對應類的實例
先通過Class對象獲取指定的Constructor對象,再調用Constructor對象的newInstance()方法來創建實例
public class NewInstanceTest {
public static void main(String[] args) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
Class c = Date.class;
Date date1 = (Date) c.newInstance(); // 第1種方式:使用Class對象的newInstance()方法來創建Class對象對應類的實例
System.out.println(date1); // Wed Dec 19 22:57:16 CST 2018
long timestamp =date1.getTime();
Constructor constructor = c.getConstructor(long.class);
Date date2 = (Date)constructor.newInstance(timestamp); // 第2種方式:先通過Class對象獲取指定的Constructor對象,再調用Constructor對象的newInstance()方法來創建實例
System.out.println(date2); // Wed Dec 19 22:57:16 CST 2018
}
}
獲取類的全部信息
上面我們定義了兩個類,現在有個需求:獲取Employee的類名,構造器簽名,所有的方法,所有的域(屬性)和值,然后打印出來。該通過什么方式來實現呢?
沒錯,猜對了,就是通過反射來獲取這些類的信息,在上面介紹中我們知道JVM虛擬機為每個類型管理一個Class對象,
為了完成我們的需求,我們需要知道一些API如下:
獲取類信息的部分API
String getName() 獲取這個Class的類名
Constructor[] getDeclaredConstructors() 返回這個類的所有構造器的對象數組,包含保護和私有的構造器;相近的方法 getConstructors() 則返回這個類的所有公有構造器的對象數組,不包含保護和私有的構造器
Method[] getDeclaredMethods() 返回這個類或接口的所有方法,包括保護和私有的方法,不包括超類的方法;相近的方法 getMethods() 則返回這個類及其超類的公有方法的對象數組,不含保護和私有的方法
Field[] getDeclaredFields() 返回這個類的所有域的對象數組,包括保護域和私有域,不包括超類的域;還有一個相近的API getFields(),返回這個類及其超類的公有域的對象數組,不含保護域和私有域
int getModifiers() 返回一個用于描述Field、Method和Constructor的修飾符的整形數值,該數值代表的含義可通過Modifier這個類分析
Modifier 類 它提供了有關Field、Method和Constructor等的訪問修飾符的信息,主要的方法有:toString(int modifiers)返回整形數值modifiers代表的修飾符的字符串;isAbstract是否被abstract修飾;isVolatile是否被volatile修飾;isPrivate是否為private;isProtected是否為protected;isPublic是否為public;isStatic是否為static修飾;等等,見名知義
打印類信息程序
public class ReflectionTest {
public static void main(String[] args) throws ClassNotFoundException {
String name;
if (args.length > 0) {
name = args[0];
} else {
Scanner in = new Scanner(System.in);
System.out.println("輸入一個類名(e.g. java.util.Date):"); // reflect.Employee
name = in.next();
}
try {
Class cl = Class.forName(name);
Class superCl = cl.getSuperclass();
String modifiers = Modifier.toString(cl.getModifiers());
if (modifiers.length() > 0) {
System.out.print(modifiers + " ");
}
System.out.print("class " + name);
if (superCl != null && superCl != Object.class) {
System.out.print(" extends " + superCl.getName());
}
System.out.println("\n{");
printConstructors(cl); // 打印構造方法
System.out.println();
printMethods(cl); // 打印方法
System.out.println();
printFields(cl); // 打印屬性
System.out.println("}");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
System.exit(0);
}
/**
* 打印Class對象的所有構造方法
*/
public static void printConstructors(Class cl) {
Constructor[] constructors = cl.getDeclaredConstructors();
for (Constructor c : constructors) {
String name = c.getName();
System.out.print(" ");
String modifiers = Modifier.toString(c.getModifiers());
if (modifiers.length() > 0) {
System.out.print(modifiers + " ");
}
System.out.print(name + "(");
// 打印構造參數
Class[] paramTypes = c.getParameterTypes();
for (int i = 0; i < paramTypes.length; i++) {
if (i > 0) {
System.out.print(", ");
}
System.out.print(paramTypes[i].getName());
}
System.out.println(");");
}
}
/**
* 打印Class的所有方法
*/
public static void printMethods(Class cl) {
Method[] methods = cl.getDeclaredMethods();
//Method[] methods = cl.getMethods();
for (Method m : methods) {
Class retType = m.getReturnType(); // 返回類型
System.out.print(" ");
String modifiers = Modifier.toString(m.getModifiers());
if (modifiers.length() > 0) {
System.out.print(modifiers + " ");
}
System.out.print(retType.getName() + " " + m.getName() + "(");
Class[] paramTypes = m.getParameterTypes();
for (int i = 0; i < paramTypes.length; i++) {
if (i > 0) {
System.out.print(", ");
}
System.out.print(paramTypes[i].getName());
}
System.out.println(");");
}
}
/**
* 打印Class的所有屬性
*/
public static void printFields(Class cl) {
Field[] fields = cl.getDeclaredFields();
for (Field f: fields) {
Class type = f.getType();
System.out.print(" ");
String modifiers = Modifier.toString(f.getModifiers());
if (modifiers.length()> 0) {
System.out.print(modifiers + " ");
}
System.out.println(type.getName() + " " + f.getName() + ";");
}
}
}
運行程序,然后在控制臺輸入一個我們想分析的類的全名,譬如 reflect.Employee,可得到下面的輸出
輸入一個類名(e.g. java.util.Date):
reflect.Employee
public class reflect.Employee extends reflect.Person
{
private reflect.Employee(java.lang.String, java.lang.String, java.lang.String);
public reflect.Employee(java.lang.String, java.lang.String, java.lang.String, int, java.lang.String, int);
public static void main([Ljava.lang.String;);
public void sayHello();
private void work();
public static java.lang.Integer totalNum;
public int empNo;
protected java.lang.String position;
private int salary;
}
上面的輸出中我們得到的類的構造器,所有方法和所有的域(屬性),包括修飾符,名稱和參數類型都是準確的,看來反射機制能完成我們的需求。
小結一下,我們通過 getDeclaredConstructors() 獲取構造器信息,通過 getDeclaredMethods() 獲得方法信息,通過 getDeclaredFields() 獲得域信息,再通過 getModifiers() 和 Modifier類 獲得修飾符信息,匯總起來就得到了整個類的類信息。
運行時查看對象數據域的實際內容
上面我們已經獲取到了類的信息,現在又有一個需求:在運行時查看對象的數據域的實際值。這個場景就像我們通過IDEA調試程序,設置斷點攔截到程序后,查看某個對象的屬性的值。
我們知道java反射機制提供了查看類信息的API,那么它應該也提供了查看Field域實際值和設置Field域實際值的API,沒錯,猜對了,確實有相關的API,但是有個疑問,有一些屬性是private修飾的私有域,這種是否也能直接查看和設置呢?看完下面的API即可知道答案
運行時查看對象數據域實際內容的相關API
Class> getComponentType() 返回數組類里組件類型的 Class,如果不是數組類則返回null
boolean isArray() 返回這個類是否為數組,同類型的API還有 isAnnotation、isAsciiDigit、isEnum、isInstance、isInterface、isLocalClass、isPrimitive 等
int Array.getLength(obj) 返回數組對象obj的長度
Object Array.get(obj, i) 獲取數組對象下標為i的元素
boolean isPrimitive() 返回這個類是否為8種基本類型之一,即是否為boolean, byte, char, short, int, long, float, 和double 等原始類型
Field getField(String name) 獲取指定名稱的域對象
AccessibleObject.setAccessible(fields, true) 當訪問 Field、Method 和 Constructor 的時候Java會執行訪問檢查,如果訪問者沒有權限將拋出SecurityException,譬如訪問者是無法訪問private修飾的域的。通過設置 setAccessible(true) 可以取消Java的執行訪問檢查,這樣訪問者就獲得了指定 Field、Method 或 Constructor 訪問權限
Class> Field.getType() 返回一個Class 對象,它標識了此 Field 對象所表示字段的聲明類型
Object Field.get(Object obj) 獲取obj對象上當前域對象表示的屬性的實際值,獲取到的是一個Object對象,實際使用中還需要轉換成實際的類型,或者可以通過 getByte()、getChar、getInt() 等直接獲取具體類型的值
void Field.set(Object obj, Object value) 設置obj對象上當前域表示的屬性的實際值
查看對象數據域實際內容程序
了解完上述相關API之后,我們敲出下面的程序來驗證
public class ObjectAnalyzer {
private ArrayList visited = new ArrayList<>();
public String toString(Object obj) {
if (obj == null) {
return "null";
}
if (visited.contains(obj)) { // 如果該對象已經處理過,則不再處理
return "...";
}
visited.add(obj);
Class cl = obj.getClass(); // 獲取Class對象
if (cl == String.class) { // 如果是String類型則直接轉為String
return (String) obj;
}
if (cl.isArray()) { // 如果是數組
String r = cl.getComponentType() + "[]{\n"; // 數組的元素的類型
for (int i = 0; i < Array.getLength(obj); i++) {
if (i > 0) { // 不是數組的第一個元素加逗號和換行,顯示更加美觀
r += ",\n";
}
r += "\t";
Object val = Array.get(obj, i);
if (cl.getComponentType().isPrimitive()) { // Class為8種基本類型的時候為 true,直接輸出
r += val;
} else {
r += toString(val); // 不是8中基本類型時,說明是類,遞歸調用toString
}
}
return r + "\n}";
}
// 既不是String,也不是數組時,輸出該對象的類型和屬性值
String r = cl.getName();
do {
r += "[";
Field[] fields = cl.getDeclaredFields(); // 獲取該類自己定義的所有域,包括私有的,不包括父類的
AccessibleObject.setAccessible(fields, true); // 訪問私有的屬性,需要打開這個設置,否則會報非法訪問異常
for (Field f : fields) {
if (!Modifier.isStatic(f.getModifiers())) { // 通過 Modifier 可獲取該域的修飾符,這里判斷是否為 static
if (!r.endsWith("[")) {
r += ",";
}
r += f.getName() + "="; // 域名稱
try {
Class t = f.getType(); // 域(屬性)的類型
Object val = f.get(obj); // 獲取obj對象上該域的實際值
if (t.isPrimitive()) { // 如果類型為8種基本類型,則直接輸出
r += val;
} else {
r += toString(val); // 不是8種基本類型,遞歸調用toString
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
r += "]";
cl = cl.getSuperclass(); // 繼續打印超類的類信息
} while (cl != null);
return r;
}
}
測試驗證結果
接下來驗證一下獲取數據域實際值是否正確,分別打印數組、自定義類的對象的實際值
public class ObjectAnalyzerTest {
public static void main(String[] args) {
int size = 4;
ArrayList squares = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
squares.add(i * i);
}
ObjectAnalyzer objectAnalyzer = new ObjectAnalyzer(); // 創建一個上面定義的分析類ObjectAnalyzer的對象
System.out.println(objectAnalyzer.toString(squares)); // 分析ArrayList對象的實際值
Employee employee = new Employee("小明", "18", "愛好寫代碼", 1, "Java攻城獅", 100); // 分析自定義類Employee的對象的實際值
System.out.println(objectAnalyzer.toString(employee));
}
}
輸出如下
java.util.ArrayList[elementData=class java.lang.Object[]{
java.lang.Integer[value=0][][],
java.lang.Integer[value=1][][],
java.lang.Integer[value=4][][],
java.lang.Integer[value=9][][]
},size=4][modCount=4][][]
reflect.Employee[empNo=1,position=Java攻城獅,salary=100][name=小明,age=18,hobby=愛好寫代碼][]
其中ArrayList打印了類名和5個元素的類型和值,Employee 打印了類名,自己定義的3個基本類型的屬性的實際值,和父類Person的3個基本類型的屬性的實際值
需要注意的是,position,age 是 protected 保護域,salary,hobby 是 private 私有域,Java的安全機制只允許查看任意對象有哪些域,但是不允許讀取它們的值
程序中是通過 AccessibleObject.setAccessible(fields, true) 將域設置為了可訪問,取消了Java的執行訪問檢查,因此可以訪問,如果不加會報異常 IllegalAccessException
小結一下,我們通過 setAccessible(true) 繞過了Java執行訪問檢查,因此能夠訪問私有域,通過 Field.getType() 獲得了屬性的聲明類型,通過了 Field.get(Object obj) 獲得了該域屬性的實際值,還有一個沒用上的 Field.set(Object obj, Object value) 設置域屬性的實際值
調用任意方法
上面我們已經獲取了類的構造器,方法,域,查看和設置了域的實際值,那么是不是還可以在調用對象的方法呢?嘿嘿,又猜對了,機智,類的方法信息,獲取都獲取了,當然就要調用一下,來都來了
上面查看Field的實際值是通過 Field 類的 get() 方法,與之類似,Method 調用方法是通過 Method 類的 invoke 方法
調用任意方法相關的API
Method getMethod(String name, Class>... parameterTypes) 獲取指定的 Method,參數 name 為要獲取的方法名,parameterTypes 為指定方法的參數的 Class,由于可能存在多個同名的重載方法,所以只有提供正確的 parameterTypes 才能準確的獲取到指定的 Method
Object invoke(Object obj, Object... args) 執行方法,第一個參數執行該方法的對象,如果是static修飾的類方法,則傳null即可;后面是傳給該方法執行的具體的參數值
調用任意方法程序
public class MethodTableTest {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Employee employee = new Employee("小明", "18", "寫代碼", 1, "Java攻城獅", 100000);
Method sayHello = employee.getClass().getMethod("sayHello");
System.out.println(sayHello); // 打印 sayHello 的方法信息
sayHello.invoke(employee); // 讓 employee 執行 sayHello 方法
double x = 3.0;
Method square = MethodTableTest.class.getMethod("square", double.class); // 獲取 MethodTableTest 的square方法
double y1 = (double) square.invoke(null, x); // 調用類方法 square 求平方,方法參數 x
System.out.printf("square %-10.4f -> %10.4f%n", x, y1);
Method sqrt = Math.class.getMethod("sqrt", double.class); // 獲取 Math 的 sqrt 方法
double y2 = (double) sqrt.invoke(null, x); // 調用類方法 sqrt 求根,方法參數 x
System.out.printf("sqrt %-10.4f -> %10.4f%n", x, y2);
}
// static靜態方法 計算乘方
public static double square(double x) {
return x * x;
}
}
執行結果
public void reflect.Employee.sayHello()
Hello, 我是 小明, 今年 18 歲, 愛好是寫代碼, 我目前的工作是Java攻城獅, 月入100000元
square 3.0000 -> 9.0000
sqrt 3.0000 -> 1.7321
相信大家都看懂啦,通過 getMethod() 獲取指定的 Method,再調用 Method.invoke() 執行該方法
反射的優缺點
此段引用自 CyC2018/CS-Notes
反射的優點:
可擴展性 :應用程序可以利用全限定名創建可擴展對象的實例,來使用來自外部的用戶自定義類。
類瀏覽器和可視化開發環境 :一個類瀏覽器需要可以枚舉類的成員。可視化開發環境(如 IDE)可以從利用反射中可用的類型信息中受益,以幫助程序員編寫正確的代碼。
調試器和測試工具 : 調試器需要能夠檢查一個類里的私有成員。測試工具可以利用反射來自動地調用類里定義的可被發現的 API 定義,以確保一組測試中有較高的代碼覆蓋率。
反射的缺點:
盡管反射非常強大,但也不能濫用。如果一個功能可以不用反射完成,那么最好就不用。在我們使用反射技術時,下面幾條內容應該牢記于心。
性能開銷 :反射涉及了動態類型的解析,所以 JVM 無法對這些代碼進行優化。因此,反射操作的效率要比那些非反射操作低得多。我們應該避免在經常被執行的代碼或對性能要求很高的程序中使用反射。
安全限制 :使用反射技術要求程序必須在一個沒有安全限制的環境中運行。如果一個程序必須在有安全限制的環境中運行,如 Applet,那么這就是個問題了。
內部暴露 :由于反射允許代碼執行一些在正常情況下不被允許的操作(比如訪問私有的屬性和方法),所以使用反射可能會導致意料之外的副作用,這可能導致代碼功能失調并破壞可移植性。反射代碼破壞了抽象性,因此當平臺發生改變的時候,代碼的行為就有可能也隨著變化。
后記
歡迎評論、轉發、分享,您的支持是我最大的動力
關注【小旋鋒】微信公眾號,及時接收博文推送
總結
以上是生活随笔為你收集整理的java反射模式_Java反射机制详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python uwsgi_Python
- 下一篇: 在JAVA语言程序中main_在Java