我发现了一个奇怪的编译限制,我无法解释,我不明白这个限制的原因。
示例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()
中的java.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());
然后这可以编译并正常工作。
所以,问题是:
为什么需要这种可访问性限制?
是的,我了解示例1中的类C1.E
和示例2中的C1.I2
是包私有。但同时很明显没有人可以为基本接口(I1
的{{1}})的方法分配较弱的访问权限,因此将对象直接转换为其基接口并获得对受限方法的访问将始终是安全的。
有人可以解释这个限制的目的和原因吗?
只有在可以访问类型的情况下,才能访问引用(类,接口或数组)类型的成员(类,接口,字段或方法)或类类型的构造函数。
看起来这是限制,但它没有解释为什么需要这种限制(在上述例子的情况下)......
答案 0 :(得分:12)
对于实例方法的调用,使用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 即可。访问控制:
如果和,则类或接口D可以访问类或接口C. 仅当满足以下任一条件时:
C是公开的。
C和D是同一运行时包(第5.3节)的成员。
C和D不是来自同一个包裹。因此,即使java为您编译此代码,它也会在调用期间抛出IllegalAccessError。编译器足够聪明,可以防止出现这种明显的错误。 这些限制来自java的类解析过程的要求。
要调用实例方法,JVM需要两件事:对象的引用和对象的描述(类或接口)。可以通过resolution process访问说明。如果失败,则调用失败。
如果在解析符号引用期间发生错误,则a 必须是IncompatibleClassChangeError(或子类)的实例 抛出(直接或间接)使用的程序中的某个点 象征性的参考。
在您的情况下,C2可以访问I1。所以接口调用效果很好。但C2无法访问I2类。这就是为什么如果此代码编译,可能会在运行时抛出IllegalAccessError。
如何重现IllegalAccessError :
线程“main”中的异常java.lang.IllegalAccessError:尝试过 访问类 来自Test类的qq.Test1 $ I2 在Test.main(Test.java:30)
答案 1 :(得分:2)
访问控制(私有,受保护,公共,包)都是明确定义的,并为程序员提供了很大的灵活性来控制对类,变量,方法等的访问。您的问题只是说明了这些的一个特定示例访问控制应用。您编写包i1
的方式,您已经说过,每个人都可以看到界面I1
,但是每个人都可以看到课程I2
。这允许您向非公开的类I2
添加功能,同时仍允许访问实现I2
的{{1}}位。例如,如果我将包本地整数添加到类I1
I2
然后你可以访问你的软件包中的int,但不能在你的软件包之外访问它,在你的软件包之外你不能通过转换来解决这个问题: - )。
答案 2 :(得分:1)
E1
和示例2中的I2
对于这些示例中的调用者都不可见。所以我很清楚,编译器说它不知道如何处理它。你的意图含糊不清。
或者反过来说:JVM应该如何处理对隐形类的调用?投射到界面?哪一个,作为一个类可以实现几个。
也许有可能让编译器在特殊情况下为其接口添加一个强制转换器,但它只是:特殊情况而不是一般规则。除此之外,开发人员获取无法访问的类的实例(想想框架)会非常困惑。
如果你想隐藏内部,你宁愿使用(公共)接口,而不是包私有实现。
答案 3 :(得分:-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
的公共方法。由于I2
的访问说明符是默认访问说明符,因此它不能在包外部使用任何方法,因此限制。
如果没有限制,我们就可以从hello()
(位于不同的包中)访问方法C2
,即使I2
具有默认访问说明符。
将c1.x
转换为Object
是有效的,因为现在编译器将其视为类Object
的对象。因此,我们可以访问其toString()
方法。因此,mObject.toString()
起作用
c1.x.toString()
并非因课程I2
无法访问课程C2
。
答案 4 :(得分:-1)
在这两个示例中,您使用的是内部类/接口。内部类不能被定义它们之外的任何对象访问。
因此,您必须将它们转换为对象(或内部类的任何其他超类型)。您必须手动执行此操作,因为JDK不知道任何隐式转换。
处理这个问题的方法是将内部类转换为常规类。内部类非常有用,但仅适用于非常特定的任务。