反射

一.反射的基本概念

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();
}
}



            


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章