Java 如何保护私有变量、包私有变量和受保护变量?

How does Java protect private, package-private, and protected variables?

我是 Java 编程的新手,有一个我一直很好奇的问题。任何帮助将不胜感激:)

private、package-private、protected和public变量在后台如何区分,以保证变量安全?

例如,我所说的安全是指,它如何确保私有变量只能由 class 中的方法访问?

我想知道 computer/JVM 里面究竟发生了什么。 Java 是否对这些类型中的每一种都有不同的内存 space?如果是这样,是什么让不同的内存 spaces 无法访问?毕竟它还在记忆中吧?

提前谢谢你:-)

如果您有兴趣,应该花点时间浏览 Java 虚拟机规范。特别是,section 4.5 defines how the attributes for a field are stored and referred to, while section 5.4.3.2 讨论了字段的实际检索方式。

归结为解析字段(如果需要,首先解析 class),然后检查调用代码是否可以访问该字段。 AFAICT 私有字段没有特殊的 space。

可见性范围在 Java 的两个地方实现:编译器和执行字节码的 JVM。

首先,编译器限制了用 publicprotectedprivate 关键字标记的变量的可见性(还要注意 Java 有第四个作用域,称为默认作用域作用域,这就是当你声明一个没有三个关键字之一的变量时得到的)。其他地方记录了差异(请参阅 In Java, difference between default, public, protected, and private 或任何关于 Java 编程的好书)。

其次,我认为这更像是您要问的,Java 保护对具有 privateprotected 和 JVM 内的默认范围的变量的访问运行时间。这主要适用于 Java 的反射 API(如果你的非反射代码编译你知道它没有访问任何它不允许访问的东西,因此不能 运行 运行时间可见性问题)。例如,假设您在 Foo.java:

中找到了这个
// class with a private member variable
public class Foo {
    private int bar = 0;
}

然后您尝试使用 Test.java 中的反射从另一个 class 访问 bar:

import java.lang.reflect.*;

// This class won't have access to Foo's member 'bar'
public class Test {

    void doStuff() {
        // create a new instance of Foo
        Foo foo = new Foo();
        try {
            // use reflection to get the variable named 'bar'
            Field barField = foo.getClass().getDeclaredField("bar");

            // attempt to access the value of 'bar' which will throw an exception
            System.out.println(barField.get(foo));

       } catch (NoSuchFieldException e) {
           throw new RuntimeException(e);
       } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
       }
    }

    public static void main(String[] args) {
        new Test().doStuff();
    }

}

您会收到一个异常,表明您无权访问该变量。有一些方法可以避免这种情况,但这里的要点是,默认情况下,JVM 保护对您通常能够看到的范围之外的变量(和方法和 classes)的访问。

附录

Java 通常不会 "hide" 任何东西,因为它不需要。 Java 语言和 JVM 字节码都无法访问幕后使用的指针,也没有其他方法可以通过语言或反射 API 访问内存中的任意位置,因此没有办法(用一个例外——见下文)读取您无权访问的变量的值。当使用反射时,JVM 只需检查一个标志(最初由编译器设置)以查看当前代码是否可以访问正在访问的变量或方法,然后允许访问或抛出异常。

我提到的异常是反射 API 调用,它可以关闭对给定变量或方法的访问检查。例如,为了避免我之前示例中的异常,您可以这样做:

...
Field barField = foo.getClass().getDeclaredField("bar");
// mark the variable 'bar' as accessible
barField.setAccessible(true);
// attempt to access the value of 'bar' which will throw an exception
System.out.println(barField.get(foo));
...

Java 使用 代码验证 来执行它的规则。在 Java 字节码中,没有内存地址,但仍然有用于访问实例字段、static 字段、局部变量或操作数堆栈的不同专用指令。由于指令显式访问字段,验证者可以检查包含指令的 class 是否有权访问该字段。

请务必了解,对于特定代码段,这种情况只会发生 一次,因为有效性不会改变,因此已验证的代码不会在后续的验证中再次验证执行,因此验证仅施加一次性开销。

允许有不同的策略,关于何时验证方法的代码,但当然,验证必须在第一次执行一段代码之前发生,也就是在 JIT 可以将字节码编译为机器码之前它确实使用内存地址来访问字段。

另见 “The Java® Virtual Machine Specification, §4.10. Verification of class Files”