本文為《Java 編程思想》14.9節的讀書筆記,文章內容僅限交流使用!
我們先看看使用接口時方法的訪問權限(就這一個小標題,真沒地方加小標題啊!)
使用interface關鍵字定義的接口主要是為了實現代碼間的隔離,用以避免 使用接口的代碼 與 實現類 之間的耦合。通常一個實現了某個接口的類中擁有自己的非來自于接口的方法,向上轉型為接口的時候,就無法通過轉型后的接口對象來調用子類自己另添加的方法。
這個是完全合理的,但是可以使用類型信息繞過這種限制,可以對實現類中的非接口方法進行調用。
首先定義一個接口A:
package com.henevalf.learn.typeinfo
public interface A {
void f();
}
然后用類B實現接口A,在后面的InterfaceViolation中我們可以看到如何繞過限制 :
package com.henvealf.learn.typeinfo;
class B implements A {
@Override
public void f() {
}
public void g(){
}
}
public class InterfaceViolation {
private A a;
public InterfaceViolation(A a) {
this.a = a;
}
public void invoke() {
a.f();
//a.g();
System.out.println(a.getClass().getName());
// 看見沒!先檢查一下類型,然后轉型調用。。。。滋滋滋,真不要臉。
if(a instanceof B) {
B b = (B)a;
b.g();
}
}
public static void main(String[] args){
InterfaceViolation inv = new InterfaceViolation(new B());
}
}
嗯,沒錯,在invoke()方法里,就是強制類型轉換,就可以使用在接口中未定義的方法g(),在本例中先用 instanceof 檢測了一下類型是否可轉。 想必也都使用過這種方式。這樣并沒有什么不妥,可以正常運行,但是他違背了我們當初使用接口的本意,類 InterfaceViolation 與類 B 無意之間就增加耦合。
如果你有難以克制的強迫癥,就是不希望使用你的類的其他程序員這樣做。那么有兩種解決方法:
- 到他面前義正言辭的告訴他,不許你這樣用。然而誰理你!!
- 自己在代碼中進行控制。
怎么控制那?最簡單的方式就是對實現類使用包訪問權限。意思是將你的實現類放在一個包中,并設置實現類只能在包中才能被訪問到,使用你類的程序員就找不到你的實現類的存在,就無法完成轉型,看代碼:
package com.henvealf.learn.typeinfo.packageaccess;
import com.henvealf.learn.typeinfo.A;
/**
* Created by Henvealf on 2016/9/10.
*/
class C implements A {
@Override
public void f() {
System.out.println("public C.f()");
}
public void g() {
System.out.println("public C.g()");
}
void u() {
System.out.println("package C.u()");
}
protected void v() {
System.out.println("protected C.v()");
}
public void w() {
System.out.println("private C.w()");
}
}
public class HiddenC {
public static A makeA(){
return new C();
}
}
注意包名,現在A的實現類C是在一個獨立的包中,在這個包里面,唯一對外開放的public既是HiddenC,它有一個靜態方法,返回C的一個對象,這樣的話你就無法在包的外部調用A以外的任何方法了,因為你無法在包的外部找到C的類型信息,就無法進行轉型:
package com.henvealf.learn.typeinfo;
import com.henvealf.learn.typeinfo.packageaccess.HiddenC;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
*
* Created by Henvealf on 2016/9/10.
*/
public class HiddenImplementation {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
A a = HiddenC.makeA();
a.f();
System.out.println(a.getClass().getName());
/*if(a instanceof C) { 編譯錯誤,因為找不到C
.....
C c = (C)a;
c.g();
}*/
//我的天,反射竟然可以讓你繼續調用個g()
callHiddenMethod(a,"g");
// 甚至私有方法都可以
callHiddenMethod(a,"u");
callHiddenMethod(a,"v");
callHiddenMethod(a,"w");
}
static void callHiddenMethod(Object a, String methodName) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
// 先獲得類型信息(Class對象),然后獲取其Method對象。
Method g = a.getClass().getDeclaredMethod(methodName);
// 就是這里,可以允許訪問私有方法。
g.setAccessible(true);
//調用
g.invoke(a);
}
}
可以發現,在包外我們無法找到類型C,無法進行相應的轉換。除此之外,可以看到竟然可以通過反射來調用對象C中的方法。甚至是私有方法,其原因就是在Method對象上調用了setAccessible(true),顧名思義就是設置訪問權限。
你可能會想,要想使用這種方式,就必須要獲得類C的方法列表,如果我們得到的只是類C編譯后的字節碼(.class文件),我們大可以使用javap來反編譯字節碼,以來的到方法列表。
反射除了能夠突破包訪問的權限,還能夠訪問到私有內部類,匿名類的所有方法。
當然除了方法,對于域(字段/屬性),也同樣如此,不過在域的訪問中有一個特殊的,就是final字段,它只能被讀取,不通過反射被再次賦值。
你可能會問,這樣反射不就無法無天了嗎!什么都能夠訪問到。而反射存在原因就是為了給程序員提供一個后門,能夠讓程序員解決一些難以解決的某些特定類型的問題(至于什么樣的問題我也不清楚)。如果沒有反射,這些問題將會難以或者不可能解決,所以說反射的好處是毋庸置疑的。
End...