23

我有

class A {
    private static class B {
        B() {
        }
    }
}

虽然 B 是私有的,但我从另一个类加载 A$B.class 没有问题。为什么允许这样做?

class C {
    public static void main(String[] args) throws Exception {
        System.out.println(Class.forName("A$B").newInstance());
    }
}

输出

A$B@affc70

更新

我知道加载任何类的限制是故意提出的,但必须有一个合理的解释。

请注意,包私有 B{} 构造函数是故意的。如果我删除它,我会得到

java.lang.IllegalAccessException:B 类不能使用修饰符“private”访问 A$B 类的成员

4

5 回答 5

12

类加载器可以加载任何类,但访问规则是在实例化时强制执行的。

那么为什么你的代码能够实例化这个类呢?

内部类在字节码级别是一个大黑客,因为它们被实现为与 Java 1.0 的字节码兼容。

所以这个源代码级别的私有内部类实际上在字节码级别是包保护的。您可以通过将类 C 移动到另一个包中并将 B 的构造函数公开来验证这一点:

Exception in thread "main" java.lang.IllegalAccessException: 
Class something.C can not access a member of class A$B with modifiers "public"

反射确实允许使用 setAccessible() 覆盖访问规则,但这里不是这种情况。

于 2012-12-24T10:52:45.223 回答
8

此行为符合javadoc

请注意,此方法不检查其调用者是否可以访问所请求的类。

换句话说,forName可以访问不可访问的类并且不会抛出IllegalAccessExcessException. 而且因为构造函数是包私有的,即(我假设)可以从您的调用位置访问,newInstance所以也不会抛出任何异常。

如果构造函数不可访问(因为您将其移动到另一个包或将其设为私有),newInstance则会引发异常。

关于您的更新,如果您删除构造函数,您将调用与类具有相同访问权限的默认构造函数,在这种情况下为私有(参见JLS 8.8.3如果类被声明为私有,则默认构造函数为隐式给出访问修饰符 private )。

于 2012-12-24T11:16:17.567 回答
3

反射通常由需要能够检查或修改在 Java 虚拟机中运行的应用程序的运行时行为的程序使用。

它是一种可以让人们打破某些规则的工具,可以将其扔到脚上或正确使用它。

反射提供了一种通过类和对象之间的常规交互通常无法获得的方法来访问有关类的信息的机制。

其中之一是允许从外部类和对象访问私有字段。

但是,两者getDeclaredField()setAccessible()都由安全管理器实际检查,并且当您的代码不允许执行此操作时会抛出异常。许多反射技巧可能不适用于活跃的安全管理器。

于 2012-12-24T10:53:34.093 回答
3

虽然反射是一个很好的比较,但类加载器超出了反射库的功能。例如,类加载器可以创建类,而反射则无法做到这一点。它还可以

  • 触发枚举实例的创建。
  • 在初始化之前给你一个类的引用。
  • “覆盖”父类加载器中的类。
  • 卸载时触发要卸载的类。
  • 给你一个类的字节码,给你原始的实现细节。
  • 在类路径中为您提供非类资源的流。

像反射一样,它可以加载带有私有成员的类,如果不能加载,那将毫无意义。

于 2012-12-24T11:19:16.823 回答
0

使用反射即使你可以改变修饰符

class MyClass{
private int value;
}

Field value = MyClass.class.getDeclaredField("value");
value.setAccessible(true);
于 2012-12-24T11:19:16.873 回答