I found a strange compilation restriction which I cannot explain, and I don't understand this restriction's reason.
Example-1:
Consider these classes:
In package e1;
:
public class C1 {
enum E1 { A, B, C }
public E1 x;
}
In package e2;
:
import e1.C1;
public class C2 {
public String test(C1 c1) {
return c1.x.toString(); // here compilation error
}
}
This causes the following compilation error:
Error:(5,20) java:
toString()
injava.lang.Enum
is defined in an inaccessible class or interface
Example-2:
Consider these classes:
In 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();
}
In 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
}
}
This also causes the same compilation error, but if we change the offending line to:
System.out.println(((I1)c1.x).someMethod());
Then this can be compiled and works fine.
So, the question is:
Why is this restriction of accessibility needed?
Yes, I understand that classes C1.E
in example-1) and C1.I2
in example-2) are package private. But at the same time it's clear that nobody could assign weaker access privileges to methods of a base interface (I1
of Object
), so it will be always safe to make direct casting of object to its base interface and get access to restricted method.
Could somebody explain the purposes and the reason of this restriction?
UPDATE: assylias pointed out JLS §6.6.1:
A member (class, interface, field, or method) of a reference (class, interface, or array) type or a constructor of a class type is accessible only if the type is accessible...
Looks like this is the restriction, but it doesn't explain why this restriction (in the cases of the above mentioned examples) is needed...
For invocation of instance methods invokevirtual instruction is used. To invoke this method class must have a resolved reference to this method
From invokevirtual specification:
Linking Exceptions
During resolution of the symbolic reference to the method, any of the exceptions pertaining to method resolution (§5.4.3.3) can be thrown.
5.4.3.3. Method Resolution:
To resolve an unresolved symbolic reference from D to a method in a class C, the symbolic reference to C given by the method reference is first resolved (§5.4.3.1).
5.4.3.1. Class and Interface Resolution:
If C is not accessible (§5.4.4) to D, class or interface resolution throws an IllegalAccessError.
5.4.4. Access Control:
A class or interface C is accessible to a class or interface D if and only if either of the following conditions is true:
C is public.
C and D are members of the same run-time package (§5.3).
C and D are not from the same package. So even if java compiles this code for you, it will throw an IllegalAccessError during invocation. Compiler is clever enough to prevent such obvious errors. These restrictions are coming from requirements of java's class resolution process.
To invoke instance method JVM requires two things: reference to the object and description of the object(class or interface). Description is accessed through resolution process. If it fails, invocation fails.
If an error occurs during resolution of a symbolic reference, then an instance of IncompatibleClassChangeError (or a subclass) must be thrown at a point in the program that (directly or indirectly) uses the symbolic reference.
In your case C2 has access to I1. So interface invocation works well. But C2 does not have access to class I2. And that's why IllegalAccessError could be thrown at runtime if this code compiles.
How to reproduce IllegalAccessError:
Exception in thread "main" java.lang.IllegalAccessError: tried to access class qq.Test1$I2 from class Test at Test.main(Test.java:30)
The access controls (private, protected, public, package) are all well-defined, and give the programmer a lot of flexibility to control the access to classes, variables, methods etc. Your question just illustrates a particular example of these access controls applying. The way you've coded up your package i1
, you've said that it's OK for everyone to see interface I1
, but that it's not OK for everyone to see class I2
. This allows you to add functionality to class I2
that isn't public, while still allowing access to the bit of I2
that implements I1
. For example, if I add a package local integer to class I2
public class C1 {
static class I2 implements I1 {
int i;
public int xxx() {
return 1;
}
}
public I2 x = new I2();
}
then you've got access to the int within your package but not outside it, and outside your package you can't get around this by casting :-).
E1
from example 1 and I2
from example 2 are both not visible to the callers in those examples. So it is clear to me, that the compiler says it doesn't know how to handle it. Your intention is ambiguous.
Or put it the other way around: how should the JVM handle such a call to invisible classes? Cast to an interface? Which one, as a class may implement several.
Maybe it would be possible to let the compiler add a cast to its interface in special cases, but it's just that: special cases and not a general rule. Besides that, it would be highly confusing to developers to get an instance of a class which can not be accessed (think of frameworks).
If you want to hide internals, you'd rather use (public) interfaces, instead of package-private implementations.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With