Java - 打破訪問權限的方式

本文為《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 無意之間就增加耦合。

如果你有難以克制的強迫癥,就是不希望使用你的類的其他程序員這樣做。那么有兩種解決方法:

  1. 到他面前義正言辭的告訴他,不許你這樣用。然而誰理你!!
  2. 自己在代碼中進行控制。

怎么控制那?最簡單的方式就是對實現類使用包訪問權限。意思是將你的實現類放在一個包中,并設置實現類只能在包中才能被訪問到,使用你類的程序員就找不到你的實現類的存在,就無法完成轉型,看代碼:

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...

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,517評論 6 539
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,087評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,521評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,493評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,207評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,603評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,624評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,813評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,364評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,110評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,305評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,874評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,532評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,953評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,209評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,033評論 3 396
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,268評論 2 375

推薦閱讀更多精彩內容

  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,809評論 18 139
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,693評論 25 708
  • 有人說:“人這一輩子啊,難得糊涂;又有人說:“最重要的是難得清醒。 小時候愛吃冰棍,愛吃零食;因為父母說這些是...
    萌里花閱讀 221評論 0 2
  • 再一次 想起你的感覺 就像一個餓了許久沒有進食空蕩蕩的胃 燒灼的疼 莫名焦躁 只能拼命的做事 轉移自己的注意力 卻...
    晴歌晴歌閱讀 203評論 1 0
  • 自己給自己雙手服務,自己給自己內心撫平,盡量通過這些活動,發泄自己的不良情緒,不讓不良情緒左右我的心靈。
    用愛感動世界閱讀 251評論 0 0