美文网首页Java学习笔记Java 杂谈
Java - 打破访问权限的方式

Java - 打破访问权限的方式

作者: 希尔大 | 来源:发表于2016-09-10 17:38 被阅读144次

    本文为《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...

    相关文章

      网友评论

        本文标题:Java - 打破访问权限的方式

        本文链接:https://www.haomeiwen.com/subject/ffnyettx.html