20

我发现了一个奇怪的编译限制,我无法解释,我不明白这个限制的原因。

示例 1:

考虑这些类:

package e1;

public class C1 {
    enum E1 { A, B, C }
    public E1 x;
}

package e2;

import e1.C1;
public class C2 {
    public String test(C1 c1) {
        return c1.x.toString();    // here compilation error
    } 
}

这会导致以下编译错误:

错误:(5,20)java:toString()injava.lang.Enum被定义在一个不可访问的类或接口中

示例 2:

考虑这些类:

package i1;

public interface I1 {
    int someMethod();
}

public class C1 {
    static class I2 implements I1 {
        public int someMethod() {
            return 1;
        }
    }
    public I2 x = new I2();
}

package i2;

import i1.C1;
import i1.I1;
public class C2 {
    public static void main(String[] args) {
        C1 c1 = new C1();
        System.out.println(c1.x.someMethod());  // compilation error
    }
}

这也会导致相同的编译错误,但如果我们将违规行更改为:

System.out.println(((I1)c1.x).someMethod());

然后可以编译并正常工作。


所以,问题是:

为什么需要这种可访问性限制?

是的,我知道C1.Eexample-1) 和C1.I2example-2) 中的类是包私有的。但同时很明显,没有人可以为基接口(I1of Object)的方法分配较弱的访问权限,因此将对象直接转换为其基接口并获得对受限方法的访问总是安全的。

有人可以解释这种限制的目的和原因吗?

更新assylias指出 JLS §6.6.1

引用(类、接口或数组)类型的成员(类、接口、字段或方法)或类类型的构造函数只有在类型可访问时才可访问...

看起来这是限制,但它没有解释为什么需要这个限制(在上述示例的情况下)......

4

5 回答 5

12

对于实例方法的调用,使用invokevirtual指令。要调用此方法,类必须具有对此方法的已解析引用

从invokevirtual规范:

链接异常

在解析对方法的符号引用期间,可以抛出与方法解析(第5.4.3.3 节)有关的任何异常。

5.4.3.3 . 方法解析:

要将未解析的符号引用从 D 解析到类 C 中的方法,首先解析由方法引用给出的对 C 的符号引用(第5.4.3.1 节)。

5.4.3.1。类和接口解析:

如果 D 无法访问 C(第 5.4.4 节),则类或接口解析会引发 IllegalAccessError。

5.4.4 . 访问控制:

当且仅当以下任一条件为真时,类或接口 D 才能访问类或接口 C:

  • C是公开的。

  • C 和 D 是同一运行时包的成员(第 5.3 节)。

C 和 D 不是来自同一个包。所以即使java为你编译了这段代码,它也会在调用过程中抛出一个IllegalAccessError。编译器足够聪明,可以防止这种明显的错误。这些限制来自 java 的类解析过程的要求。

调用实例方法JVM需要两件事:对对象的引用和对象的描述(类或接口)。描述是通过解析过程访问的。如果失败,则调用失败。

如果在解析符号引用期间发生错误,则必须在程序中(直接或间接)使用符号引用的点处抛出 IncompatibleClassChangeError(或子类)的实例。

在您的情况下,C2 可以访问 I1。所以接口调用效果很好。但 C2 无权访问 I2 类。这就是为什么 IllegalAccessError 如果此代码编译在运行时可能会抛出的原因。

如何重现 IllegalAccessError

  1. 例如,将内部类公开并在 IDE 中编译所有内容
  2. 将内部类包设为私有并使用命令行中的 javac 对其进行编译。
  3. 用 cmd 生成的类替换 IDE 生成的类
  4. 你会看到类似的东西:

线程“主”java.lang.IllegalAccessError 中的异常:试图从 Test.main 的类 Test 访问类 qq.Test1$I2(Test.java:30)

于 2013-04-04T11:38:18.070 回答
2

访问控制(私有、受保护、公共、包)都定义良好,并为程序员提供了很大的灵活性来控制对类、变量、方法等的访问。您的问题只是说明了应用这些访问控制的一个特定示例. 你写包的方式i1,你说大家都可以看interface I1,但不是大家都可以看class I2。这允许您向I2不公开的类添加功能,同时仍然允许访问I2implementation的位I1。例如,如果我将包本地整数添加到类I2

public class C1 {
    static class I2 implements I1 {
        int i;
        public int xxx() {
            return 1;
        }
    }
    public I2 x = new I2();
}

那么您可以访问包内的 int ,但不能访问包外的 int ,而在包外,您无法通过强制转换来解决这个问题:-)。

于 2013-04-03T09:20:22.500 回答
1

E1在这些示例中,来自示例 1 和I2示例 2 的调用者均不可见。所以我很清楚,编译器说它不知道如何处理它。你的意图是模棱两可的。或者反过来说:JVM 应该如何处理这种对不可见类的调用?投射到界面?哪一个,作为一个类可以实现几个。

也许可以让编译器在特殊情况下向其接口添加强制转换,但这只是:特殊情况而不是一般规则。除此之外,对于开发人员来说,获取一个无法访问的类的实例(想想框架)会非常混乱。

如果你想隐藏内部,你宁愿使用(公共)接口,而不是包私有实现。

于 2013-04-03T10:01:50.137 回答
-1

我将尝试通过使用您的示例 2 并对其进行一些更改来解释它。

假设班级C1是这样的

public class C1 {
    static class I2 implements I1 {
        public int xxx() {
            return 1;
        }

        public int hello(){
            return 1;
        }
    }
    public I2 x = new I2();
}

我刚刚向 I2 类添加了另一个公共方法 hello()。

package i2;:

import i1.C1;
import i1.I1;
public class C2 {
    public static void main(String[] args) {
        C1 c1 = new C1();

        c1.x.hello(); // Will not compile.
        c1.x.toString(); //Will not compile.             

        Object mObject=(Object)c1.x;
        mObject.toString(); //Will compile.
    }
}

现在,回答你为什么要限制的问题。该限制允许程序员阻止访问类的公共方法I2。由于访问说明符 ofI2是默认访问说明符,因此它的任何方法都不应该在包外可用,因此受到限制。如果没有限制,即使具有默认访问说明符,我们也可以hello()从(在不同的包中)访问该方法。C2I2

转换c1.xObject有效,因为现在编译器将其视为 class 的对象Object。因此我们可以访问它的toString()方法。因此,mObject.toString()有效, c1.x.toString()而不I2能从 class 访问 class C2

于 2013-04-03T12:41:21.783 回答
-1

在这两个示例中,您都使用内部类/接口。内部类不能被定义它们的类之外的任何对象访问。

Hence, you have to cast them to objects (or any other supertype of your inner class). You have to do this cast manually because the JDK does not know of any implicit casts.

The way to deal with this is to turn your inner classes into regular classes. Inner classes are very useful, but only for very specific tasks.

于 2013-04-06T23:43:03.377 回答