一、概述
1、Java反射機制(Java-Reflect):
在運行狀態中,對于任意一個類,都能夠知道這個類中的所有屬性和方法;對于任意一個對象,都能夠調用它的任意一個方法和屬性;這種動態獲取的信息以及動態調用對象的方法的功能稱為java的反射機制。
反射是Java開發中一個非常重要的概念,掌握了反射的知識,才能更好的學習Java高級課程.
2、Java 反射機制的功能
在運行時判斷任意一個對象所屬的類。
在運行時構造任意一個類的對象。
在運行時判斷任意一個類所具有的成員變量和方法。
在運行時調用任意一個對象的方法。
生成動態代理。
3、Java 反射機制的應用場景
逆向代碼 ,例如反編譯
與注解相結合的框架 例如Retrofit
單純的反射機制應用框架 例如EventBus
動態生成類框架 例如Gson
二、通過Java反射查看類信息
1、獲得Class對象
每個類被加載之后,系統就會為該類生成一個對應的Class對象。通過該Class對象就可以訪問到JVM中的這個類。
在Java程序中獲得Class對象通常有如下三種方式:
使用 Class 類的
forName(String clazzName)
靜態方法。該方法需要傳入字符串參數,該字符串參數的值是某個類的全限定名(必須添加完整包名)。調用某個類的
class
屬性來獲取該類對應的 Class 對象。調用某個對象的
getClass()
方法。該方法是java.lang.Object類中的一個方法。
//第一種方式 通過Class類的靜態方法——forName()來實現
class1 = Class.forName("com.lvr.reflection.Person");
//第二種方式 通過類的class屬性
class1 = Person.class;
//第三種方式 通過對象getClass方法
Person person = new Person();
Class<?> class1 = person.getClass();
對于方式一和方式二都是直接根據類來取得該類的 Class 對象,相比之下,方式二有如下的兩種優勢:
- 代碼跟安全。程序在編譯階段就能夠檢查需要訪問的 Class 對象是否存在。
- 線程性能更好。因為這種方式無須調用方法,所以性能更好。
可以通過類的類類型創建該類的對象實例。
Class.newInstance();
//
Foot foot = (Foot) c1.newInstance();
2、從 Class 中獲取信息
一旦獲得了某個類所對應的Class 對象之后,就可以調用 Class 對象的方法來獲得該對象的和該類的真實信息了。
獲取 Class 對應類的成員變量
Field[] getDeclaredFields();
// 獲取 Class 對象對應類的所有屬性,與成員變量的訪問權限無關。
Field[] getFields();
// 獲取 Class 對象對應類的所有 public 屬性。
Field getDeclaredField(String name);
// 獲取 Class 對象對應類的指定名稱的屬性,與成員變量的訪問權限無關。
Field getField(String name);
// 獲取 Class 對象對應類的指定名稱的 public 屬性。獲取 Class 對應類的方法
Method[] getDeclaredMethods();
// 獲取 Class 對象對應類的所有聲明方法,于方法的訪問權限無關。
Method[] getMethods();
// 獲取 Class 對象對應類的所有 public 方法,包括父類的方法。
Method getMethod(String name, Class<?>...parameterTypes);
// 返回此 Class 對象對應類的、帶指定形參列表的 public 方法。
Method getDeclaredMethod(String name, Class<?>...parameterTypes);
// 返回此 Class 對象對應類的、帶指定形參列表的方法,與方法的訪問權限無關。獲取 Class 對應類的構造函數
Constructor<?>[] getDeclaredConstructors();
// 獲取 Class 對象對應類的所有聲明構造函數,于構造函數的訪問權限無關。
Constructor<?>[] getConstructors();
// 獲取 Class 對象對應類的所有 public 構造函數。
Constructor<T> getDeclaredConstructor(Class<?>...parameterTypes);
// 返回此 Class 對象對應類的、帶指定形參列表的構造函數,與構造函數的訪問權限無關。
Constructor<T> getConstructor(Class<?>...parameterTypes);
// 返回此 Class 對象對應類的、帶指定形參列表的 public 構造函數。獲取 Class 對應類的 Annotation(注釋)
<A extends Annotation>A getAnnotation(Class<A> annotationClass);
// 嘗試獲取該 Class 對對象對應類存在的、指定類型的 Annotation;如果該類型的注解不存在,則返回 null。
<A extends Annotation>A getDeclaredAnnotation(Class<A> annotationClass);
// 這是Java8新增的方法,該方法嘗試獲取直接修飾該 Class 對象對應類、指定類型的Annotation;如果該類型的注解不存在,則返回 null。
Annotation[] getAnnotations();
// 返回修飾該 Class 對象對應類存在的所有Annotation。
Annotation[] getDeclaredAnnotations();
// 返回直接修飾該 Class 對應類的所有Annotation。
<A extends Annotation>A[] getAnnotationsByType(Class<A> annotationClass);
// 獲取修飾該類的、指定類型的多個Annotation。
<A extends Annotation>A[] getDeclaredAnnotationsByType(Class<A> annotationClass);
// 獲取直接修飾該類的、指定類型的多個Annotation。獲取 Class 對應類的內部類
Class<?>[] getDeclaredClasses();
// 返回該 Class 對象對應類包含的全部內部類。獲取 Class 對應類的外部類
Class<?> getDeclaringClass();
// 返回該 Class 對象對應類所在的外部類。獲取 Class 對應類所實現的接口
Class<?>[] getInterfaces();
獲取 Class 對應類所繼承的父類
Class<? super T> getSuperClass();
獲取 Class 對應類的修飾符、所在包、類名等基本信息
int getModifiers();
// 返回此類或接口的所有修飾符。修飾符由 public、protected、private、final、static、abstract 等對應的常量組成,返回的整數應使用 Modifier 工具類的方法來解碼,才可以獲取真實的修飾符。
Package getPackage()
// 獲取該類的包。
String getName()
// 以字符串的形式返回此 Class 對象所表示的類的名稱。
String getSimpleName()
// 以字符串的形式返回此 Class 對象所表示的類的簡稱。判斷該類是否為接口、枚舉、注解類型等
boolean isAnnotation()
// 返此 Class 對象是否是一個注解類型(由@interface定義)。
boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)
// 判斷此 Class 對象是否使用了Annotation修飾。
boolean isAnonymousClass()
// 此 Class 對象是否是一個匿名類。
boolean isArray()
//此 Class 對象是否是一個數組。
boolean isEnum()
// 此 Class 對象是否是一個枚舉(由 enum 關鍵字定義)。
boolean isInterface()
// 此 Class 對象是否是一個接口(由 interface 關鍵字定義)。
boolean isInstance(Object obj)
// 判斷 obj 是否是此 Class 對象的實例。該方法完全可以替代 instanceof 操作符。
3、Java 8 新增的方法——參數反射
Java 8 在java.lang.reflect包下新增了一個 Executable 抽象基類,該對象代表可執行的類成員,該類派生了 Constructor、Method 兩個子類。
Executable 基類提供了大量的方法來獲取修飾該方法或構造器的注解信息;還提供了
isVarArgs()
// 用于判斷該方法或構造器是否包含數量可變的形參。
getModifiers()
// 獲取該方法或構造器的修飾符。
此外,Executable 提供了如下兩個方法:
int ParamenterCount()
// 獲取該構造器或方法的形參個數。
Paramenter[] getParamenters()
// 獲取該構造器或方法的所有形參。
上面的第二個方法返回了一個 Paramenter[] 數組,Paramenter也是 Java 8 新增的API,每個 Paramenter 對象代表方法或構造器的一個參數。
Paramenter 提供了大量方法來獲取聲明該參數的泛型信息,還提供了如下常用的方法來獲取參數信息:
getModifiers()
// 獲取修飾該形參的修飾符。
String getName()
// 獲取形參名。
Type getParamenterizedType()
// 獲取帶泛型的形參類型。
Class<?> getType()
// 獲取形參類型。
boolean isNamePresent()
// 返回該類的 class 文件中是否包含了方法的形參名信息。
boolean isVarArgs()
// 用于判斷該參數是否為個數可變的形參。
三、使用反射生成并操作對象
Class 對象可以獲得該類里的方法(由 Method 對象表示)、構造器(由 Constructor 對象表示)、成員變量(由 Field 對象表示),這三個類都位于 java.lang.reflect 包下。
程序可以通過 Method
對象來執行對應的方法,
通過 Constructor
對象來調用對應的構造器創建實例,
通過 Field
對象直接訪問并修改對象的成員變量值。
1、創建對象
通過反射來生成對象的兩種方式:
使用 Class 對象的
newInstance()
方法來創建該 Class 對象對應類的實例。
要求:Class 對象的對應類要有默認構造器,而執行newInstance()
方法實際上是利用默認構造器來創建該類的實例。先使用 Class 對象獲取指定的 Constructor 對象,再調用 Constructor 對象的
newInstance()
方法來創建該 Class 對象對應類的實例。
這種方式可以選擇使用指定的構造器來創建實例。
通過第一種方式來創建對象是比較常見的情形,在很多的 JavaEE 框架中都需要根據配置文件信息來創建Java對象。從配置文件中讀取的只是某個類的字符串類名,程序需要根據該字符串來創建對應的實例,就必須使用到反射。
先建一個配置文件,obj.properties
a=java.util.Date
b=javax.swing.JFrame
/**
* 功能:實現一個簡單的對象處
* 思路:該對象池會根據配置文件讀取 key-value 對,然后創建這些對象,
* 并將這些對象放入到一個 HashMap 中。
* @author Administrator
*
*/
public class ObjectPoolFactory {
// 定義一個對象池,<對象名,實際對象>
private Map<String, Object> objectPool = new HashMap<>();
/**
* 創建對象
* @param className 字符串類名
* @return 返回對應的 Java 對象
* @throws ClassNotFoundException
* @throws InstantiationException
* @throws IllegalAccessException
*/
private Object createObject(String className)
throws ClassNotFoundException
, InstantiationException, IllegalAccessException {
// 獲取對應的 Class 對象
Class<?> clazz = Class.forName(className);
// 使用對應類的默認構造器創建實例
return clazz.newInstance();
}
/**
* 根據指定文件來初始化對象池
* @param fileName 配置文件名
* @throws ClassNotFoundException
* @throws InstantiationException
* @throws IllegalAccessException
*/
public void initPool(String fileName)
throws ClassNotFoundException
, InstantiationException, IllegalAccessException {
try(FileInputStream fis = new FileInputStream(fileName)) {
Properties props = new Properties();
props.load(fis);
for (String name : props.stringPropertyNames()) {
objectPool.put(name, createObject(props.getProperty(name)));
}
} catch (IOException e) {
System.out.println("讀取" + fileName + "異常!");
}
}
/**
* 根據指定的 name 值來獲取對應的對象
* @param name
* @return
*/
public Object getObject(String name) {
return objectPool.get(name);
}
}
測試文件:
public class Test {
public static void main(String[] args)
throws ClassNotFoundException
, InstantiationException, IllegalAccessException {
ObjectPoolFactory opf = new ObjectPoolFactory();
opf.initPool("obj.properties");
System.out.println(opf.getObject("a"));
System.out.println(opf.getObject("b"));
}
}
運行的結果:
這種使用配置文件來配置對象,然后由程序根據配置文件來創建對象的方式非常有用,如 Spring 框架就采用這種方式大大簡化了 JavaEE 應用的開發,當然,Spring采用的是 XML 配置文件——因為 XML 配置文件能配置的信息更加的豐富。
第二種方式,利用指定的構造器來創建 Java 對象。
- 獲取該類的 Class 對象。
- 利用 Class 對象的
getConstrustor()
方法來獲取指定的構造器。 - 調用 Construstor 的
newInstance()
方法來創建 Java 對象。
public class CreateObject {
public static void main(String[] args) throws Exception {
Class<?> jframeClass = Class.forName("javax.swing.JFrame");
Constructor<?> ctor = jframeClass.getConstructor(String.class);
Object obj = ctor.newInstance("測試窗口");
System.out.println(obj);
}
}
實際上,只有當程序需要動態創建某個類的對象時才會考慮使用反射,通常在開發通用性比較廣的框架、基礎平臺時可能會大量使用反射。
2、調用方法
當獲得某個類對應的 Class 對象后,就可以通過該 Class 對象的方法:
getMethods()
// 獲取全部方法。
getMethod()
// 獲取指定方法。
這兩個方法的返回值是 Method 數組,或者 Method 對象。
每個 Method 對象對應一個方法,獲得 Method 對象后,程序就可通過該 Method 來調用它對應的方法。在 Method 里包含一個 invoke()
方法:
Object invoke(Object obj, Object... args)
// 該方法中的 obj 是執行該方法的主調,后面的 args 是執行該方法時傳入該方法的實參。
// 生成新的對象:用newInstance()方法
Object obj = class1.newInstance();
// 首先需要獲得與該方法對應的Method對象
Method method = class1.getMethod("setAge", int.class);
// 調用指定的函數并傳遞參數
method.invoke(obj, 28);
當通過Method的invoke()
方法來調用對應的方法時,Java會要求程序必須有調用該方法的權限。如果程序確實需要調用某個對象的 private 方法,則可以先調用以下方法:
setAccessible(boolean flag)
// 值為true,指示該 Method 在使用時應該取消Java語言的訪問權限檢查;值為false,則知識該Method在使用時要實施Java語言的訪問權限檢查。
Method 、 Constructor 、Field 都可以調用該方法,從而實現通過反射來調用 private 方法、private 構造器、private 成員變量。
3、訪問成員變量值
通過Class對象的:
getFields()
// 獲取全部成員變量,返回 Field數組或對象。
getField()
// 獲得指定成員變量,返回 Field數組或對象。
Field 提供了兩組方法來讀取或設置成員變量的值:
getXXX(Object obj)
// 獲取obj對象的該成員變量的值。
setXXX(Object obj, XXX val)
// 將 obj 對象的該成員變量設置成val值。
//生成新的對象:用newInstance()方法
Object obj = class1.newInstance();
//獲取age成員變量
Field field = class1.getField("age");
//將obj對象的age的值設置為10
field.setInt(obj, 10);
//獲取obj對象的age的值
field.getInt(obj);