【问题标题】:"method ___() in ___ is defined in inaccessible class or interface" compilation error“___ 中的方法 ___() 在不可访问的类或接口中定义”编译错误
【发布时间】:2013-03-30 18:50:57
【问题描述】:

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

示例 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() in java.lang.Enum is defined in an inaccessible class or interface

示例 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());

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


所以,问题是:

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

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

有人能解释一下这个限制的目的和原因吗?

更新assylias指出JLS §6.6.1

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

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

【问题讨论】:

  • enum E1 { A, B, C }写成public enum E1 { A, B, C }
  • @Andremoniy 答案在#6.6.1 我相信:引用(类、接口或数组)类型或构造函数的成员(类、接口、字段或方法)只有类型可访问并且声明成员或构造函数允许访问,才能访问类类型。
  • @BheshGurung 我知道,我在问:为什么我们需要这种可访问性限制?
  • @assylias #6.6.1 看起来就像那个限制,但它并没有解释这个愚蠢和不必要的限制。是吗?
  • @Andremoniy 不幸的是,根本原因很可能隐藏在编写/决定的人的脑海中(或者他们只是没有想到那个特定的用例)......

标签: java compiler-errors


【解决方案1】:

为了调用实例方法,使用invokevirtual 指令。要调用这个方法类必须有一个对该方法的解析引用

来自invokevirtual规范:

链接异常

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

5.4.3.3。方法解析:

将 D 中未解析的符号引用解析为 a 中的方法 类 C,方法引用给出的对 C 的符号引用是 首先解决(§5.4.3.1)。

5.4.3.1。类和接口解析:

如果 C 不可访问 (§5.4.4) D、类或接口解析 抛出 IllegalAccessError。

5.4.4。访问控制:

类或接口 C 可以被类或接口 D 访问,如果并且 仅当以下任一条件为真时:

  • C 是公开的。

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

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

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

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

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

如何重现 IllegalAccessError

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

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

【讨论】:

  • 嗯,谢谢你的回答。它很有说服力。我想等待另一个答案变体,并为您的答案投票。
【解决方案2】:

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

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

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

【讨论】:

  • 亲爱的@Stochasticly,问题不在于如何找到解决方法并修复代码。问题是:为什么我应该将我的引用对象转换为Object 类(例如)以访问Object 类方法,而所有类都隐式继承Object。。跨度>
  • @Andremoniy,我没有修复你的代码,我想出了一个例子来说明为什么这些规则是有意义的。 Object 类是公共的,类 C1 中的类 I2 的实例 x 是公共的,因此根据 Java 的规则,您可以在包之外访问 x 作为 (a) 一个对象,或 (b) 一个类实现 I1。但是,因为 I2 的类定义是包本地的,所以您不能以任何其他方式访问它。所以在包之外你不能访问我添加的 int ,如果它要实现例如一个包本地接口 I3,那么你也不能作为 I3 访问它。
【解决方案3】:

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

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

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

【讨论】:

  • 它们都是不可见的,但它们都继承自 Object 类。那么,如果我只想调用toString() 方法,为什么我显然应该将它们转换为Object
  • 您为什么希望它起作用?您显式返回 INVISIBLE 类型的实例。在 Java 中,碰巧所有的类都继承自 Object,如果您将类型声明为 Object,一切都很好,因为调用者和被调用者就合约达成一致,双方都可以看到/验证。在您的示例中,您隐藏了这些合同。尽管对您来说很明显,它与 Object 的合同相同,但它不适用于编译器 - 它只是无法与您有相同的看法。
【解决方案4】:

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

因此,您必须将它们转换为对象(或内部类的任何其他超类型)。您必须手动执行此转换,因为 JDK 不知道任何隐式转换。

处理这个问题的方法是将你的内部类变成普通类。内部类非常有用,但仅适用于非常具体的任务。

【讨论】:

  • 事实并非如此。如果内部类是公共的,您可以从另一个类或包中访问它。如果内部类是public static,那么它就像任何其他类一样。如果内部类是public,但不是静态的,那么由于缺少范围,您不能从另一个类创建它的新实例。
【解决方案5】:

我将尝试使用您的示例 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 的公共方法。由于I2 的访问说明符是默认访问说明符,因此它的任何方法都不应该在包外可用,因此受到限制。 如果没有限制,即使I2 具有默认访问说明符,我们也可以从C2(在不同的包中)访问方法hello()

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

【讨论】:

  • 你在谈论显而易见的事情。我正在寻找对此限制的更深入的分析和解释。对不起。
猜你喜欢
  • 2018-09-16
  • 1970-01-01
  • 2020-05-06
  • 2014-02-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多