Introduction
內容源自對慕課網視頻Java反射教程的筆記
慕課網教程-反射——Java高級開發必須懂的
Class類的使用
- 類是對象,類是java.lang.Class的對象
- 如何表示這個對象
public class ClassDemo1 {
// Foo的實例對象的表示
Foo foo = new Foo();
// 萬事萬物皆對象,那麼Foo這個類怎麼作爲對象表示出來
}
class Foo {}
我們進入Class的源代碼,可以看到一個私有的構造函數(這個構造函數可能因爲JDK的版本不同而不同)
/*
* Private constructor. Only the Java Virtual Machine creates Class objects.
* This constructor is not used and prevents the default constructor being
* generated.
*/
private Class(ClassLoader loader) {
// Initialize final field for classLoader. The initialization value of non-null
// prevents future JIT optimizations from assuming this final field is null.
classLoader = loader;
}
說得很明確了只有JVM才能訪問。
要想把Foo這個類作爲對象表示出來有三種方式,內容基本都在註釋裏面了
package com.nevercome;
/**
* @author: sun
* @date: 2019/4/6
*/
public class ClassDemo1 {
public static void main(String[] args) {
// Foo的實例對象的表示
// Foo foo = new Foo("1");
Foo foo = new Foo();
// 萬事萬物皆對象,Foo這個類怎麼作爲對象表示出來
// 任何一個類都是Class的實例對象,但是我我們無法用構造函數的方式
// 但其實有三種方式
// 1. .class
Class c1 = Foo.class; // 這說明所有的類,都有一個爲class的靜態變量
// 2. 已知該類的對象 getClass()
Class c2 = foo.getClass();
// 來梳理一下概念
// 根據官網的定義: c1,c2 表示了Foo類的類類型(class type)
// 類類型指的就是類自己,它是Class類的一個實例,如Foo類的類類型就是c1
// c1是它作爲Class類的實例(對象),我們稱之爲該類(這裏是Foo)的類類型
// c1 和 c2都是Foo的類類型,一個類作爲Class類的實例只可能有一個實例對象
System.out.println(c1 == c2); // true
Class c3 = null;
try {
c3 = Class.forName("com.nevercome.Foo");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
System.out.println(c2 == c3); // true
// 我們還可以通過類的類類型來創建該類的對象
// 這要求該類必須有無參的構造函數(隱式和顯示皆可)
// 不然會拋出NoSuchMethodException
try {
Foo foo1 = (Foo)c1.newInstance();
Foo foo2 = (Foo)c2.newInstance();
foo.print();
foo1.print();
foo2.print();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
class Foo {
// public Foo(String name) {
//
// }
public void print() {
System.out.println("foo");
}
}
動態加載類 類的反射
上述說的第三種方式Class.forName(“類的權限定名”),不僅代表了類的類類型,還代表了動態加載類。編譯時刻加載類是靜態加載類,運行時刻加載類是動態加載類。
看一下下面這個Office類:
public class Office {
public static void mian(String[] args) {
if("Word".equals(args[0])) {
Word w = new Word();
w.start();
}
if("Excel".equals(args[0])) {
Excel e = new Excel();
e.start();
}
}
}
使用javac命令編譯這個Office類,毫無疑問,它會報錯。因爲我們沒有Word和Excel類。那麼如果你有了Word類呢,當然還是會報錯,因爲你沒有Excel。仔細想一下這個問題,這是否和你的真實需求相背離了呢?我們希望有Word類,Word的功能就可以使用,而不是因爲Excel缺失了而所有的Office全家桶都無法使用了。這就是靜態加載類(new 都是靜態加載),編譯時刻要求加載所需的全部類的缺陷。我們希望只有在我們需要使用他們的時候,他們才被加載,Class.forName()可以完成這個任務。
public class OfficeBetter {
public static void main(String[] args) {
try {
// 動態加載類 運行時加載
Class c = Class.forName(args[0]);
// 通過類類型創建對象
// 強制類型轉換? Word 還是 Excel
// 沒錯!需要接口或者繼承
OfficeAble oa = (OfficeAble) c.newInstance();
oa.start();
} catch(Exception e) {
e.printStackTrace();
}
}
}
public interface OfficeAble {
void start();
}
public class Excel implements OfficeAble {
public void start() {
System.out.println("excel start");
}
}
public class Word implements OfficeAble {
public void start() {
System.out.println("word start");
}
}
這樣子我們就實現了功能上的彼此獨立與動態擴展。
獲取方法信息 方法的反射
基本數據類型、void關鍵字都可以獲取類的類類型
public class ClassDemo2 {
public static void main(String[] args) {
Class c1 = int.class; // int數據類型 的類類型
Class c2 = String.class; // String類的 類類型
Class c3 = double.class; // double數據類型 的類類型
Class c4 = Double.class; // Double類 的類類型
Class c5 = void.class; // void關鍵字的 類類型
// 只要是可以在類內被定義的,就都可以獲取到它的類類型
// Class c6 = package.class; // 會報錯
System.out.println(c1.getName()); // int
System.out.println(c2.getName()); // java.lang.String
System.out.println(c2.getSimpleName()); // String
System.out.println(c3.getName()); // double
System.out.println(c4.getName()); // java.lang.Double
System.out.println(c5.getName()); // void
}
}
public class ClassUtils {
/**
* 打印類的信息:包括類的成員函數 成員變量
* @param obj 該對象所屬類的信息
*/
public static void printClassInfo(Object obj) {
// 1. 要獲取類的信息 首先要獲取類的類類型
// 這裏我們傳入的是一個Object參數 那麼我可以使用obj.getClass()方法
// Object是所有對象的父類 那麼這裏傳遞的是哪個子類 獲取到的就是那個子類的類類型
// 其底層實現使用了Native方法來實現...Native方法是Java聲明底層用C語言去實現的 目前我們不關心它的底層原理
Class c = obj.getClass();
System.out.println("類的全限定名是:" + c.getName());
// 2. 要獲取成員函數...而方法是Method類的對象
/*
* Method類,方法對象
* 一個成員方法就是一個Method對象
* getMethods() 獲取的是所有public的方法,包括從父類繼承而來
* getDeclaredMethods() 獲取的是該類自己聲明的方法,不包括符類,不限定訪問權限
*/
Method[] methods = c.getMethods();
// c.getDeclaredMethods();
for(Method method : methods) {
// 1. 獲取方法的返回類型
Class returnType = method.getReturnType(); // 返回的是類類型
System.out.print(returnType.getName() + " ");
// 2. 獲取方法的名稱
System.out.print(method.getName() + "(");
// 3. 獲取參數列表(類型)
Class[] paramTypes = method.getParameterTypes(); // 返回的是參數列表的類類型
for(int i=0; i<paramTypes.length; i++) {
if(i == paramTypes.length - 1) {
System.out.print(paramTypes[i].getName());
} else {
System.out.print(paramTypes[i].getName() + ", ");
}
}
System.out.println(")");
}
}
}
獲取構造函數與成員變量信息 構造函數與成員變量的反射
上面說到了獲取方法信息,接着來看構造函數與成員變量。
/**
* 打印成員變量的信息
* @param obj
*/
public static void printFieldInfo(Object obj) {
/*
* 成員變量也是對象
* 是java.lang.reflect.Field的對象
* getFields() 獲取的是public 包括父類
* getDeclaredFields() 所有自己聲明的
*/
Class c = obj.getClass();
System.out.println("類的成員變量:");
Field[] fields = c.getFields();
Field[] fields1 = c.getDeclaredFields();
for(Field field : fields1) {
Class fieldType = field.getType(); // 成員變量類型的類類型
String typeName = fieldType.getName();
String filedName = field.getName(); // 成員變量的名字 如int a 即爲a
System.out.println(fieldType + " " + filedName);
}
}
和方法的獲取基本是一致的。
同樣的,構造函數也是基本一致的
/**
* 打印構造函數的信息
* @param obj
*/
public static void printContructInfo(Object obj) {
Class c = obj.getClass();
/*
* 構造函數也是對象
* java.lang.Constructor 封裝了構造函數的信息
* getConstructors() public
* getDeclaredConstructors() 自己聲明的
*/
// Constructor[] constructors = c.getConstructors();
Constructor[] constructors = c.getDeclaredConstructors();
System.out.println("類的全限定名是:" + c.getName());
System.out.println("類的自己聲明的構造函數:");
for(Constructor constructor : constructors) {
// 獲取構造函數名
System.out.print(constructor.getName() + "(");
// 參數列表
Class[] paramTypes = constructor.getParameterTypes();
for (int i = 0; i < paramTypes.length; i++) {
if (i == paramTypes.length - 1) {
System.out.print(paramTypes[i].getName());
} else {
System.out.print(paramTypes[i].getName() + ", ");
}
}
System.out.println(")");
}
}
當然Class還有很多其它的API…諸如獲取到父類…包名等待,要記住的一點是要使用Class的接口就必須要先獲取到類類型。
方法的反射
- 獲取某一個方法 getMethod(名稱, 參數列表)
- 方法反射的操作 method.invoke(對象, 參數列表)
public class MethodDemo1 {
public static void main(String[] args) {
// 要獲取到方法 首先要得到類的類類型
A a = new A();
Class c = a.getClass();
// 獲取什麼樣的方法
// print(int,int)
// getMethod 共有方法
// getDeclaredMethod 聲明的方法
try {
// ...參數類型
// Method method = c.getMethod("print", new Class[]{int.class, int.class});
Method m = c.getMethod("print", int.class, int.class);
a.print(1, 2);
try {
// 有返回值則獲取返回值 沒有則爲null
Object o = m.invoke(a, 1, 2);
System.out.println(o);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
try {
Method m = c.getMethod("print", String.class, String.class);
m.invoke(a, "hello", "java");
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
try {
Method m = c.getMethod("print");
m.invoke(a);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
class A {
public void print() {
System.out.println("Hello World");
}
public void print(int a, int b) {
System.out.println(a+b);
}
public void print(String a, String b) {
System.out.println(a.toUpperCase() + " " + b.toUpperCase());
}
}
通過反射來認識集合泛型的本質
集合泛型是設計來規定輸入的
public class ClassMethod4 {
public static void main(String[] args) {
ArrayList list = new ArrayList();
ArrayList<String> list1 = new ArrayList<>();
list1.add("hello");
// list1.add(1); // 當然會報錯 集合泛型規定了只能放入String
// 但是
Class c = list.getClass();
Class c1 = list1.getClass();
System.out.println(c == c1); // true
/*
* 反射的操作都是編譯之後的操作
* c1 == c2 說明編譯之後集合的泛型是去泛型化的
* Java中集合的泛型是防止錯誤輸入的 繞過編譯 泛型就不起作用了
* 我們可以用方法的泛型來繞過編譯
*/
try {
Method m = c.getMethod("add", Object.class);
m.invoke(list1, 100);
System.out.println(list1.size()); // 2
System.out.println(list1); // [hello, 100]
// 那麼這樣子在獲取數據的時候就造成了困難
// 我們很難用foreach和for來遍歷集合
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
ReflectonUtils
來看一個Reflection的工具類,裏面有一些最基本的用法
代碼出自JeeSite
獲取對象的DeclaredMethod
/**
* 循環向上轉型, 獲取對象的DeclaredMethod,並強制設置爲可訪問.
* 如向上轉型到Object仍無法找到, 返回null.
* 只匹配函數名。
*
* 用於方法需要被多次調用的情況. 先使用本函數先取得Method,然後調用Method.invoke(Object obj, Object... args)
*/
public static Method getAccessibleMethodByName(final Object obj, final String methodName) {
Validate.notNull(obj, "object can't be null");
Validate.notBlank(methodName, "methodName can't be blank");
for (Class<?> searchType = obj.getClass(); searchType != Object.class; searchType = searchType.getSuperclass()) {
Method[] methods = searchType.getDeclaredMethods();
for (Method method : methods) {
if (method.getName().equals(methodName)) {
makeAccessible(method);
return method;
}
}
}
return null;
}
/**
* 循環向上轉型, 獲取對象的DeclaredMethod,並強制設置爲可訪問.
* 如向上轉型到Object仍無法找到, 返回null.
* 只匹配函數名。
*
* 用於方法需要被多次調用的情況. 先使用本函數先取得Method,然後調用Method.invoke(Object obj, Object... args)
*/
public static Method getAccessibleMethodByName(final Object obj, final String methodName) {
Validate.notNull(obj, "object can't be null");
Validate.notBlank(methodName, "methodName can't be blank");
for (Class<?> searchType = obj.getClass(); searchType != Object.class; searchType = searchType.getSuperclass()) {
Method[] methods = searchType.getDeclaredMethods();
for (Method method : methods) {
if (method.getName().equals(methodName)) {
makeAccessible(method);
return method;
}
}
}
return null;
}
其中的Validate出自org.apache.commons.lang3,方法的意思很好理解,getMethods和getDeclaredMethod一個只能獲取共有方法,一個只能是自己聲明的。那麼我們通過getSuperclass獲取父類,繼續調用父類的getDeclaredMethod來找到方法,並最後返回method。
其中的makeAccessible
/**
* 改變private/protected的方法爲public,儘量不調用實際改動的語句,避免JDK的SecurityManager抱怨。
*/
public static void makeAccessible(Method method) {
if ((!Modifier.isPublic(method.getModifiers()) || !Modifier.isPublic(method.getDeclaringClass().getModifiers()))
&& !method.isAccessible()) {
method.setAccessible(true);
}
}
對應的調用方式
/**
* 直接調用對象方法, 無視private/protected修飾符.
* 用於一次性調用的情況,否則應使用getAccessibleMethod()函數獲得Method後反覆調用.
* 同時匹配方法名+參數類型,
*/
public static Object invokeMethod(final Object obj, final String methodName, final Class<?>[] parameterTypes, final Object[] args) {
Method method = getAccessibleMethod(obj, methodName, parameterTypes);
if (method == null) {
throw new IllegalArgumentException("Could not find method [" + methodName + "] on target [" + obj + "]");
}
try {
return method.invoke(obj, args);
} catch (Exception e) {
throw convertReflectionExceptionToUnchecked(e);
}
}
/**
* 直接調用對象方法, 無視private/protected修飾符,
* 用於一次性調用的情況,否則應使用getAccessibleMethodByName()函數獲得Method後反覆調用.
* 只匹配函數名,如果有多個同名函數調用第一個。
*/
public static Object invokeMethodByName(final Object obj, final String methodName, final Object[] args){
Method method = getAccessibleMethodByName(obj, methodName);
if (method == null) {
throw new IllegalArgumentException("Could not find method [" + methodName + "] on target [" + obj + "]");
}
try {
return method.invoke(obj, args);
} catch (Exception e) {
throw convertReflectionExceptionToUnchecked(e);
}
}
獲取對象的DeclaredFeild
/**
* 循環向上轉型, 獲取對象的DeclaredField, 並強制設置爲可訪問.
*
* 如向上轉型到Object仍無法找到, 返回null.
*/
public static Field getAccessibleField(final Object obj, final String fieldName) {
Validate.notNull(obj, "object can't be null");
Validate.notBlank(fieldName, "fieldName can't be blank");
for (Class<?> superClass = obj.getClass(); superClass != Object.class; superClass=superClass.getSuperclass()) {
try {
Field field = superClass.getDeclaredField(fieldName);
makeAccessible(field);
return field;
} catch (NoSuchFieldException e) {
// Field不在當前類定義,繼續向上轉型
continue;
}
}
return null;
}
/**
* 改變private/protected的成員變量爲public,儘量不調用實際改動的語句,避免JDK的SecurityManager抱怨。
*/
public static void makeAccessible(Field field) {
if ((!Modifier.isPublic(field.getModifiers()) || !Modifier.isPublic(field.getDeclaringClass().getModifiers()) || Modifier
.isFinal(field.getModifiers())) && !field.isAccessible()) {
field.setAccessible(true);
}
}
對應的讀取對象屬性值的方法
/**
* 直接讀取對象屬性值, 無視private/protected修飾符, 不經過getter函數.
*/
public static Object getFieldValue(final Object obj, final String fieldName) {
Field field = getAccessibleField(obj, fieldName);
if (field == null) {
throw new IllegalArgumentException("Could not find field [" + fieldName + "] on target [" + obj + "]");
}
Object result = null;
try {
result = field.get(obj);
} catch (IllegalAccessException e) {
logger.error("不可能拋出的異常{}", e.getMessage());
}
return result;
}
/**
* 直接設置對象屬性值, 無視private/protected修飾符, 不經過setter函數.
*/
public static void setFieldValue(final Object obj, final String fieldName, final Object value) {
Field field = getAccessibleField(obj, fieldName);
if (field == null) {
throw new IllegalArgumentException("Could not find field [" + fieldName + "] on target [" + obj + "]");
}
try {
field.set(obj, value);
} catch (IllegalAccessException e) {
logger.error("不可能拋出的異常:{}", e.getMessage());
}
}