java反射機制認知
java反射機制是在運行狀態中,對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意一個對象,都能夠調用它的任意一個方法和屬性;這種動態獲取類的信息以及動態調用對象的方法的功能稱為java語言的反射機制Reflection。
這就說明:Java程序可以加載一個編譯期間完全未知的class,獲悉其完整構造,並生成其對象實體、或對其fields設值、或喚起其methods。雖然java並不是動態語言。
如何達到上述目的,是本文探討的內容。本文將介紹Reflection APIs,java.lang.Class,以及java.lang.reflect中的Method、Field、Constructor等類。
java.lang.Class是反射入口。java中一切皆對象,繼承自object。每一個對象都有自己的類型,通過getClass()得到一個java.lang.Class對象,(基本類型通過字面值.class獲得,也叫類標記,如:int.class)這個Class對象中包含了與類型有關的信息,包括其字段、方法、父類或接口等。
類的裝載
在正式開始學習反射之前,我們來了解一些類的裝載的知識。
類裝載器把一個類裝入JVM 中,要經過以下步驟:
1.裝載:查找和導入Class 文件;
通過一個類的全限定名來獲取定義此類的二進制字節流.然后將這個字節流所代表的靜態存儲結構轉化為方法區的運行時數據結構.最后在Java堆中生成一個代表這個類的java.lang.class對像,作為方法區的數據入口.
2.鏈接:執行校驗、准備和解析步驟,其中解析步驟是可以選擇的:
a)校驗:檢查載入Class 文件數據的正確性;
b)准備:給類的靜態變量分配存儲空間;
c)解析:將符號引用轉成直接引用;
3.初始化:對類的靜態變量、靜態代碼塊執行初始化工作。
網上找到一個例子,可以看到一個類在什么時候被初始化:
package study.javacore.test; import java.util.Random; import klg.utils.MyTestUtil; class Initable { static final int staticFinal = 47; static final int staticFinal2 = InitClassTest.rand.nextInt(100); static { System.out.println("Initialization Initable"); } } class Initable2 { static int staticNoFinal = 147; static { System.out.println("Initialization Initable2"); } } class Initable3 { static int staticNoFinal = 74; static { System.out.println("Initialization Initable3"); } } public class InitClassTest { public static Random rand = new Random(47); public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException { Class clazz = Initable.class; // 不會引起初始化 MyTestUtil.print(clazz.getDeclaredFields());//讀取字段信息不會初始化,獲取方法信息也一樣 //當然如果取得字段的值就已經是初化了,調用方法也一樣。因為這些操作都需要一個對象。 //Initable initable=(Initable) clazz.newInstance(); //這個必然會導致Initable初始化 System.out.println("after creating Initable reference"); System.out.println(Initable.staticFinal); // 引用編譯器常量不會引起初始化 System.out.println(Initable.staticFinal2); // 引起初始化 System.out.println(Initable2.staticNoFinal); // 引用非編譯期常量會引起初始化 Class initable3 = Class.forName("study.javacore.test.Initable3"); // 默認會引起初始化 System.out.println("after creating Initable3 reference"); System.out.println(Initable3.staticNoFinal);// 前面已經初始化此處不用再初始化 } }
Class對象
Class 類的實例表示正在運行的 Java 應用程序中的類和接口。枚舉是一種類,注釋是一種接口。每個數組屬於被映射為 Class 對象的一個類,所有具有相同元素類型和維數的數組都共享該 Class 對象。基本的 Java 類型(boolean、byte、char、short、int、long、float 和 double)和關鍵字 void 也表示為 Class 對象。
Class 沒有公共構造方法。Class 對象是在加載類時由 Java 虛擬機以及通過調用類加載器中的 defineClass 方法自動構造的。
對於我們編寫的每個類,它們都有一個Class 對象。(更恰當地說,是保存在一個完全同名的.class 文件中)。在運行期,一旦我們想生成那個類的一個對象,用於執行程序的Java 虛擬機(JVM)首先就會檢查那個類型的Class 對象是否已經載入。若尚未載入,JVM 就會查找同名的.class 文件,並將其載入。所以Java 程序啟動時並不是完全載入的,這一點與許多傳統語言都不同。一旦那個類型的Class 對象進入內存,就用它創建那一類型的所有對象。
取得Class對象的句柄
取得Class對象的句柄有三種方式:
- 未知類名:Class.forName(className);
- 已知對象名:new Person().getClass();
- 已知類名:Person.class;
1.Class.forName(className)
實際上是調 Class.forName(className, true, currentLoader)。注意第二個參數,是指Class被loading后必須被初始化。
2.實例對象.getClass()。
說明:對類進行靜態初始化、非靜態初始化;返回引用object運行時真正(子類對象的句柄可能會賦給父類對象的句柄<多態>)所屬的類的Class的對象。
Person person=new Person(); Object op=person; System.out.println(op.getClass());//class study.javacore.test.Person
這里有一個特例,基本類型在向上類型轉換以后,getClass()得到的是基本類型對應的包裝類型。
int i=2; Object oi=i; System.out.println(oi.getClass());//class java.lang.Integer
3.類名.class,又叫類標記。
說明: JVM將使用類裝載器, 將類裝入內存(前提是:類還沒有裝入內存),不做類的初始化工作。
這樣做不僅更加簡單,而且更安全,因為它會在編譯期間得到檢查。由於它取消了對方法調用的需要,所以執行的效率也會更高。類標記不僅可以應用於普通類,也可以應用於接口、數組以及基本數據類型。除此以外,針對每種基本數據類型的封裝器類,它還存在一個名為TYPE 的標准字段:例如,int.class==Integer.TYPE
之所以說是句柄,是因為這三種方式得到Class對象是同一個:
System.out.println(Class.forName("java.lang.Object")==Object.class);//true System.out.println(Object.class==new Object().getClass()); //true
認識反射的api
我們主要從4個方面認識反射的api
- 獲取類的基本信息java.lang.Class
- 獲取類的實例java.lang.Class和java.lang.reflect.Constructor<T>
- 操作實例的屬性java.lang.reflect.Field
- 調用實例的方法java.lang.reflect.Method
-
要記住一切都是由Class對象開始,java.lang.Class是反射入口。
獲取類的基本信息
我們可以通過一個類的Class對象了解該類的基本信息。
Java class 內部模塊
Java class 內部模塊說明
相應之Reflection API,多半為Class methods。
返回值類型(return type)
package
class隸屬哪個package
getPackage()
Package
field
class的屬性
按訪問權限分為:
所有、可訪問
getDeclaredFields() ;
getDeclaredField(String name) ;
Field[]
Field
method
class的方法
按訪問權限分為:
所有、可訪問
getMethods() ;
getMethod(String name, Class<?>...parameterTypes);
Method[]
Method
modifier
class(或methods, fields)的屬性
int getModifiers()
Modifier.toString(int)
Modifier.isInterface(int)
int
String
bool
class name or interface name
class/interface
名稱getName()
String
type parameters
參數化類型的名稱
getTypeParameters()
TypeVariable
<Class>[]
base class
base class(只可能一個)
getSuperClass()
Class或null
implemented interfaces
實現有哪些interfaces
getInterfaces()
Class[]
inner classes
內部classes
getDeclaredClasses()
Class[]
outer class
如果我們觀察的class 本身是inner classes,那么相對它就會有個outer class。
getDeclaringClass()
Class
下面我們來實踐一下:
Person.java
class Person { private int age; private String name; public Person() { super(); } public Person(int age, String name) { this.age = age; this.name = name; } public void say(){ System.out.println("Person say!"); } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "Person [age=" + age + ", name=" + name + "]"; } }
測試:注:MyTestUtil是我用反射寫的用於打印對象的屬性值工具類。
public static void main(String[] args) { Class demo1 = null; try { demo1 = Class.forName("study.javacore.test.Person"); } catch (ClassNotFoundException e1) { e1.printStackTrace(); } Class demo = Person.class; System.out.println(demo1 == demo);// true System.out.println(demo.getPackage());// 包名 System.out.println(demo.getModifiers());// 訪問權限修飾符,0就是沒有 MyTestUtil.print(demo.getConstructors());// 構成方法 MyTestUtil.print(demo.getDeclaredFields());// 字段信息 MyTestUtil.print(demo.getMethods());// 方法信息 }
獲取類的實例
獲取累的實例有兩種方法:
-
通過Class對象的newInstance方法,創建此 Class 對象所表示的類的一個新實例。
try { // 調用的是無參的構成方法,如果沒有無參數的構造方法報錯。 Person person =(Person) demo.newInstance(); person.setAge(10); person.setName("klguang"); MyTestUtil.printWithSign("person", person); } catch (Exception e) { System.out.println("Class newInstance wrong!!!"); e.printStackTrace(); }
-
通過Constructor對象,獲取類的實例。
try { Class[] types = new Class[] { int.class, String.class }; Constructor<Person> constructor = demo.getConstructor(types); Person person2 = constructor.newInstance(20, "klguang"); MyTestUtil.printWithSign("person2", person2); } catch (Exception e) { System.out.println("::constructor wrong!!!"); e.printStackTrace(); }
操作實例的屬性
可通過Class對象的getDeclaredFields() 或getDeclaredField(String name) 方法取得字段Field對象,然后調用field.setAccessible(true),允許訪問字段,最后用field.set(Object obj,Object value)或field.get(Object obj)來設置和獲取字段的值。
Person person3=new Person(20,"klguang"); Field[] fileds = demo.getDeclaredFields(); for (Field field : fileds) { field.setAccessible(true);// 設置些屬性是可以訪問的 String name=field.getName();//取得field的名稱 try { Object value = field.get(person3);// 得到此屬性的值 System.out.println("fieldName:"+name+"\tfieldValue:"+value); if(name.equals("age")) field.set(person3, 40); } catch (Exception e) { e.printStackTrace(); } } MyTestUtil.printWithSign("person after access filed", person3);
調用實例的方法
可通過Class對象的getMethods() 或getMethod(String name, Class<?>...parameterTypes)方法取得Method對象,通過method.invoke(Object obj, Object... args)調用obj對象的方法。
Person person4=new Person(20,"klguang"); Method methods[] = demo.getMethods(); for (java.lang.reflect.Method method : methods) { if (method.getName().startsWith("get")) { try { Object value=method.invoke(person4, null); System.out.println("method invoke , return value is "+value); } catch (Exception e) { System.err.println("method invoke wrong!!"); } } }
反射實踐,控制台打印工具類
我用反射寫了一個打印對象的屬性值工具類MyTestUtil,在控制台格式化輸出對象屬性。因此,可以不用在以后測試javabean時候,復寫toString();當然復寫了更好。
源碼下載 MyTestUtil http://files.cnblogs.com/files/klguang/MyTestUtil.rar
該工具類用到的反射方法如下:
/** * * * @param object * @param recursion * 是否要遞歸 * @return */ private static String beanToStr(Object object, boolean recursion) { if (object == null) return "null"; Class clazz = object.getClass(); StringBuilder sb = new StringBuilder(); //返回源代碼中給出的底層類的簡稱 sb.append(clazz.getSimpleName()).append("["); Field[] fields = sortFieldByType(clazz.getDeclaredFields()); int iMax = fields.length - 1; if (iMax == -1) return sb.append("]").toString(); for (int i = 0;; i++) { Field field = fields[i]; field.setAccessible(true);// 設置些屬性是可以訪問的 String name = field.getName();// 取得field的名稱 if (name.equals("serialVersionUID")) continue; try { Object value = field.get(object);// 得到此屬性的值 if (isSimpleType(value) || !recursion) sb.append(name + " = " + String.valueOf(value)); else sb.append("\r\n" + indent(clazz.getSimpleName().length() + 2," ") + objToStr(value, false) + "\r\n"); } catch (Exception e) { e.printStackTrace(); } if (i == iMax) return sb.append("]").toString(); sb.append(","); } }
-