简体   繁体   English

Java如何保护私有变量,程序包私有变量和受保护变量?

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

I am new to Java programming and have a question that I have been curious about. 我是Java编程的新手,有一个我一直很好奇的问题。 Any help will be appreciated :) 任何帮助将不胜感激 :)

How are private, package-private, protected, and public variables differentiated behind the scenes, so as to keep variables safe? 在后台如何区分私有变量,包私有变量,受保护变量和公共变量,以确保变量安全?

By safe I mean for example, how does it make sure that private variables are accessible only to methods inside the class? 举例来说,我的意思是,如何确保私有变量只能由类内部的方法访问?

I want to know what actually happens inside the computer/JVM. 我想知道计算机/ JVM内部实际发生了什么。 Does Java have a different memory space for each of these types? Java是否对每种类型都有不同的内存空间? And if so what makes the different memory spaces inaccessible? 如果是的话,是什么使得不同的存储空间无法访问? After all it is still in the memory right? 毕竟它仍然在内存中吗?

Thank you in advance :-) 先感谢您 :-)

Visibility scoping is implemented in two places in Java: in the compiler and in the JVM that executes the bytecode. 可见性范围在Java中的两个地方实现:在编译器中和在执行字节码的JVM中。

First, the compiler limits visibility of variables flagged with public , protected and private keywords (also note that Java has a fourth scope, called the default scope, that was what you get when you declare a variable without one of the three keywords). 首先,编译器限制了用publicprotectedprivate关键字标记的变量的可见性(还要注意,Java具有第四个作用域,称为默认作用域,这是在声明不包含三个关键字之一的变量时所得到的结果)。 The differences are documented elsewhere (see In Java, difference between default, public, protected, and private or any good books on Java programming). 差异记录在其他地方(请参见Java中有关Java编程的默认书,公共书,受保护和私有书或任何好书之间的区别 )。

Second, and I think this is more what you are asking about, is that Java protects access to variables with private , protected and the default scope within the JVM at runtime. 其次,我想这是您要问的更多问题,是Java在运行时通过JVM中的privateprotected和默认作用域来保护对变量的访问。 This primarily applies to Java's reflection API (if your non-reflection code compiles you know it isn't accessing anything it isn't allowed to access and thus can't run into runtime visibility problems). 这主要适用于Java的反射API(如果您的非反射代码可以编译,则您知道它无法访问不允许访问的任何内容,因此不会遇到运行时可见性问题)。 For example, say you've got this in Foo.java: 例如,假设您在Foo.java中具有以下功能:

// class with a private member variable
public class Foo {
    private int bar = 0;
}

And then you try to access bar from another class using reflection in Test.java: 然后,您尝试使用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();
    }

}

You would get an exception indicating you have no access to that variable. 您将得到一个异常,表明您无权访问该变量。 There are ways to circumvent this, but the point here is that by default the JVM protects access to variables (and methods and classes) outside the scope you'd normally be able to see. 有许多方法可以避免这种情况,但是这里的要点是,默认情况下,JVM保护对通常无法看到的范围之外的变量(以及方法和类)的访问。

Addendum 附录

Java doesn't typically "hide" anything since it doesn't need to. Java通常不需要隐藏任何东西,因为它不需要。 Neither the Java language nor the JVM bytecode have access to the pointers used behind the scenes and there's also no other way through the language or the reflection API to access arbitrary locations in memory and thus no way (with one exception -- see below) to read the value of a variable to which you don't have access. Java语言和JVM字节码都无法访问在幕后使用的指针,也没有其他方法可以通过语言或反射API来访问内存中的任意位置,因此(除了一个例外,请参阅下文)读取您无权访问的变量的值。 When using reflection the JVM simply checks a flag (initially set by the compiler) to see if the current code has access to the variable or method being accessed and then either allows the access or throws an exception. 使用反射时,JVM只需检查一个标志(最初由编译器设置),以查看当前代码是否有权访问所访问的变量或方法,然后允许访问或引发异常。

The exception I mentioned is a reflection API call that can turn off the access checking for a given variable or method. 我提到的异常是反射API调用,该调用可以关闭对给定变量或方法的访问检查。 For example, to avoid the exception from my previous example you could do this: 例如,为避免我之前的示例出现异常,您可以这样做:

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

You should spend a little time browsing the Java Virtual Machine Spec if you're interested. 如果您有兴趣,应该花一些时间浏览Java虚拟机规范。 In particular, section 4.5 defines how the attributes for a field are stored and referred to, while section 5.4.3.2 discusses how fields are actually retrieved. 特别是, 第4.5节定义了字段的属性如何存储和引用,而第5.4.3.2节则讨论了如何实际检索字段。

It boils down to resolving the field (first resolving the class if need be) and then checking whether the field is accessible to the calling code. 它归结为解析该字段(如果需要,首先解析该类),然后检查调用代码是否可访问该字段。 AFAICT there is no special space for private fields. AFAICT没有专用于私有领域的空间。

Java uses code verification to enforce it's rules. Java使用代码验证来执行其规则。 In the Java bytecode, there are no memory adresses but still distinct dedicated instructions for accessing fields of an instance, static fields, local variables or the operand stack. 在Java字节码中,没有内存地址,但是仍然有不同的专用指令来访问实例的字段, static字段,局部变量或操作数堆栈。 Since the instructions explicitly access fields, the Verifier can check whether the class containing the instruction has the right to access that field. 由于指令显式地访问字段,因此验证程序可以检查包含指令的类是否有权访问该字段。

It's important to understand that this happens only once for a particular piece of code, as the validity doesn't change, thus verified code won't be verified again on subsequent executions, so the verification imposes only a one-time overhead. 重要的是要了解,对于特定的代码段,这种情况只会发生一次 ,因为有效性不会改变,因此,经过验证的代码不会在后续执行中再次得到验证,因此验证仅带来一次性开销。

There are different strategies allowed, regarding when to verify a method's code, but of course, the verification must happen before the first execution of a piece of code, which is before a JIT may compile the byte code to machine code which really uses memory addresses for accessing fields. 关于何时验证方法的代码,可以采用不同的策略,但是当然,验证必须在第一次执行一段代码之前进行,即在JIT将字节码编译为真正使用内存地址的机器代码之前用于访问字段。

See also “The Java® Virtual Machine Specification, §4.10. 另请参阅“Java®虚拟机规范,第4.10节。 Verification of class Files” 验证class文件”

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM