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阅读 200,045评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,114评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,120评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,902评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,828评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,132评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,590评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,258评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,408评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,335评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,385评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,068评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,660评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,747评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,967评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,406评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,970评论 2 341

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,566评论 18 139
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,279评论 25 707
  • 有人说:“人这一辈子啊,难得糊涂;又有人说:“最重要的是难得清醒。 小时候爱吃冰棍,爱吃零食;因为父母说这些是...
    萌里花阅读 210评论 0 2
  • 再一次 想起你的感觉 就像一个饿了许久没有进食空荡荡的胃 烧灼的疼 莫名焦躁 只能拼命的做事 转移自己的注意力 却...
    晴歌晴歌阅读 193评论 1 0
  • 自己给自己双手服务,自己给自己内心抚平,尽量通过这些活动,发泄自己的不良情绪,不让不良情绪左右我的心灵。
    用爱感动世界阅读 218评论 0 0