一.反射的基本概念
1.類的加載:
將一個.class文件加載到內存中,形成一個對象,並執行起來;
Java 虛擬機內使用類加載器將.class文件加載到內存中,形成一個對象;這個對象在內存中只有一份;由虛擬機類加載器負責創建,程序員只能獲取使用,程序員不能自己創建;
進一步細分,可以分爲:加載,連接,初始化;
加載:將.class讀取到內存中的過程;
連接:檢查語法格式與關鍵代碼,及常量的賦值與常量的運算;
初始化:創建出來對象,和以前面向對象的初始化過程一致;
2.類加載器:用於將所有的.class文件(磁盤上的或者網絡上的)加載到內存中,併爲之生成對應的java.lang.Class 對象
Bootstrap ClassLoader:根(原始/引導)類加載器,負責加載JAVA的核心類;不是java.lang.ClassLoader 的子類,而是由JVM自身實現的.
Extension ClassLoader:擴展類加載器,負責加載jre的擴展目錄(%JAVA_HOME%jre/lib/ext)中JAR包的類.
System ClassLoader:系統(應用)類加載器.
3.字節碼:程序的一種低級表示,可以運行於JAVA虛擬機.
4.類初始化的時機:
1:使用關鍵字new,創建一個類的對象;
2:使用一個類中的靜態的方法或屬性;
3:使用反射強制加載一個類;
4:初始化一個類(A)的子類(B)的時候;
5:直接使用java.exe命令運行一個.class文件;
5.反射概述:
JAVA 反射機制是在運行狀態中,對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意一個對象,都能夠調用它的任意方法和屬性;這種動態獲取信息以及動態調用對象方法的功能稱爲java語言的反射機制。
二.class類介紹及獲取字節碼對象的三種方式:
Class:描述類的類;
所有的數據類型都有對應的.class文件,由.class文件生成的對象,我們成爲:字節碼文件對象!
所有的字節碼文件對象的數據類型都是Class類型;
方式1:
使用一個數據類型的靜態屬性,屬性名是:class
方式2:
使用Object類的方法,getClass方法;通過實例化對象調用
方式3:(重點)
使用Class類的靜態方法,forName(要加載的類的全路徑);全路徑就是帶包的路徑;由Class類直接調用.如果不使用eclipse,則類的全路徑名爲(類所在的文件夾名.類名)
class 類 (java.lang)
繼承關係:java.lang.Object--java.lang.Class<T>
定義:public final class Class<T> extends Object implements Serializable, GenericDeclaration, Type, AnnotatedElement
常用方法:
public static Class<?> forName(String className) throws ClassNotFoundException{}:
返回與帶有給定字符串名的類或接口相關聯的 Class 對象
代碼演示:
import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.lang.reflect.Field; class Person{ private String name; private int age; //無參構造 public Person(){ } //全參構造 public Person(String name, int age){ this.name = name; this.age = age; } //getter/setter public String getName(){ return name; } public int getAge(){ return age; } public void setName(String name){ this.name = name; } public void setAge(int age){ this.age = age; } @Override public String toString(){ return "["+"name="+name+",age="+age+"]"; } } public class Ref{ public static void main(String[] args) throws Exception{ //方式一獲取字節碼文件:使用數據類型的靜態屬性 Class c1 = Person.class; System.out.println(c1);//class Person //獲取String類型的字節碼文件 String str = "helloworld"; System.out.println(String.class);//class java.lang.String System.out.println(str.getClass());//class java.lang.String //方式二:Object類的方法,getClass方法 Person p = new Person(); System.out.println(p.getClass());//class Person //方式三:使用Class類的靜態方法,forName(要加載的類的全路徑) Class c2 = Class.forName("Person"); System.out.println(c2); //獲取構造方法一:Class類的getConstructor()方法 Constructor co1 = c1.getConstructor(); System.out.println(co1);//public Person() //獲取構造方法二:Class類的getConstructors()方法 Constructor[] co2 = c1.getConstructors(); for(Constructor c : co2){ System.out.println(c);//public Person() public Person(java.lang.String,int) } //獲取構造方法三:利用Constructor類的newInstance()方法初始化Object對象 Class c4 = Class.forName("Person"); Constructor co4 = c4.getConstructor(String.class, int.class); Object obj = co4.newInstance("Jack",18); System.out.println(obj); //利用newInstance()方法快速獲取空對象 Object obj1 = c4.newInstance(); System.out.println(obj1); //獲取普通方法--setName方法 Class c5 = Class.forName("Person");//獲取字節碼文件 Object obj5 = c5.newInstance();//獲取字節碼文件的普通對象 Method m5 = c5.getMethod("setAge",int.class);//面向普通對象獲取方法 Object obj6 = m5.invoke(obj5,22);//面向方法,傳入對象,及方法需要的實參 System.out.println(obj5); //獲取屬性 Class c6 = Class.forName("Person");//獲取字節碼文件對象 Field f6 = c6.getDeclaredField("name");//獲取name屬性 f6.setAccessible(true); Object obj7 = c6.newInstance(); f6.set(obj7,"rongrong"); Field age = c6.getDeclaredField("age"); age.setAccessible(true); age.set(obj7,24); System.out.println(obj7); } }
三.獲取構造方法:只有從字節碼文件中才能獲取構造方法
1.通過Class類的普通方法
public Constructor<T> getConstructor(Class<?>... parameterTypes) throws NoSuchMethodException, SecurityException{}:
返回一個 Constructor 對象,它反映此 Class 對象所表示的類的指定公共構造方法。
parameterTypes:代表的是參數數據類型的字節碼文件對象!
public Constructor<?>[] getConstructors() throws SecurityException{}:獲取到所有的public權限的構造方法,一般不使用該方法
2.通過Construct類的newInstance(),類中必須包含無參構造
public T newInstance(Object... initargs) throws InstantiationException,IllegalAccessException,IllegalArgumentException,
InvocationTargetException{}:
四.獲取普通方法:
1.通過Class類的普通方法
public Method getMethod(String name,Class<?>... parameterTypes) throws NoSuchMethodException,SecurityException{}:
name所賦值(實參)的類型的字節碼文件
public Method[] getMethods() throws SecurityException{}:
public T newInstance() throws InstantiationException, IllegalAccessException{}:創建此 Class 對象所表示的類的一個新實例。(普通對象)
調用該方法的時候,必須保證字節碼文件對象中有空參數的public權限的構造方法;否則執行報錯
2.通過 Method 類的方法
public Object invoke(Object obj,Object... args) throws IllegalAccessException,IllegalArgumentException,InvocationTargetException{}:
對帶有指定參數的指定對象調用由此 Method 對象表示的底層方法
Obj:代表的是該方法對象所在的字節碼文件對象創建出來的實例化對象;
Args:方法執行的時候,需要的實際參數;
Object:返回的是方法執行的結果,如果方法是void,那麼默認該方法的返回值是null
五.獲取屬性:
1.概述:
由於屬性私有,可使用暴力反射(不推薦!!),因此,實際開發中都是通過反射屬性的getXxx與setXxx方法,來替代反射屬性;
通過字節碼文件對象,使用反射提取出來的屬性或成員變量,稱爲Field類的對象;
Field 類的對象可以用於保存數據值和獲取數據值;
成員變量隨着對象的創建而產生,隨着對象的死亡而死亡,因此在使用Field對象的時候,必須有普通對象;
定義:public final class Field extends AccessibleObject implements Member
2.通過Class類的普通方法
public Field getDeclaredField(String name) throws NoSuchFieldException,SecurityException{}:
public Field[] getDeclaredFields() throws SecurityException{}:
3.AccessibleObject 類的方法: 執行之前,忽略權限檢查
public void setAccessible(boolean flag) throws SecurityException{}:權限設爲true,通過檢查
4.Field 類的方法
public void set(Object obj,Object value) throws IllegalArgumentException,IllegalAccessException{}:
public Object get(Object obj) throws IllegalArgumentException,IllegalAccessException{}:
六.綜合案例:
//在eclipse中執行,並在工程中創建相應環境,包括configure.properties文件和Person類(String name, int age); package case01; import java.io.FileReader; import java.lang.reflect.Method; import java.util.Properties; import java.util.Set; /* 使用反射的方式創建一個類的對象,並對對象的屬性賦值,打印對象;給對象賦值的時候,屬性值從配置文件中讀取; */ public class ReflectCase { public static void main(String[] args) throws Exception { // 創建Property集合,用於存儲配置文件信息 Properties p = new Properties(); FileReader fr = new FileReader("configure.properties"); p.load(fr); // 獲取反射路徑 String path = p.getProperty("classPath"); // 根據路徑獲取字節碼對象 Class c = Class.forName(path); // 面向字節碼對象創建普通對象 Object obj = c.newInstance(); // 獲取鍵集 Set<String> keySet = p.stringPropertyNames(); // 迭代,並拼接成set方法 for (String s : keySet) { // 判斷如果不是name或age,就跳過 if (!("name".equals(s) || "age".equals(s))) { continue; } // 拼接成set方法,我們需要反射set方法,需要一個方法名,即setName,setAge StringBuilder builder = new StringBuilder(); builder.append("set").append(s.substring(0, 1).toUpperCase()).append(s.substring(1)); if (builder.toString().endsWith("ame")) { // 反射方法 Method setNameMethod = c.getMethod(builder.toString(),String.class); // 執行方法 Object obj1 = setNameMethod.invoke(obj, p.getProperty(s)); } else { // 反射方法 Method setAgeMethod = c.getMethod(builder.toString(), int.class); // 執行方法 Object obj2 = setAgeMethod.invoke(obj,Integer.parseInt(p.getProperty(s))); } } System.out.println(obj); } }
七.需求,返回傳入數據的類型
public class Check{ public static void main(String[] args){ System.out.println(test(-15%4)); } public static Object test(Object obj){ return obj.getClass(); } }