【问题标题】:How does Java protect private, package-private, and protected variables?Java 如何保护私有、包私有和受保护的变量?
【发布时间】:2015-08-11 09:27:52
【问题描述】:

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

如何在幕后区分私有、包私有、受保护和公共变量,以保证变量安全?

我的意思是,例如,它如何确保私有变量只能由类内的方法访问?

我想知道计算机/JVM 内部实际发生了什么。 Java 是否对这些类型中的每一种都有不同的内存空间?如果是这样,是什么使不同的内存空间无法访问?毕竟它还在记忆中吧?

提前谢谢你:-)

【问题讨论】:

  • 我不这么认为,另一个问题问区别,这个问题问发生了什么。
  • 安全管理器可用于防止访问私有属性。除此之外,AFAIK,没有专门的空间供这些人使用。访问超出范围的字段只是语义错误。这就是反射可以绕过它的原因——考虑到安全管理器不会妨碍它。
  • @necreaux 不是重复的
  • 其实我相信最终的局部变量是分开存储的。
  • @jrahhali Java 还限制了 JVM 在运行时的访问(这是为了防止在使用反射 API 时出现问题)。

标签: java variables access-modifiers


【解决方案1】:

如果您有兴趣,应该花一点时间浏览 Java 虚拟机规范。特别是,section 4.5 定义了如何存储和引用字段的属性,而section 5.4.3.2 讨论了如何实际检索字段。

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

【讨论】:

  • 感谢您的帮助...有趣的东西,但它仍然不能回答我想知道的问题。 JAVA 在得知字段属性是私有的后如何限制访问?喜欢它是否隐藏了数据的地址?
【解决方案2】:

可见性范围在 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 中的反射从另一个类访问 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 会保护对您通常能够看到的范围之外的变量(以及方法和类)的访问。

附录

Java 通常不会“隐藏”任何内容,因为它不需要。 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“隐藏”了什么来保护变量……数据的地址?如果是这样,是什么也保证了地址的安全? :-P XD
  • @Sirius 我已经更新了我的答案,提供了一些希望有帮助的说明。
【解决方案3】:

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

重要的是要理解,对于特定的代码,这种情况只会发生一次,因为有效性不会改变,因此在后续执行中不会再次验证已验证的代码,因此验证只强加一次性开销。

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

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

【讨论】:

    猜你喜欢
    • 2013-04-04
    • 2011-07-09
    • 2014-04-26
    • 1970-01-01
    • 2018-07-16
    • 1970-01-01
    • 1970-01-01
    • 2012-05-06
    • 2023-04-08
    相关资源
    最近更新 更多