本文為《Java 編程思想》14.9節(jié)的讀書筆記,文章內(nèi)容僅限交流使用!
我們先看看使用接口時(shí)方法的訪問權(quán)限(就這一個(gè)小標(biāo)題括堤,真沒地方加小標(biāo)題啊!)
使用interface關(guān)鍵字定義的接口主要是為了實(shí)現(xiàn)代碼間的隔離悄窃,用以避免 使用接口的代碼 與 實(shí)現(xiàn)類 之間的耦合讥电。通常一個(gè)實(shí)現(xiàn)了某個(gè)接口的類中擁有自己的非來自于接口的方法,向上轉(zhuǎn)型為接口的時(shí)候轧抗,就無法通過轉(zhuǎn)型后的接口對(duì)象來調(diào)用子類自己另添加的方法恩敌。
這個(gè)是完全合理的,但是可以使用類型信息繞過這種限制横媚,可以對(duì)實(shí)現(xiàn)類中的非接口方法進(jìn)行調(diào)用纠炮。
首先定義一個(gè)接口A:
package com.henevalf.learn.typeinfo
public interface A {
void f();
}
然后用類B實(shí)現(xiàn)接口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());
// 看見沒!先檢查一下類型灯蝴,然后轉(zhuǎn)型調(diào)用抗碰。。绽乔。弧蝇。滋滋滋,真不要臉折砸。
if(a instanceof B) {
B b = (B)a;
b.g();
}
}
public static void main(String[] args){
InterfaceViolation inv = new InterfaceViolation(new B());
}
}
嗯看疗,沒錯(cuò),在invoke()方法里睦授,就是強(qiáng)制類型轉(zhuǎn)換两芳,就可以使用在接口中未定義的方法g(),在本例中先用 instanceof 檢測(cè)了一下類型是否可轉(zhuǎn)去枷。 想必也都使用過這種方式怖辆。這樣并沒有什么不妥,可以正常運(yùn)行删顶,但是他違背了我們當(dāng)初使用接口的本意竖螃,類 InterfaceViolation 與類 B 無意之間就增加耦合。
如果你有難以克制的強(qiáng)迫癥逗余,就是不希望使用你的類的其他程序員這樣做特咆。那么有兩種解決方法:
- 到他面前義正言辭的告訴他,不許你這樣用录粱。然而誰理你D甯瘛!
- 自己在代碼中進(jìn)行控制啥繁。
怎么控制那菜职?最簡(jiǎn)單的方式就是對(duì)實(shí)現(xiàn)類使用包訪問權(quán)限。意思是將你的實(shí)現(xiàn)類放在一個(gè)包中旗闽,并設(shè)置實(shí)現(xiàn)類只能在包中才能被訪問到酬核,使用你類的程序員就找不到你的實(shí)現(xiàn)類的存在蜜另,就無法完成轉(zhuǎn)型,看代碼:
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();
}
}
注意包名愁茁,現(xiàn)在A的實(shí)現(xiàn)類C是在一個(gè)獨(dú)立的包中蚕钦,在這個(gè)包里面亭病,唯一對(duì)外開放的public既是HiddenC,它有一個(gè)靜態(tài)方法鹅很,返回C的一個(gè)對(duì)象,這樣的話你就無法在包的外部調(diào)用A以外的任何方法了罪帖,因?yàn)槟銦o法在包的外部找到C的類型信息促煮,就無法進(jìn)行轉(zhuǎn)型:
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) { 編譯錯(cuò)誤,因?yàn)檎也坏紺
.....
C c = (C)a;
c.g();
}*/
//我的天整袁,反射竟然可以讓你繼續(xù)調(diào)用個(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對(duì)象)菠齿,然后獲取其Method對(duì)象。
Method g = a.getClass().getDeclaredMethod(methodName);
// 就是這里坐昙,可以允許訪問私有方法绳匀。
g.setAccessible(true);
//調(diào)用
g.invoke(a);
}
}
可以發(fā)現(xiàn),在包外我們無法找到類型C炸客,無法進(jìn)行相應(yīng)的轉(zhuǎn)換疾棵。除此之外,可以看到竟然可以通過反射來調(diào)用對(duì)象C中的方法痹仙。甚至是私有方法是尔,其原因就是在Method對(duì)象上調(diào)用了setAccessible(true),顧名思義就是設(shè)置訪問權(quán)限。
你可能會(huì)想开仰,要想使用這種方式拟枚,就必須要獲得類C的方法列表,如果我們得到的只是類C編譯后的字節(jié)碼(.class文件)众弓,我們大可以使用javap來反編譯字節(jié)碼恩溅,以來的到方法列表。
反射除了能夠突破包訪問的權(quán)限谓娃,還能夠訪問到私有內(nèi)部類暴匠,匿名類的所有方法。
當(dāng)然除了方法傻粘,對(duì)于域(字段/屬性)每窖,也同樣如此,不過在域的訪問中有一個(gè)特殊的弦悉,就是final字段窒典,它只能被讀取,不通過反射被再次賦值稽莉。
你可能會(huì)問瀑志,這樣反射不就無法無天了嗎!什么都能夠訪問到。而反射存在原因就是為了給程序員提供一個(gè)后門劈猪,能夠讓程序員解決一些難以解決的某些特定類型的問題(至于什么樣的問題我也不清楚)昧甘。如果沒有反射,這些問題將會(huì)難以或者不可能解決战得,所以說反射的好處是毋庸置疑的充边。
End...