日常敲碼中,如果想要在程式執行階段訪問某個類的所有資訊,並支援修改類的狀態或者行為的話,肯定會用到反射,而反射靠的就是Class類。Java的動態代理也用到了這個東西,所以瞭解其基本操作在苦逼的CRUD中會新增一絲絲樂趣(有點意思)。
首先來看看Class的操作有哪些?
public final class Class<T> {}
上述程式碼可知,Class是一個由final修飾的泛型類,所以並不能直接通過new Class()獲取其例項。那麼應該如何獲取呢?
//直接通過類的靜態變數來獲取
Class<Integer> intClass = Integer.class;
//通過例項變數的getClass方法
Integer integer = new Integer(0);
Class<? extends Integer> aClass = integer.getClass();
//通過Class.forName("類的全限定名")
Class<?> aClass1 = Class.forName("java.lang.Integer");
上述三種就是獲取某個Class的class例項的方式,需要注意的是,JVM只會載入一個Class例項,也就是說上述三種方式獲取到的class例項都是一樣的。
而在運用反射的時候,Class.forName是最常用的一種方式。而Class.forName底層會指向forName0這個本地方法
(1)name:類的全限定名
(2)initialize:是否初始化這個類
(3)loader:類載入器
(4)caller:呼叫Class.forName所在類的Class,比如A類程式碼塊裡有Class.forName,那麼caller就是A的class例項。
通過Class類可以獲取類的例項,構造方法,欄位,成員方法,介面等資訊。獲取之後可以通過API進行相應的操作。
接下來看一下獲取到class例項之後怎麼獲取當前類的例項以及構造方法。
上述兩種方式都是呼叫預設的無參構造進行例項化物件,那麼怎麼通過公共或私有的有參構造獲取例項呢?
//屬性
int a;
String b ;
//公共有參構造
public ClassSource(int a) {
this.a = a;
}
//私有有參構造
private ClassSource(String b) {
this.b = b;
}
public static void main(String[] args){
Class<?> aClass = Class.forName("com.liusy.lang.ClassSource");
//獲取public有參構造
Constructor<?> constructor = aClass.getConstructor(int.class);
ClassSource o1 = (ClassSource) constructor.newInstance(2);
System.out.println("屬性【a】的值為:"+ o1.a);
//獲取private有參構造
Constructor<?> constructor1 = aClass.getDeclaredConstructor(String.class);
//這個有貓膩
constructor1.setAccessible(true);
ClassSource o2 = (ClassSource) constructor1.newInstance("abc");
System.out.println("屬性【b】的值為:"+ o2.b);
}
上述程式碼執行的結果如下:
而獲取私有建構函式最重要的是要setAccessible(true)這個方法,點進去看一下,這個方法最後是給override欄位賦值的,可用於類的欄位、方法以及建構函式。
public void setAccessible(boolean flag) throws SecurityException {
//這個“安全管理器”,允許應用程式在進行某些不安全或敏感操作
//之前執行安全策略,如果不允許執行,則通過拋異常阻止操作。
SecurityManager sm = System.getSecurityManager();
if (sm != null) sm.checkPermission(ACCESS_PERMISSION);
setAccessible0(this, flag);
}
private static void setAccessible0(AccessibleObject obj,
boolean flag){
if (obj instanceof Constructor && flag == true) {
Constructor<?> c = (Constructor<?>)obj;
if (c.getDeclaringClass() == Class.class) {
//如果是Class.class,則拋異常
}
}
//最終是設定此屬性
obj.override = flag;
}
//訪問級別是否可以被覆蓋,可用於欄位,方法,建構函式
boolean override;
需要注意的是,在程式碼中用反射操作當前類私有欄位、私有方法或其他私有屬性時,並不需要setAccessible(true),例如在A類中用反射操作A類的私有屬性。只有在A類中操作其他類的私有屬性才需要設定setAccessible(true)。
不管是Class.newInstance還是Constructor.newInstance,底層呼叫的都是ConstructorAccessor(建構函式訪問器)介面的newInstance方法。
(1)Class.newInstance
//標識所有公共屬性
public static final int PUBLIC = 0;
//標識所有私有屬性
public static final int DECLARED = 1;
public T newInstance(){
//省略部分程式碼
// 檢查有沒有存在已載入過的構造器
if (cachedConstructor == null) {
//省略部分程式碼
try {
Class<?>[] empty = {};
//獲取預設私有無參構造
final Constructor<T> c = getConstructor0(empty, Member.DECLARED);
//省略部分程式碼
cachedConstructor = c;
} catch (NoSuchMethodException e) {
throw (InstantiationException)
new InstantiationException(getName()).initCause(e);
}
}
Constructor<T> tmpConstructor = cachedConstructor;
//省略部分程式碼
try {
//呼叫無參構造的newInstance方法
return tmpConstructor.newInstance((Object[])null);
} catch (InvocationTargetException e) {
//省略部分程式碼
return null;
}
}
private Constructor<T> getConstructor0(Class<?>[] parameterTypes,
int which)
{
//獲取建構函式,重點看這個方法,引數是false
Constructor<T>[] constructors = privateGetDeclaredConstructors((which == Member.PUBLIC));
//省略匹配建構函式引數的程式碼
}
private Constructor<T>[] privateGetDeclaredConstructors(boolean publicOnly) {
//檢查是否已被初始化,如果否,則進行安全檢查
checkInitted();
Constructor<T>[] res;
//反射的資料
ReflectionData<T> rd = reflectionData();
if (rd != null) {
res = publicOnly ? rd.publicConstructors : rd.declaredConstructors;
if (res != null) return res;
}
// 檢查是否是介面
if (isInterface()) {
//程式碼省略
} else {
//底層呼叫的是本地方法
res = getDeclaredConstructors0(publicOnly);
}
if (rd != null) {
if (publicOnly) {
rd.publicConstructors = res;
} else {
rd.declaredConstructors = res;
}
}
return res;
}
如上,可以看到Class.newInstance底層是呼叫無參構造的newInstance方法。
(2)Constructor.newInstance
public T newInstance(Object ... initargs){
//override就是是否可覆蓋級別訪問
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, null, modifiers);
}
}
ConstructorAccessor ca = constructorAccessor; // read volatile
if (ca == null) {
ca = acquireConstructorAccessor();
}
@SuppressWarnings("unchecked")
T inst = (T) ca.newInstance(initargs);
return inst;
}
public interface ConstructorAccessor {
Object newInstance(Object[] var1) throws InstantiationException, IllegalArgumentException, InvocationTargetException;
}
可以看到,Class.newInstance呼叫的是無參構造的newInstance,而建構函式呼叫的是ConstructorAccessor介面類的newInstance,所以最終呼叫的都是ConstructorAccessor介面類的newInstance。
最終呼叫的是NativeConstructorAccessorImpl的newInstance方法。
【注】以下僅演示操作方法,原始碼不貼,防止篇幅太長。
獲取例項以及構造方法之後,來看一下如何訪問,修改類欄位資訊。
//public屬性
public int a;
//private屬性
private String b;
public static void main(String[] args) throws Exception {
Class<?> aClass = Class.forName("com.liusy.lang.ClassSource");
//獲取例項
Object instance = aClass.newInstance();
//根據欄位名獲取公共屬性並進行賦值
Field a = aClass.getField("a");
a.set(instance,2);
System.out.println("a的值為:"+a.get(instance));
//根據欄位名獲取私有屬性並進行賦值
Field b = aClass.getDeclaredField("b");
b.setAccessible(true);
b.set(instance,"abc");
System.out.println("b的值為"+b.get(instance));
}
上述程式碼執行結果為:
接下來瞧一下如何利用class訪問類的成員方法:
public static void main(String[] args) throws Exception {
Class<?> aClass = Class.forName("com.liusy.lang.ClassSource");
//獲取例項
Object instance = aClass.newInstance();
//獲取公共方法並呼叫
Method sayPublicHello = aClass.getMethod("sayPublicHello");
sayPublicHello.invoke(instance);
//獲取私有方法並呼叫
Method sayPrivateHello = aClass.getDeclaredMethod("sayPrivateHello", int.class);
sayPrivateHello.setAccessible(true);
sayPrivateHello.invoke(instance,1);
}
public void sayPublicHello(){
System.out.println("sayPublicHello");
}
private void sayPrivateHello(int param){
System.out.println("sayPrivateHello,引數:"+param);
}
上述程式碼執行結果為:
還可以使用“==”判斷資料型別
Class<?> aClass = Class.forName("com.liusy.lang.ClassSource");
System.out.println(aClass == ClassSource.class);
之前遇到過用了很多反射的程式碼,看起來很噁心。但其實瞭解過後反而覺得反射是個很強大的東西,包括JDK動態代理,spring AOP,事務底層都是用的反射,研究之後有利於瞭解更多底層的知識。
=======================================================
我是Liusy,一個喜歡健身的程式設計師。
歡迎關注微信公眾號【Liusy01】,一起交流Java技術及健身,獲取更多幹貨。