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

你可能感兴趣的:(Java - 打破访问权限的方式)