反射是如何破坏私有性的? -- 《JAVA编程思想》49

interface 关键字相信大家都不会陌生,我们可以使用它来创建接口,从而实现隔离构件,降低代码耦合性的目的。

先来看一个例子,定义接口 A ,接口 A 的实现类 B。

public interface A {
    
    
    void f();
}

public class B implements A {
    
    

    @Override
    public void f() {
    
    
        System.out.println("A.f execute");
    }
    
    private void g() {
    
    
        System.out.println("B.g execute");
    }

}

在 InterfaceViolation 类中通过类型检查得知 a 是被当做 B 实现的,将 a 转换成 B 可调用接口 A 中不存在的方法。

public class InterfaceViolation {
    
    

    public static void main(String[] args) {
    
    
        A a = new B();
        a.f();
        System.out.println(a.getClass().getName());
        if (a instanceof B){
    
    
            B b = (B) a;
            b.f();
        }
    }
    
}
A.f execute
mtn.baymax.charpter14.B
A.f execute

上述的操作虽然是合法的,但我们有时候会不希望客户端程序员使用接口以外的方法。最简单的方式对实现类使用包访问权限,再通过一个公共类返回实现类的引用。如下所示:

class B implements A {
    
    

    @Override
    public void f() {
    
    
        System.out.println("A.f execute");
    }

    public void g() {
    
    
        System.out.println("B.g execute");
    }

}
public class HiddenB {
    
    

    public static A makeA() {
    
    
        return new B();
    }
    
}

这样即使知道返回的类是 B 类型,也会因无法获取到B类的访问权限,无法实现强制转换。

在这里插入图片描述
但这样就能阻止别人调用 B 类中的方法了嘛?

答案是不可能的,通过反射仍然可以做到。

public class InterfaceViolation {
    
    

    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException,
            IllegalAccessException {
    
    
        A a = HiddenB.makeA();
        Method g = a.getClass().getDeclaredMethod("g");
        g.setAccessible(true);
        g.invoke(a);
    }

}
B.g execute

这里可能会有同学有疑问,这里必须知道 B 类中的方法名才能调用。我仅给别人提供编译过的 class 文件,是不是就没有这种问题了?

曾经的我也是如此天真。但现实是,通过JDK 自带的 javap 命令即可反编译突破限制。

在这里插入图片描述
不仅仅是获取非公共方法,即使是类的私有变量和私有方法也可以通过 javap -private 类进行查看。

public class C implements A {
    
    

    private String name = "tom";

    private int age = 24;

    private final String address = "NO.1 Street";

    @Override
    public void f() {
    
    
        System.out.println("A.f execute");
    }

    private void h() {
    
    
        System.out.println("C.h execute");
    }

    @Override
    public String toString() {
    
    
        return "C{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", address='" + address + '\'' +
                '}';
    }
    
}

在这里插入图片描述

其次,即使是 private 域也无法阻止反射的访问,反射不仅可以调用私有方法,还可以获取私有变量,甚至可以修改私有变量的值。

public class HiddenC {
    
    

    public static A makeA() {
    
    
        return new C();
    }
    
}
public class ModifyingPrivateClass {
    
    

    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException,
            IllegalAccessException, NoSuchFieldException {
    
    
        A a = HiddenC.makeA();
        System.out.println("------调用私有方法------");
        Method h = a.getClass().getDeclaredMethod("h");
        h.setAccessible(true);
        h.invoke(a);
        System.out.println("------输出原对象------");
        System.out.println(a);
        System.out.println("------改变name属性------");
        Field name = a.getClass().getDeclaredField("name");
        name.setAccessible(true);
        name.set(a, "Jerry");
        System.out.println(a);
        System.out.println("------改变age属性------");
        Field age = a.getClass().getDeclaredField("age");
        age.setAccessible(true);
        age.set(a, 12);
        System.out.println(a);
        System.out.println("------改变address属性------");
        Field address = a.getClass().getDeclaredField("address");
        address.setAccessible(true);
        address.set(a, "NO.12 Street");
        System.out.println(a);
    }
    
}
------调用私有方法------
C.h execute
------输出原对象------
C{
    
    name='tom', age=24, address='NO.1 Street'}
------改变name属性------
C{
    
    name='Jerry', age=24, address='NO.1 Street'}
------改变age属性------
C{
    
    name='Jerry', age=12, address='NO.1 Street'}
------改变address属性------
C{
    
    name='Jerry', age=12, address='NO.1 Street'}

不过,通过上述例子,我们发现被 final 修饰的变量 address 是无法被反射修改的,故它是安全的。

其实,不仅仅是 private 域,类中的私有内部类或者匿名类,在反射面前都没有任何隐私可言。

虽然通过反射违反访问权限的操作看似非常糟糕,但在某些特定时刻,说不定可以帮助你解决问题。当然,我们还是应当尽量避免此类操作,因调用服务提供类中的 private 中的方法会增加代码的耦合性,在后续的迭代升级中,可能会使得程序不兼容,服务无法正常运行。

本次分享至此结束,希望本文对你有所帮助,若能点亮下方的点赞按钮,在下感激不尽,谢谢您的【精神支持】。

若有任何疑问,也欢迎与我交流,若存在不足之处,也欢迎各位指正!

Guess you like

Origin blog.csdn.net/BaymaxCS/article/details/120069309