Reflection
- 什么是
反射
在運行區(qū)間,動態(tài)地去獲取類中的信息(類的信息鼻弧,方法信息,構(gòu)造器信息,字段信息)
- 反射中常用的
API
//Class:表示所有的類信息
// 獲取對應(yīng)類的Class實例
static Class<?> forName(String class className); // 獲取對應(yīng)類的Class實例
T newInstance(); // 創(chuàng)建對應(yīng)類的對象(該類中必須有一個公共無參數(shù)的構(gòu)造器)
String getName(); //獲取類的權(quán)限定名
String getSimpleName(); //獲取類的簡單名稱
Constructor<?>[] getConstructors(); // 獲取當前類中所有的公共構(gòu)造器
COnstructor<T> getConstructor(Class<?> ...parameterTypes); // 獲取當前類中指定的構(gòu)造方法
Method[] getMethods(); // 獲取當前類中所有的公共方法(包括從父類中繼承過來的方法)
Method getMethod(String name, Class<?> ...parameterTypes); // 獲取指定的類方法
Method[] getDeclaredMethods(); //獲取當前類中所有方法青自,和訪問權(quán)限無關(guān)
Method getDeclaredMethod(String name, Class<?> ...parameterTypes);
// 獲取指定的類方法,和訪問權(quán)限無關(guān)
Object invoke(Object obj, Object ...args); //調(diào)用指定的方法
//參數(shù):obj,該方法所屬的對象驱证,如果是靜態(tài)方法則傳入null;args,調(diào)用方法所需的實際參數(shù)
Class類和Class的實例
-
Class
類:
用于描述一切
類
和接口
延窜。枚舉
是一種類
,注解
是一種接口
抹锄。為了明確區(qū)分出
Class
實例表示的是誰的字節(jié)碼逆瑞,Class
提供了泛型
Class<Date> clzl = Date.class; //clzl表示的是Date的字節(jié)碼
Class<String> clz2 = String.class; // clz2表示的是String的字節(jié)碼
-
Class
實例:
JVM
中一份字節(jié)碼
- 獲取到Class實例的三種方式
-
類型.class
(就是一份字節(jié)碼) -
Class.forName(String className);
根據(jù)一個類的全限定名來構(gòu)建Class對象 - 每一個對象都有
getClass()的方法
-
反射很強大荠藤,但是非常消耗性能,主要是為了做工具和框架是用的获高。
// 同一個類在JVM中只有一份字節(jié)碼;
//第一種方式:數(shù)據(jù)類型.class
Class<User> clz1 = User.class;
// 第二種方式:Class.forName(String className);
Class<?> clz2 = Class.forName("cn.itsource.User");
// 第三種方式:對象.getClass();得到對象的真實類型
User u = new User();
Class clz3 = u.getClass();
//clz1 == clz2 == clz3; 因為表示都是JVM中共同的一份字節(jié)碼(User.class)
- 八大基本數(shù)據(jù)類型和關(guān)鍵字
Void
的Class
實例
在八大基本數(shù)據(jù)類型的包裝類和
Void
類中都有一個常量:TYPE
所有數(shù)據(jù)類型都有class屬性哈肖,表示對應(yīng)的Class實例
Integer.Type-->int.class
Void.Type-->void.class
Integer.Type == int.class; // true
Integer.Type == Integer.class; // false
- 數(shù)據(jù)的
Class
實例- 特點
所有具有相同元素類型和維數(shù)的數(shù)組才共享同一份字節(jié)碼對象(Class對象)
// 表示數(shù)組的Class實例:
String[] sArr = {"A", "B", "C"};
Class clz = String[].class; // 此時clz表示就是一個String類的一位數(shù)組類型;
- 示例代碼
public class ArrayClassInstanceDemo {
String[] arr1 = {};
String[] arr2 = {"A", "B"};
Class clz1 = String[].class
Class clz2 = arr2.getClass();
Class clz3 = getClass();
System.out.println(cl2 == c3);
String[][] arr = {}
System.out.println(clz1 == String[][].class); //false
int[] iArr = {};
System.out.prinln(iArr.getClass == clz1); // false
}
-
獲取類中的構(gòu)造器
- 常用方法
// Constructor<T>類:表示父類中構(gòu)造器的類型,Constructor的實例就是某一個類中的某一個構(gòu)造器
// 獲取某一個類中的所有構(gòu)造器:
// 1.明確操作的是哪一份字節(jié)碼對象念秧。
// 2.獲取構(gòu)造器淤井。
// Class類獲取構(gòu)造器方法:
// Constructor類:表示類中構(gòu)造器的類型,Constructor的實例就是某一個類中中的某一個構(gòu)造器
public Constructor<?>[] getConstructors(); // 該方法只能獲取當前Class所表示的類的public修飾的構(gòu)造器
public Constructor<?>[] getDeclaredConstructors(); // 獲取當前Class所表示類的所有構(gòu)造器摊趾,和訪問權(quán)限無關(guān)
public Constructor<T> getConstructor(Class<?>... parameterTypes); // 獲取當前Class所表示類中指定的一個public的構(gòu)造器
// 參數(shù):parameterTypes 表示:構(gòu)造器參數(shù)的Class類型
// 例如:
public User(String username);
Constructor c = clz.getConstructor(String.class);
public Constructor<T> getDeclaredConstructor(Class<?>.. parameterTypes); // 獲取當前Class所表示類中指定的一個構(gòu)造器
- 實例代碼
public class User {
private String name;
private Integer age;
private User() {}
public User(String name) {}
public static void main(String[] args) {
Class<User> clz = User.class;
Constructor<User> conn = clz.getConstructor(in.class);
System.out.println(conn);
// 由于在User類中沒有帶有int類型參數(shù)的構(gòu)造器币狠,所以會拋出異常
// NoSuchMethodException
}
}
- 創(chuàng)建對象
- 方式一
// 在反射中,Constructor最主要的作用就是調(diào)用構(gòu)造器砾层,創(chuàng)建構(gòu)造對象
// 常用方法
public T newInstance(Object... initargs); // 如果調(diào)用帶參數(shù)的構(gòu)造器漩绵,只能使用該方法
// 參數(shù):initargs:表示調(diào)用構(gòu)造器的實際參數(shù)
// 返回:返回創(chuàng)建的實例,T表示Class所表示的類的類型
// 如果:一個雷的構(gòu)造器可以直接訪問梢为,同時沒有參數(shù)渐行,那么可以直接使用Class類中的newInstance方法創(chuàng)建對象.
public Object newInstance(); // 相當于new 類名();
// 注意不能調(diào)用私有的構(gòu)造器(可以先將對象的)
* 方式二
// 調(diào)用Class類中的newInstance()方法來創(chuàng)建
Class clz = User.class;
User user = clz.newInstance();
-
API
中AccessibleObject
的介紹AccessibleObject
類是File``Method
和Constructor
對象的基類。在使用Field铸董、Method或者Constructor對來來設(shè)置或者獲取訪問字段祟印、調(diào)用方法或者創(chuàng)建和初始化類的實例的時候,會執(zhí)行訪問檢查粟害。在ACcessibleObject中提供了一個方法
setAccessible(boolean flag)
方法來設(shè)置是否忽略底層的訪問檢查flag:true
表示忽略訪問檢查蕴忆,false表示要檢查(缺省值)
獲取類中的方法
- 使用反射獲取某一個勒種的方法:
- 找到獲取方法所在類的字節(jié)碼對象
- 摘到需要被獲取的方法
-
Class
類中常用方法:
// 獲取包括自身和繼承過來的所有public方法
public Method[] getMethods();
// 獲取自身的所有方法(不包括繼承,和訪問權(quán)限無關(guān))
public Method[] getDeclaredMethods();
// 表示調(diào)用指定的一個公共方法(包括繼承的)
// 參數(shù):methodName(被調(diào)用方法的名字);parameterTypes(被調(diào)用方法的參數(shù)類型,如:String.class)
public Method getMethod(String methodName, Class<?>... parameterTypes);
// 調(diào)用指定的一個本類中的方法(不包括繼承的)
// 參數(shù):同上
public Method getDeclaredMethod(String name, Class<?>... parameterTypes);
- 示例代碼
// 獲取所有方法
private static void getAllMethod() {
Class clz = User.class;
Method[] ms = clz.getMethods(); // 獲取所有方法(注意:包括繼承的所有公共方法)
for (Method m : ms) {
System.out.println(m);
}
ms = clz.getDeclaredMethos();// 獲取所有的方法(注意:不包括繼承的)
}
for (Method m : ms) {
System.out.println(m);
}
pubic void testMethods() {
// 獲取 public void sayHi();
Class clz = User.class;
// 只有通過方法簽名才能找到唯一的方法
// 方法簽名 = 方法簽名 + 參數(shù)列表(參數(shù)類型悲幅,參數(shù)個數(shù)套鹅,參數(shù)順序)
Method m = clz.getMethod("sayHi", String.class);
System.out.println(m);
// 調(diào)用private void sayGoodBye(String name, int age);
m = clz.getDeclaredMethod("sayGoodBye", String.class, int.class);
System.out.println(m);
}
使用反射調(diào)用方法
- 使用反射調(diào)用方法:
- 找到被調(diào)用方法所在的字節(jié)碼
- 獲取到被調(diào)用的方法對象
- 調(diào)用該方法
調(diào)用方法
// 調(diào)用當前Method所表示的方法
// 參數(shù):obj(被調(diào)用方法的對象--一般都是這個類的一個對象);args(傳遞的參數(shù))
public Object invoke(Object obj,Object.. args);
- 示例代碼
public static void main(String[] args) {
// 1.獲取User類的Class實例
Class<User> clz = User.class;
// 2.獲取User類中指定的方法:public String doWork3(String name, Integer.class);
Method method3 = clz.getMethod("doWork3", String.class, Integer.class);
// 3.調(diào)用對象方法,這里需要傳入被調(diào)用方法所屬的對象汰具,和方法所需要的實際參數(shù)
User u = clz.newInstance(); // 保證在User類中有公共無參數(shù)的構(gòu)造器
// 用變量ret來接收該方法的返回值
String ret = (String) method3.invoke(u, "neld", 18);
// 獲取User類的一個指定的私有方法:private void doWork2(String name)
Method method2 = clz.getMethod("doWork2", String.class);
// 將方法設(shè)置為可訪問的
method2.setAccessible(true);
// 調(diào)用方法
method2.invoke(u, "neld");
}
使用放射調(diào)用靜態(tài)方法
- 方法
public Object invoke(Object obj, Object.. args);
// 如果底層方法是靜態(tài)的卓鹿,那么可以忽略指定的obj參數(shù)。將obj參數(shù)設(shè)置為null即可留荔。
使用反射調(diào)用可變參數(shù)
- 方法
對于數(shù)組的引用類型吟孙,底層會自動解包 ,為了解決該問題聚蝶,我們使用Object的一個一維數(shù)組把實際參數(shù)包裝起來杰妓。
無論是基本數(shù)據(jù)類型還是引用數(shù)據(jù)類型,或者是可變參數(shù)類型碘勉,方正就是一切實際參數(shù)都包裝在
new Object[] {}
中巷挥,就沒有自動解包
的問題了
- 示例代碼
public static main(String[] args) {
// 使用反射調(diào)用public static int show(int... args)
Class clz = VarArgsMethodInvokeDemo.class; // 獲取本類的字節(jié)碼
Method m = clz.getMethod("show1", int[].class);
//m.invoke(null, 1, 2, 3, 4, 5) // 會報錯
m.invoke(null, new int[]{1, 2, 3, 4, 5}); // 通過
m.invoke(null, new Object[]{new int[] {1, 2, 3, 4, 5}}); // 通過
m = clz.getMethod("show2", String[].class);
// m.invoke(null, "A", "B", "C"); // 不通過
// m.invoke(null, new String[] {"A", "B", "C"})// 不通過
// 對于數(shù)組類型的引用型參數(shù),底層會自動解包验靡,為了解決該問題倍宾,我們使用Object的一維數(shù)組把實際參數(shù)包裝起來
m.invoke(null, new Object[]{new String[]{"A", "B", "C"}});
}
// 可變參數(shù)底層就是一個數(shù)組
// 基本類型
public static void show1(int... args) {
System.out.println(Arrays.toString(args));
}
// 引用類型
public static void show2(String.. . args) {
System.out.println(Arrays.toString(args));
}
使用反射獲取字段
-
思路
- 找到字段所在類的字節(jié)碼
- 獲取字段
常用方法
// 獲取當前Class所表示類中的所有public的字段雏节,包括繼承的字段
public Field[] getFileds();
// 獲取類中指定的public字段,包括繼承的字段
public Field getField(String fieldName);
// 獲取當前類中的所有字段高职,不包括繼承的字段
public Field[] getDeclaredFileds();
// 獲取當前Class所表示類中該fieldName名字的字段矾屯,不包括繼承的字段
public Field[] getDeclaredFileds();
// 獲取當前Class中的字段,指定的字段初厚,不包括繼承的字段
public Field getDeclaredFiled(String name);
單例設(shè)計模式
- 概念
在項目中件蚕,某個類有且只有一個實例,一般的把工具類做成單例的
- 步驟
- 把當前類的構(gòu)造器私有化
- 在當前類中實現(xiàn)創(chuàng)建好一個私有的靜態(tài)對象
- 向外暴露一個公共的靜態(tài)的方法來返回該對象
- 寫法
- 餓漢式
- 懶加載式
- 枚舉
- 使用緩存機制來實現(xiàn)單例效果
- 在Spring中(對象工廠),創(chuàng)建的對象默認就是單例的
餓漢式
public class ArrayTool {
private ArrayTool(){}
private static ArrayTool instance = new ArrayTool();
// 獲取類的一個實例
public static ArrayTool getInstance() {
return instance;
}
// 工具方法
public void sort(int arr) {
System.out.println("數(shù)組排序...");
}
}
懶加載(可能有線程安全問題产禾,所以要使用同步方法)
public class ArrayTool {
private static ArrayTool instance = null;
public static ArrayTool getInstace() {
if (instance == null) {
synchronized (ArrayTool.class) {
if (instance == null) {
instance = new ArrayTool();
}
}
}
}
}
public void sort(int[] arr) {
System.out.println("數(shù)組排序...");
}
枚舉
public enum ArrayTool {
INSTANCE; // public static final ArrayTool INSTANCE;
// 工具方法
private ArrayTool(){}
public void sort(int[] arr) {
System.out.println("數(shù)組排序...");
}
}
Eclipse項目下classpath文件分析
source folder目錄下的文件會編譯到output(默認bin)目錄中
- 加載資源文件
- 使用相對路徑
相對于
CLASSPATH
的根路徑(output, 輸出目錄) 排作,需要獲取ClassLoader
對象
// 獲取ClassLoader對象的兩種方式
// 方式一:
ClassLoader loader = Thread.currentThread().getContextClassLoader();
// 方式二:
ClassLoader loader2 = 當前類名.class.getClassloader();
// 在調(diào)用即可獲取到output目錄下的資源文件流
public InputStream getResourceAsStream(String fileName);
注意
在使用
newInstace()
的時候,需要被實例化對象的類亚情,具有無參的公共的構(gòu)造方法妄痪。