可见性范围在 Java 中的两个地方实现:编译器和执行字节码的 JVM。
首先,编译器限制使用public、protected 和private 关键字标记的变量的可见性(另请注意,Java 有第四个范围,称为默认范围,这是您在声明变量时得到的没有三个关键字之一)。其他地方记录了这些差异(请参阅In Java, difference between default, public, protected, and private 或任何有关 Java 编程的好书)。
其次,我认为这更多的是您要问的问题,即 Java 在运行时使用private、protected 和 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));
...