反射的基本概念
如果正常的情況下,如果使用一個類,則必須按照如下的步驟操作:
- 使用import 導入類所在的包;(類:java.lang.Class)
- 明確的使用類名稱或借口名稱定義對象;
- 通過關鍵字new進行類對象實例化;(構造方法:java.lang.reflect.Constructor);
- 產生對象可以使用“對象.屬性”進行類中屬性的調用(屬性:java.lang.reflect.Field);
- 通過“對象.方法()”調用類中方法(方法:java.lang.reflect.Method);
反射過程不需要有明確類型的對象,所有的對象使用Object表示
1. 可以直接使用Object與反射機制的混合調用類中的方法。
Object類中的所有方法以及每一個方法使用上的注意事項:
- 對象克隆:protected Object clone() throws CloneNotSupportedException 創建並返回此對象的副本。 “復制”的精確含義可能取決於對象的類。
- 為什么克隆方法返回的是Object? 答:因為克隆方法可能針對所有類對象使用,為了統一參數用Object
- 克隆對象所在的類一定要實現java.lang.Cloneable接口而子類只需要繼續調用Object 的克隆方法就可以成功實現克隆操作;
- 對象輸出:public String toString() 返回對象的字符串表示形式。
- 直接輸出對象時會默認調用toString()方法
- 原因:由於平時我們會直接System.out.println();來直接輸出對象,那么我們打開源碼看一下為什么會默認調用toString()方法
在printStream類中找到print輸出方法 如下
public void print(Object obj) {write(String.valueOf(obj));}
可以看到他在輸出的時候調用了String的valueOf方法,下面打開valueOf方法源碼
public static String valueOf(Object obj) { return (obj == null) ? "null" : obj.toString(); }
從中我們可以看到如果傳入的對象不為null的話就會自動的調用toString()方法然后返回
- 對象比較:public boolean equals(Object obj) 指示一些其他對象是否等於此。
- 有哪些時候會隱式調用此方法??
- 當我們保存Set集合時,會依靠hashCode()和equals()判斷對象是否重復;
- 取得對象的hash碼:public boolean equals(Object obj) 指示一些其他對象是否等於此。
- 可以理解為每一個對象的唯一編碼,比較時會先判斷編碼是否相同,然后再調用equals方法判斷是否相同
- 取得Class類對象:public final Class<?> getClass() 返回此Object的運行時類。
- 通過一個已經實例化好的對象進行對象的反射操作;
- 線程等待:public final void wait() throws InterruptedException 導致當前線程等到另一個線程調用該對象的notify()方法或notifyAll()方法。 換句話說,這個方法的行為就好像簡單地執行調用wait(0) 。
- 執行到此代碼時線程要等待執行,直到執行notify()或者notifyAll()方法來喚醒線程;
- 一個線程喚醒:public final void notify() 喚醒正在等待對象監視器的單個線程。
- 全部線程喚醒:public final void notifyAll() 喚醒正在等待對象監視器的所有線程。
- 垃圾回收前釋放:protected void finalize() throws Throwable 已過時。 定稿機制本質上是有問題的。 定稿可能導致性能問題,死鎖和掛起。
- 當使用gc回收無用的垃圾空間時默認調用;
Class類
class類是整個反射的操作源頭,而類的定義如下:
public final class Class<T> extends Object implements
Serializable, GenericDeclaration, Type, AnnotatedElement
如果想要使用Class類進行操作,那么必須首先產生Class類的實例化對象,而有三種方法可以去的Claas類的實例化對象
- Object類提供了一個返回Class類對象的方法:public final Class<?> getClass();
- 利用“類.class”取得,日后建的最多的就是在Hibernate上;
- 利用Class類的static方法取得,public static Class<?> forName(String className) throws ClassNotFoundException 返回與給定字符串名稱的類或接口相關聯的Class對象。
如果是程序設計人員,使用最多的方法一定是forName()方法,但是如果是使用者會使用“類.class”。工廠設計模式最好利用反射機制來解決耦合問題。
利用反射實例化對象
Class類如果使用了forName()方法之后,就可以使用Class類定義的newInstance()方法默認去調用類之中的無參構造器進行操作
public T newInstance() throws InstantiationException, IllegalAccessException,此泛型使用不到
代碼演示: 在這里是不能接受的了這個返回值的,

