最近由於學習編寫 Android應用程式 所以再次學習 Java
發現一些功能不能直接使用,而是需要使用 映射(Reflection) 來執行
在下因此尋找 映射 資料
發現一些功能不能直接使用,而是需要使用 映射(Reflection) 來執行
在下因此尋找 映射 資料
Java 的一般情況下,當 類別(Class) 或 方法(Method) 或 屬性(Field) 以 私有(private) 的情況下宣告
其他類別都不能存取該資料
但當使用 (映射)Reflection 時,即使使用 私有 ,仍能被存取
其他類別都不能存取該資料
但當使用 (映射)Reflection 時,即使使用 私有 ,仍能被存取
獲取私有靜態屬性
public class MyClass {
private static boolean MY_BOOLEAN = true;
} 靜態(static) 屬性屬於 類別 ,能以 Class.Field 的方式存取,但設定為 私有 時便無法讓其他 類別 存取
public class Test {
public static void main(String[] args) throws Exception {
System.out.println(MyClass.MY_BOOLEAN);
}
}測試靜態屬性
編譯結果:
Test.java:3: error: MY_BOOLEAN has private access in MyClass System.out.println(MyClass.MY_BOOLEAN); ^ 1 error
由於 MY_BOOLEAN 為 私有,因此 MY_BOOLEAN 只能在 MyClass 中使用,導致編譯出錯
但如果使用 映射 時,便可以存取 私有屬性:
import java.lang.reflect.Field;
public class Test {
public static void main(String[] args) throws Exception {
Class clazz = MyClass.class;
Field field = clazz.getDeclaredField("MY_BOOLEAN");
field.setAccessible(true);
System.out.println(field.get(null));
}
} field.setAccessible(true) 令 私有屬性 能被存取
再使用 field.get() 便能獲取 私有屬性 的資料
再使用 field.get() 便能獲取 私有屬性 的資料
true
不過 field.get() 只會傳回 Object類別 ,而輸出內容只會將物件的 toString 顯示,並不能確定型態
例如:
例如:
public class MyClass {
private static boolean PRIMITIVE_BOOLEAN = true;
private static Boolean CLASS_BOOLEAN = true;
private static String CLASS_String = "true";
}import java.lang.reflect.Field;
public class Test {
public static void main(String[] args) throws Exception {
Class clazz = MyClass.class;
Field[] fields = {
clazz.getDeclaredField("PRIMITIVE_BOOLEAN"),
clazz.getDeclaredField("CLASS_BOOLEAN"),
clazz.getDeclaredField("CLASS_String"),
};
for (Field field : fields) {
field.setAccessible(true);
System.out.println(field.get(null));
}
}
}執行結果:
true true true
所有資料輸出都只會顯示 true ,不能確認傳回是 原始boolean 或 Boolean類別 或 String類別
因此還需要使用 field.getType().getName() 的確定型態
因此還需要使用 field.getType().getName() 的確定型態
import java.lang.reflect.Field;
public class Test {
public static void main(String[] args) throws Exception {
Class clazz = MyClass.class;
Field[] fields = {
clazz.getDeclaredField("PRIMITIVE_BOOLEAN"),
clazz.getDeclaredField("CLASS_BOOLEAN"),
clazz.getDeclaredField("CLASS_String"),
};
for (Field field : fields) {
field.setAccessible(true);
System.out.println(field.getType().getName());
}
}
}執行結果:
boolean java.lang.Boolean java.lang.String
確認類型後,便可以 強行變更型態 來執行其他操作
既然能夠獲取 私有屬性 ,因此 封裝(<default>) 及 保護(protected) 的資料亦能夠相同方法獲取
因此不重覆測試
因此不重覆測試
更改私有靜態屬性
使用 映射 除了能夠獲取 屬性,還可以使用 field.set() 來修改 屬性
import java.lang.reflect.Field;
public class Test {
public static void main(String[] args) throws Exception {
Class clazz = MyClass.class;
Field field = clazz.getDeclaredField("PRIMITIVE_BOOLEAN");
field.setAccessible(true);
System.out.println(field.get(null));
field.set(null, false);
System.out.println(field.get(null));
}
}執行結果:
true false
獲取及更改物件私有屬性
public class MyClass {
private boolean myBoolean = true;
}沒有 static 的屬性不再是靜態屬性,不能直接以類別方式存取
而是需要先建立該類別的物件,再在物件中存取
而是需要先建立該類別的物件,再在物件中存取
import java.lang.reflect.Field;
public class Test {
public static void main(String[] args) throws Exception {
MyClass myClass = new MyClass();
Class clazz = myClass.getClass();
Field field = clazz.getDeclaredField("myBoolean");
field.setAccessible(true);
System.out.println(field.get(myClass));
field.set(myClass, false);
System.out.println(field.get(myClass));
}
}使用 field.get() 時,需要指定物件參數,才能存取該物件對應的屬性
使用私有靜態功能
public class MyClass {
private static void myMethod() {
System.out.println("I am private static method.");
}
}與先前的例子相同,私有方法 不能被其他類別存取
import java.lang.reflect.Method;
public class Test {
public static void main(String[] args) throws Exception {
Class clazz = MyClass.class;
Method method = clazz.getDeclaredMethod("myMethod");
method.setAccessible(true);
method.invoke(null);
}
}與 field.get() 相似,不過改為使用 method.invoke()
Note: Test.java uses unchecked or unsafe operations. Note: Recompile with -Xlint:unchecked for details.
編譯時,由於 clazz.getDeclaredMethod() 有機會出現不安全的操作,因此會出現提示,但仍然能夠編譯
如果想避免編譯時出現提示,可以將 Test類別 修改成:
如果想避免編譯時出現提示,可以將 Test類別 修改成:
import java.lang.reflect.Method;
public class Test {
public static void main(String[] args) throws Exception {
Class clazz = MyClass.class;
@SuppressWarnings("unchecked")
Method method = clazz.getDeclaredMethod("myMethod");
method.setAccessible(true);
method.invoke(null);
}
}執行結果:
I am private static method.
由於 method.invoke() 不會區分 void 或 傳回資料
public class MyClass {
private static void voidMethod() {
return;
}
private static String nullMethod() {
return null;
}
private static String stringMethod() {
return "null";
}
}import java.lang.reflect.Method;
public class Test {
public static void main(String[] args) throws Exception {
Class clazz = MyClass.class;
@SuppressWarnings("unchecked")
Method[] methods = {
clazz.getDeclaredMethod("voidMethod"),
clazz.getDeclaredMethod("nullMethod"),
clazz.getDeclaredMethod("stringMethod"),
};
for (Method method : methods) {
method.setAccessible(true);
System.out.println(method.getReturnType().getName());
System.out.println(method.invoke(null));
}
}
}執行結果:
void null java.lang.String null java.lang.String null
使用 method.invoke() 時,即使功能是 void 仍會傳回 null 資料
與屬性相似,使用 method.getReturnType().getName() 來確認傳回類型
與屬性相似,使用 method.getReturnType().getName() 來確認傳回類型
使用私有物件功能
public class MyClass {
private void objectMethod() {
System.out.println("I am private object method.");
}
}import java.lang.reflect.Method;
public class Test {
public static void main(String[] args) throws Exception {
MyClass myClass = new MyClass();
Class<MyClass> clazz = myClass.getClass();
@SuppressWarnings("unchecked")
Method method = clazz.getDeclaredMethod("objectMethod");
method.setAccessible(true);
method.invoke(myClass);
}
}以 映射 使用物件的功能 與獲取物件的資料相似
使用 method.invoke() 時,同樣需要指定物件參數,才能使用該物件對應的方法
使用 method.invoke() 時,同樣需要指定物件參數,才能使用該物件對應的方法
使用附有參數的功能
public class MyClass {
private static int square(int value) {
return value * value;
}
}import java.lang.reflect.Method;
public class Test {
public static void main(String[] args) throws Exception {
Class clazz = MyClass.class;
@SuppressWarnings("unchecked")
Method method = clazz.getDeclaredMethod("square", int.class);
method.setAccessible(true);
System.out.println(method.invoke(null, 16));
}
}使用 映射 時,由於 方法 允許 重載(Overload)
因此在 clazz.getDeclaredMethod() 除了傳入功能名稱的參數外,可以因應參數的類型(或以陣列)順序傳入以使用對應重載的 方法
最後在 method.invoke() 除了第一個參數傳入 null 或物件外,亦需要將對應的參數類型的資料(或以陣列)順序傳入
因此在 clazz.getDeclaredMethod() 除了傳入功能名稱的參數外,可以因應參數的類型(或以陣列)順序傳入以使用對應重載的 方法
最後在 method.invoke() 除了第一個參數傳入 null 或物件外,亦需要將對應的參數類型的資料(或以陣列)順序傳入
使用私有建構子建立物件
如果類別只有私有 建構子(Constructor) ,又沒有靜態方法或靜態屬性獲取該類別的物件,即是該類別是無法以正規方法建立物件
但透過 映射 就能夠將建立該類別的物件
但透過 映射 就能夠將建立該類別的物件
public class MyClass {
private MyClass(int edge) {
System.out.println(edge * 4);
}
}import java.lang.reflect.Constructor;
public class Test {
public static void main(String[] args) throws Exception {
Class clazz = MyClass.class;
Constructor<MyClass> constructor = clazz.getDeclaredConstructor(int.class);
constructor.setAccessible(true);
MyClass myClass = constructor.newInstance(8);
}
} clazz.getDeclaredConstructor() 的使用方法與 clazz.getDeclaredMethod() 相似,只是不需要傳入名稱
如果需要附加參數,同樣將參數的類型順序傳入,再在 constructor.newInstance() 順序傳入對應資料即可
如果需要附加參數,同樣將參數的類型順序傳入,再在 constructor.newInstance() 順序傳入對應資料即可
使用私有靜態內部類別的方法
內部類別(Inner Class) 與一般類別相同,只是內部類別存在於一個類別中
而且內部類別允許以私有方式製作
而且內部類別允許以私有方式製作
public class MyClass {
private static class MySubClass {
private MySubClass() {
}
private void mySubMethod() {
System.out.println(this.getClass().getName());
}
}
}import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
public class Test {
public static void main(String[] args) throws Exception {
Class clazz = Class.forName("MyClass$MySubClass");
Constructor constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true);
Object innerObject = constructor.newInstance();
Class innerClass = innerObject.getClass();
Method innerMethod = innerClass.getDeclaredMethod("mySubMethod");
innerMethod.setAccessible(true);
innerMethod.invoke(innerObject);
}
}執行結果:
MyClass$MySubClass
由於 私有內部類別 無法被匯入,因此物件只能以 Object類別 建立
但 Object類別 不會擁有 內部類別的 屬性 或 方法
而且由於無法強制轉變類型,即使是 公有(public) 的 屬性 或 方法 都不能直接使用
整過操作程式,都需要使用 映射 來使用方法或獲取屬性
但 Object類別 不會擁有 內部類別的 屬性 或 方法
而且由於無法強制轉變類型,即使是 公有(public) 的 屬性 或 方法 都不能直接使用
整過操作程式,都需要使用 映射 來使用方法或獲取屬性
另外,內部類別 的名稱使用 $(錢符號(dollar)) 連接,而非 .(點(Period)) 來連接
使用私有內部類別物件的方法
一般情況很少會設計私有內部類別物件,但技術上是允許這種設計
public class MyClass {
private MyClass() {
}
private class MySubClass {
private MySubClass() {
}
private void mySubMethod() {
System.out.println(this.getClass().getName());
}
}
}import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
public class Test {
public static void main(String[] args) throws Exception {
Class<MyClass> outerClass = MyClass.class;
Constructor<MyClass> outerConstructor = outerClass.getDeclaredConstructor();
outerConstructor.setAccessible(true);
MyClass myClass = outerConstructor.newInstance();
Class innerClass = Class.forName("MyClass$MySubClass");
Constructor innerConstructor = innerClass.getDeclaredConstructor(MyClass.class);
innerConstructor.setAccessible(true);
Object innerObject = innerConstructor.newInstance(myClass);
Method innerMethod = innerClass.getDeclaredMethod("mySubMethod");
innerMethod.setAccessible(true);
innerMethod.invoke(innerObject);
}
}執行結果:
MyClass$MySubClass
如果將例子中所有 private 改成 public
MyClass myClass = new MyClass(); MyClass.MySubClass mySubClass = myClass.new MySubClass(); mySubClass.mySubMethod();
語法上已經有點麻煩,如果必須以 映寫 方法建立物入更加繁複的步驟
更改物件常數屬性
宣告以 常數(final) 的變數,其地址只能被指派資料一次
class MyClass {
public final int VALUE = 0;
}public class Test {
public static void main(String[] args) throws Exception {
MyClass myClass = new MyClass();
myClass.VALUE = 1;
}
}執行結果:
Test.java:4: error: cannot assign a value to final variable VALUE myClass.VALUE = 1; ^ 1 error
當地址被重覆指派時,編譯時會出現錯誤
但使用 映射 仍能透過 映射 更改
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
public class Test {
public static void main(String[] args) throws Exception {
MyClass myClass = new MyClass();
Field field = clazz.getDeclaredField("VALUE");
field.setAccessible(true);
System.out.println(field.get(myClass));
field.set(myClass, 1);
System.out.println(field.get(myClass));
}
}執行結果:
0 1
更改靜態常數屬性
使用 映射 更改靜態常數屬性則比較麻煩
public class MyClass {
public static final int VALUE = 0;
}import java.lang.reflect.Field;
public class Test {
public static void main(String[] args) throws Exception {
Class clazz = MyClass.class;
Field field = clazz.getDeclaredField("VALUE");
field.setAccessible(true);
System.out.println(field.get(null));
field.set(null, 1);
System.out.println(field.get(null));
}
}執行結果:
0 Exception in thread "main" java.lang.IllegalAccessException: Can not set static final int field MyClass.VALUE to java.lang.Integer at java.base/jdk.internal.reflect.UnsafeFieldAccessorImpl.throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:76) at java.base/jdk.internal.reflect.UnsafeFieldAccessorImpl.throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:80) at java.base/jdk.internal.reflect.UnsafeQualifiedStaticIntegerFieldAccessorImpl.set(UnsafeQualifiedStaticIntegerFieldAccessorImpl.java:77) at java.base/java.lang.reflect.Field.set(Field.java:780) at Test.main(Test.java:8)
使用類似更改物件常數屬性的方法更改靜態常數屬性會出現錯誤
編譯時顯示 不能更改靜態常數屬性
編譯時顯示 不能更改靜態常數屬性
要更改類別靜態屬性需要先將更改 屬性 的 修飾(Modifier) ,將 常數位元取消 才能更改
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
public class Test {
public static void main(String[] args) throws Exception {
Class clazz = MyClass.class;
Field field = clazz.getDeclaredField("VALUE");
field.setAccessible(true);
Class modifiersClass = field.getClass();
Field modifiersField = modifiersClass.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
// remove final modifier
modifiersField.set(field, field.getModifiers() & ~Modifier.FINAL);
System.out.println(field.get(null));
field.set(null, 1);
// gain final modifier
modifiersField.set(field, field.getModifiers() & ~Modifier.FINAL);
System.out.println(field.get(null));
}
}執行結果:
WARNING: An illegal reflective access operation has occurred WARNING: Illegal reflective access by Test (file:/path/of/file/) to field java.lang.reflect.Field.modifiers WARNING: Please consider reporting this to the maintainers of Test WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations WARNING: All illegal access operations will be denied in a future release 0 1
但即是能夠修改,執行程式時,會出現警告資訊
補充資料
在測試各種以 映射 更改存取資料的操作中,會發現當使用 私有方法 會出現 不安全提示 及 更改 靜態常數屬性 會出現 警告
由於 私有方法 有機會是 讓內部其他 方法 使用,當中如果沒有檢查資料,便會出現非預期結果
public class MyClass {
private static int privateDivision(int dividend, int divider) {
return dividend / divider;
}
public static void publicDivision(int dividend, int divider) {
if (divider == 0) {
System.out.println("Cannot divided by 0");
} else {
System.out.println(MyClass.privateDivision(dividend, divider));
}
}
}public class Test {
public static void main(String[] args) throws Exception {
MyClass.publicDivision(1, 0);
MyClass.publicDivision(1, 1);
}
}執行結果:
Cannot divided by 0 1
由於 publicDivision() 在運算有檢查傳入的參數的況狀來避免執行時出現錯誤
import java.lang.reflect.Method;
public class Test {
public static void main(String[] args) throws Exception {
Class clazz = MyClass.class;
Method method = clazz.getDeclaredMethod("privateDivision", int.class, int.class);
method.setAccessible(true);
System.out.println(method.invoke(null, 1, 0));
System.out.println(method.invoke(null, 1, 1));
}
}執行結果:
Exception in thread "main" java.lang.reflect.InvocationTargetException at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:119) at java.base/java.lang.reflect.Method.invoke(Method.java:578) at Test.main(Test.java:7) Caused by: java.lang.ArithmeticException: / by zero at MyClass.privateDivision(Test.java:7) at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) ... 2 more
由於 privateDivision() 在運算沒有檢查傳入的參數的況狀,執行時會出現異常情況
更改其他類別常數屬性,是非常危險
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
public class Test {
public static void main(String[] args) throws Exception {
Class clazz = Boolean.class;
Field field = clazz.getDeclaredField("TRUE");
field.setAccessible(true);
Class modifiersClass = field.getClass();
Field modifiersField = modifiersClass.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.set(field, field.getModifiers() & ~Modifier.FINAL);
System.out.println(Boolean.TRUE);
field.set(null, false);
System.out.println(Boolean.TRUE);
modifiersField.set(field, field.getModifiers() & ~Modifier.FINAL);
}
}執行結果:
WARNING: An illegal reflective access operation has occurred WARNING: Illegal reflective access by Test (file:/path/of/file/) to field java.lang.reflect.Field.modifiers WARNING: Please consider reporting this to the maintainers of Test WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations WARNING: All illegal access operations will be denied in a future release true false
如同上述例子,將 Boolean類別 的 TRUE靜態常數屬性 更改為 false
當其他類別需要使用 Boolean.TRUE 時,便會出現嚴重問題
當其他類別需要使用 Boolean.TRUE 時,便會出現嚴重問題
不過在 JDK 12+ 基於安全考慮,已經禁止存取 Field類別 的 modifiers屬性
但仍然能夠使用 sun.misc.Unsafe 類別 來更改 靜態屬性 ,不過比之前的方法更加複雜
但仍然能夠使用 sun.misc.Unsafe 類別 來更改 靜態屬性 ,不過比之前的方法更加複雜
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class Test {
public static void main(String[] args) throws Exception {
Class booleanClass = Boolean.class;
Field fieldClass = booleanClass.getDeclaredField("TRUE");
Class unsafeClass = Class.forName("sun.misc.Unsafe");
Field unsafeField = unsafeClass.getDeclaredField("theUnsafe");
unsafeField.setAccessible(true);
Object unsafeObject = unsafeField.get(null);
Method unsafeMethodBase = unsafeClass.getDeclaredMethod("staticFieldBase", fieldClass.getClass());
Object unsafeMethodBaseObject = unsafeMethodBase.invoke(unsafeObject, fieldClass);
Method unsafeMethodOffset = unsafeClass.getDeclaredMethod("staticFieldOffset", fieldClass.getClass());
Object unsafeMethodOffsetObject = unsafeMethodOffset.invoke(unsafeObject, fieldClass);
Method unsafeMethodPutObject = unsafeClass.getDeclaredMethod("putObject", Object.class, long.class, Object.class);
System.out.println(Boolean.TRUE);
unsafeMethodPutObject.invoke(unsafeObject, unsafeMethodBaseObject, unsafeMethodOffsetObject, false);
System.out.println(Boolean.TRUE);
}
}sun.misc.Unsafe 在 JDK 7 開始出現,但 不屬於標準 JDK ,因此無法找到 sun.misc.Unsafe 的 API
sun.misc.Unsafe 能存取記憶體及其他低階操作
由於 sun.misc.Unsafe 能夠繞過 JVM 的安全設定 ,如果使用不當,會影響 JVM 的穩定性或產生不安全操作
不建議在一般開發中胡亂使用
由於 sun.misc.Unsafe 能夠繞過 JVM 的安全設定 ,如果使用不當,會影響 JVM 的穩定性或產生不安全操作
不建議在一般開發中胡亂使用
在下為了將來可以方便使用這些操作,而編寫一些簡化操作的方法
public static<T> T createObject(Class<T> clazz, Class[] parameterClasses, Object[] parameterValues) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {
Constructor<T> constructor = clazz.getDeclaredConstructor(parameterClasses);
constructor.setAccessible(true);
return constructor.newInstance(parameterValues);
}public static Object invokeMethod(Class clazz, String methodName, Class[] parameterClasses, Object[] parameterValues, Object instance) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Method method = clazz.getDeclaredMethod(methodName, parameterClasses);
method.setAccessible(true);
return method.invoke(instance, parameterValues);
}public static Object getValue(Class clazz, String fieldName, Object instance) throws NoSuchFieldException, IllegalAccessException {
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
return field.get(instance);
}public static void setValue(Class clazz, String fieldName, Object value, Object instance) throws NoSuchFieldException, IllegalAccessException {
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
int modifiers = field.getModifiers();
if ((modifiers & Modifier.FINAL) > 0) {
Class modifiersClass = field.getClass();
Field modifiersField = modifiersClass.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.set(field, modifiers & ~Modifier.FINAL);
field.set(instance, value);
modifiersField.set(field, modifiers & ~Modifier.FINAL);
} else {
field.set(instance, value);
}
}public static void setStaticValue(Class clazz, String fieldName, Object value) throws NoSuchFieldException, ClassNotFoundException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
Field field = clazz.getDeclaredField(fieldName);
Class unsafeClass = Class.forName("sun.misc.Unsafe");
Field unsafeField = unsafeClass.getDeclaredField("theUnsafe");
unsafeField.setAccessible(true);
Object unsafeObject = unsafeField.get(null);
Method unsafeMethodBase = unsafeClass.getDeclaredMethod("staticFieldBase", field.getClass());
Object unsafeMethodBaseObject = unsafeMethodBase.invoke(unsafeObject, field);
Method unsafeMethodOffset = unsafeClass.getDeclaredMethod("staticFieldOffset", field.getClass());
Object unsafeMethodOffsetObject = unsafeMethodOffset.invoke(unsafeObject, field);
Method unsafeMethodPutObject = unsafeClass.getDeclaredMethod("putObject", Object.class, long.class, Object.class);
unsafeMethodPutObject.invoke(unsafeObject, unsafeMethodBaseObject, unsafeMethodOffsetObject, false);
} 總結
編寫 Android 程式時,偶然發現一些功能,不是 公有方法 或 公有屬性
但這些功能在下希望能加入到自己編寫的 Android應用程式 中,因此尋找資料發現可以透過 映射 達至效果
但這些功能在下希望能加入到自己編寫的 Android應用程式 中,因此尋找資料發現可以透過 映射 達至效果