這里就是解決上面錯誤的代碼實現: 從中我們就可以看到我們不一定非要使用new實例化對象,只要我們有一個類的完整名稱也可以實例化對象
public class R { public static void main(String[] args) throws Exception { Class<?> cls = Class.forName("test.Student"); //jdk 1.9 開始直接使用的newInstance()方法已經過時,可以使用下面的方式來調用newInstance()方法 //相當於關鍵字new實例化對象。等價於 Object newInstance = new Student();Object newInstance = cls.getDeclaredConstructor().newInstance(); } } class Student{ public Student() { System.out.println("構造方法Student"); } }
執行結果:構造方法Student
但是如果使用反射實例化對象,必須要求類中存在有無參構造方法,因為newInstance()方法只能找到無參。如果沒有無參構造函數如下:
Exception in thread "main" java.lang.NoSuchMethodException:
如果想找到無參構造怎么辦?操作構造方法
操作構造方法:
為了解決NoSuchMethodException錯誤,這個時候這能取得類之中的構造方法,傳遞所需要的參數后才能使用。
在Class類里面定義了可以取得一個類中的構造方法的操作:
- public Constructor<T> getConstructor(Class<?>... parameterTypes) (重點使用)
throws NoSuchMethodException, SecurityException
返回一個Constructor對象,及時取得類中制定參數的構造,該對象反映由此Class對象表示的類的指定公共構造函數。
- public Class<?>[] getDeclaredClasses() throws SecurityException
返回一個Class對象的數組,就是全部構造,反映了所有被聲明為由這個Class對象表示的類的成員的類和接口。
代碼演示:取得String中的全部構造方法。
import java.lang.reflect.Constructor; public class GetStringConstructor { public static void main(String[] args) throws Exception { Class<?> forName = Class.forName("java.lang.String"); Constructor<?>[] constructors = forName.getConstructors();//得到所有構造 for (int i = 0; i < constructors.length; i++) { System.out.println(constructors[i]); } } }
執行結果:

public java.lang.String(byte[]) public java.lang.String(byte[],int,int) public java.lang.String(byte[],java.nio.charset.Charset) public java.lang.String(byte[],java.lang.String) throws java.io.UnsupportedEncodingException public java.lang.String(byte[],int,int,java.nio.charset.Charset) public java.lang.String(java.lang.StringBuilder) public java.lang.String(java.lang.StringBuffer) public java.lang.String(char[],int,int) public java.lang.String(char[]) public java.lang.String(java.lang.String) public java.lang.String() public java.lang.String(byte[],int,int,java.lang.String) throws java.io.UnsupportedEncodingException public java.lang.String(byte[],int) public java.lang.String(byte[],int,int,int) public java.lang.String(int[],int,int
所以如果現在想要進行制定構造方法的調用,就必須將關注點放在Contructor類中。
public T newInstance(Object... initargs) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException 使用由此Constructor對象表示的構造函數,使用指定的初始化參數創建和初始化構造函數的聲明類的新實例。
代碼實現:實例化剛才的Student對象
import java.lang.reflect.Constructor; class Student{ public Student(String str) { System.out.println("構造方法Student: "+str); } } public class GetStringConstructor { public static void main(String[] args) throws Exception { Class<?> forName = Class.forName("test.Student"); Constructor<?> constructors = forName.getConstructor(String.class); //從這可以看到要與想調用的構造方法的參數類型一直才可以 constructors.newInstance("純菜鳥-java-反射"); } }
執行結果:構造方法Student: 純菜鳥-java-反射
正是因為是通過構造方法實例化對象規格不統一。所以在進行簡單java類的時候就明確的規定了必須有無參的構造方法。
調用類中的方法
取得一個類的實例化對象之后,下面主要的任務就是要調用類之中可以使用的操作方法,對於一個類中的方法有兩類
取得父類繼承而來的方法:
取得全部方法:public 方法[] getMethods() throws SecurityException 返回一個包含方法對象的數組
取得指定方法:public 方法 getMethod(String name,Class<?>... parameterTypes) throws NoSuchMethodException, SecurityException 返回一個方法對象
取得本類定義的方法:
取得全部方法:public 方法[] getDeclaredMethods() throws SecurityException返回一個包含方法對象的數組,
取得指定方法:public 方法 getDeclaredMethod(String name, Class<?>... parameterTypes) throws NoSuchMethodException, SecurityException 返回一個方法對象,
代碼示例:取得Student類中的方法
import java.lang.reflect.Method; class Student { private String Student(String name,Integer age) { return name + " " + age; } } public class R { public static void main(String[] args) throws Exception { Class<?> cls = Class.forName("test.Student"); //獲取對象 { Method[] methods = cls.getMethods(); for (int i = 0; i < methods.length; i++) { System.out.println(methods[i]); } } { System.out.println("======================================="); Method[] methods = cls.getDeclaredMethods(); for (int i = 0; i < methods.length; i++) { System.out.println(methods[i]); } } }
執行結果:

public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException public final void java.lang.Object.wait() throws java.lang.InterruptedException public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException public boolean java.lang.Object.equals(java.lang.Object) public java.lang.String java.lang.Object.toString() public native int java.lang.Object.hashCode() public final native java.lang.Class java.lang.Object.getClass() public final native void java.lang.Object.notify() public final native void java.lang.Object.notifyAll() ======================================= private java.lang.String test.Student.Student(java.lang.String,java.lang.Integer)
下面就是利用Method類來獲取指定對象的方法的方法修飾符
- public int getModifiers() 返回此類或接口的Java語言修飾符,以整數編碼。
由於此方法返回的是一個整數類型的值,我們並不好判斷是什么類型的修飾符,例如public 代表1 static代表200,此時方法如果是public static 修飾,那么就返回201,所以這時候我們有一個Modifer類的toString()方法來進行根據返回的數值返回字符類型的修飾符,區別與Object 的toString()方法,
程序中找的不是Public,Static關鍵字,而是關鍵字所代表的數字
import java.lang.reflect.Method; import java.lang.reflect.Modifier; class Student { private String Student(String name,Integer age) { return name + " " + age; } } public class R { public static void main(String[] args) throws Exception { Class<?> cls = Class.forName("test.Student"); //獲取對象 { Method[] methods = cls.getDeclaredMethods(); for (int i = 0; i < methods.length; i++) { System.out.println(methods[i].getModifiers()); System.out.println(Modifier.toString(methods[i].getModifiers())); } } } }
執行結果:
2
private
下面直接說一下Method中的獲取方法的參數類型,返回值,方法名之類的一系列的方法的使用
代碼范例:獲取Student中的方法的詳細信息
package test; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.lang.reflect.Parameter; class Student { private static String rr(String name,Integer age) { return name + " " + age; } public final void tt() throws Exception{ } } public class R { public static void main(String[] args) throws Exception { Class<?> cls = Class.forName("test.Student"); //獲取對象 { //只有在正真調用本類方法的時候才需要創建實例對象 Method[] methods = cls.getDeclaredMethods();//獲取對象的本類方法 for (int i = 0; i < methods.length; i++) { //由於methods[i].getModifiers()返回的是修飾符的數值,可以用Modifier.toString()方法將其轉換成字符類型的修飾符 System.out.print(Modifier.toString(methods[i].getModifiers())); //得到方法的返回值類型,這時候返回的是全類名類似於java.lang.String,getSimpleName()可以的到簡單類名 System.out.print(" "+methods[i].getReturnType().getSimpleName()); //返回方法名 System.out.print(" "+methods[i].getName()); //返回拋出的錯誤類型數組 Class<?>[] exceptionTypes = methods[i].getExceptionTypes(); if(exceptionTypes.length>0) { System.out.print(" throws "); for (int j = 0; j < exceptionTypes.length; j++) { //取得錯誤的簡單類名 System.out.print(exceptionTypes[j].getSimpleName()); if(exceptionTypes.length-1>j) { System.out.print(","); } } } System.out.print("("); //返回此時方法的全部參數類型 Parameter[] parameters = methods[i].getParameters(); for (int j = 0; j < parameters.length; j++) { //每一個參數類型得到類型后再次得到簡單類名,方便看 System.out.print(parameters[j].getType().getSimpleName()+" arg"+j); if(j+1<parameters.length) { System.out.print(","); } } System.out.print(")"); System.out.println("{}"); } } } }
執行結果:
private static String rr(String arg0,Integer arg1){}
public final void tt throws Exception(){}
所謂的隨筆提示功能就是依據上述代碼實現的,在Method中有一個最重要的方法Invoke();
調用:public Object invoke(Object obj,Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException
使用指定的參數調用由此方法對象表示的基礎方法。
代碼范例:反射調用Student中的方法
package test;
import java.lang.reflect.Method; class Student { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } } public class R { public static void main(String[] args) throws Exception { Class<?> cls = Class.forName("test.Student"); //獲取對象 Object object = cls.getDeclaredConstructor().newInstance();//獲取實例對象 Method method = cls.getMethod("setName", String.class);//得到setName對象 Method method2 = cls.getMethod("getName"); method.invoke(object, "純菜鳥");//setName方法賦值 Object invoke2 = method2.invoke(object);//調用getName System.out.println(invoke2); } }
執行結果:純菜鳥
在開發之中,getter 和 setter必須嚴格編寫
調用類中的一個屬性(盡量不用)
關於類中的屬性也可以直接利用反射進行操作,而支持的方法有兩類
取得所有繼承而來的屬性
取得全部屬性:public Field[] getFields() throws SecurityException
返回一個包含Field對象的數組
取得指定屬性:public Field getField(String name) throws NoSuchFieldException, SecurityException 返回一個Field對象
取得本類定義的屬性
取得全部屬性:public Field[] getDeclaredFields() throws SecurityException
返回一個Field對象的數組
取得指定屬性:public Field getDeclaredField(String name) throws NoSuchFieldException, SecurityException返回一個Field對象
代碼示例:取得一個類中的全部屬性、
package test; import java.lang.reflect.Field; interface People{ public static final String age = "男"; } class Person{ private String name; } class Student extends Person implements People{ private String school; } public class R { public static void main(String[] args) throws Exception { Class<?> cls = Class.forName("test.Student"); //獲取對象 { //獲得本類的屬性 System.out.println("================本類屬性=================="); Field[] declaredFields = cls.getDeclaredFields(); for (int i = 0; i < declaredFields.length; i++) { System.out.println(declaredFields[i]); } } { //獲得父類的屬性 System.out.println("================父類屬性=================="); //在這是已經得到父類的對象,所以再調用本類的屬性方法時是在調父類中的屬性 Field[] declaredFields = cls.getSuperclass().getDeclaredFields(); for (int i = 0; i < declaredFields.length; i++) { System.out.println(declaredFields[i]); } } { //獲得父類的屬性 System.out.println("================接口屬性=================="); Field[] declaredFields = cls.getFields(); for (int i = 0; i < declaredFields.length; i++) { System.out.println(declaredFields[i]); } } } }
執行結果:
================本類屬性==================
private java.lang.String test.Student.school
================父類屬性==================
private java.lang.String test.Person.name
================接口屬性==================
public static final java.lang.String test.People.age
在Field類里面還定義有屬性調用的方法:
設置屬性內容:public void set(Object obj,Object value) throws、
IllegalArgumentException,IllegalAccessException
將指定的對象參數上由此Field對象表示的字段設置為指定的新值
取得屬性內容:public Object get(Object obj)throws
IllegalArgumentException,IllegalAccessException
返回指定對象上由此Field表示的字段的值
代碼范例:設置Student的值並且輸出次值:
import java.lang.reflect.Field; class Student { private String school; } public class R { public static void main(String[] args) throws Exception { Class<?> cls = Class.forName("test.Student"); //獲取對象 Object student = cls.getDeclaredConstructor().newInstance(); Field school = cls.getDeclaredField("school"); school.set(student, "河北軟件"); Object object = school.get(student); System.out.println(object); } }
執行結果
Exception in thread "main" java.lang.IllegalAccessException: class test.R cannot access a member of class test.Student with modifiers "private"
at java.base/jdk.internal.reflect.Reflection.newIllegalAccessException(Unknown Source)
at java.base/java.lang.reflect.AccessibleObject.checkAccess(Unknown Source)
at java.base/java.lang.reflect.Field.checkAccess(Unknown Source)
at java.base/java.lang.reflect.Field.set(Unknown Source)
at test.R.main(R.java:13)
看錯誤原因是因為這個參數的private 的屬性封裝引起的錯誤,
我們來看AccessibleObject這個類中的子類信息如下,Field是他的直接子類,我們再點開Executable就能看 到,他的子類是Constructor和Method(可能是因為漢化原因導致的單詞漢化了),所以我們就知道了這三個類Method,Constructor,Field的父類中是AccessibleObject,正好這個父類中有一個取消封裝操作的方法:


這就是父類中那個方法:public void setAccessible(boolean flag)
將此反射對象的accessible標志設置為指示的布爾值。 值為true表示反射對象應該在使用 Java語言訪問控制時抑制檢查,也就是當設置為true時,屬性就會取消封裝了
代碼更正:在上個代碼示例中Field school = cls.getDeclaredField("school");后加入取消封裝操作:school.setAccessible(true);代碼就可以正常運行了。
執行結果:河北軟件
在開發中,只需要靈活使用Constructor,Method,Field以及源Class就可以使用反射進行一系列的代碼實現了