java compiler oddity: field declared in same class, yet "not visible" - java

The eclipse compiler refuses to compile the following code, stating that the field s is not visible. (IBM's Aspect J compiler also refuses, stating that "s could not be resolved") Why is that?
public class Test {
String s;
void foo(Object o) {
String os = getClass().cast(o).s;
}
}
The Java Language Specification states:
Otherwise, we say there is default
access, which is permitted only when
the access occurs from within the
package in which the type is declared.
The way I understand it, the field is declared and accessed in the same compilation unit, thus within the same package, and should therefore be accessible.
Even more strangely, adding a downcast from ? extends Test to Test makes the field visible, i.e. the following code compiles:
public class Test {
String s;
void foo(Object o) {
Test t = getClass().cast(o);
String os = t.s;
}
}
Have I stumbled across a compiler bug, or misunderstood the Java Spec?
Edit:
I am on another computer now. Here, javac accepts the code, but eclipse still doesn't. Versions on this machine:
Eclipse Platform
Version: 3.4.2 Build id:
M20090211-1700
JDK 1.6.0
Edit 2
Indeed, javac accepts the code. I had tested by running the ant build, which uses IBM's Ascpect J compiler ...

Try this:
void foo(Object o) {
Test foo = getClass().cast(o);
String so = foo.s;
}
[Edit to clarify]:
getClass().cast(o) returns an object of type 'capture#1-of? extends Test' and not Test. So the issue is related to generics and how the compiler treats it. I don't know the details of the spec on generics but given that some compilers (per comments here) do accept your code, then this is either a loop hole in the spec or some of these compilers are not entirely according to spec.
[Last thoughts]:
I believe the eclipse compiler is actually (carefully) correct here. The object o may in fact be an extension of Test (and defined in another package) and the compiler has no way of knowing if that is indeed the case or not. So it is treating it as the worst case of an instance of an extension defined in another package. It would have been super correct if adding a final qualifier to class Test would have allowed access to field s, but it does not.

Well, let's see. I'd say the compiler can't properly guarantee that foo() will be called by some entity within the package, and therefore can't guarantee that s is visible. For example, add
protected void bar() {
foo();
}
and then in some subclass Banana in another package
public void quux() { bar(); }
and oops! getClass() yields Banana, which cannot see s.
Edit: In a sense, other.package.Banana doesn't have a field s. If Banana were in the same package, it could still have its own s property, and would have to refer to Test's s via super.

I can't reproduce what you are saying. These both compile fine for me without warning, error or anything with javac directly.
WinXP, javac 1.6.0_16
No I tried with eclipse (v3.4.1, Build id: M20080911-1700) and for the first one it says:
The field Test.s is not visible
At least for Compiler Compliance level 1.6 and 1.5.
The funny thing being, if you look at the Quick-fix options it lists a Change to 's' resolution. Which of course doesn't solve the problem. So the eclipse compiler and the Quick-fix "generator" seem to have different views on this too ;-)
For Compiler Compliance level 1.4 (as was to be expected) in eclipse for the first one I get
s cannot be resolved or is not a field
and for the second one I get
Type mismatch: cannot convert from Object to Test
If I specify -source 1.4 and target -1.4 in the command line directly javac says for the first one
cannot find symbol
and for the second one I get
incompatible types

Actually in almost all cases, except when required by Generics, it's better (and safer) to use Java cast operator. I discussed it here. Java cast operator does look over verbose, but it's the right tool here.
Replacing cast method with the operator compiles just fine in Eclipse.
public class Test {
String s;
void foo(Object o) {
String os = ((Test) o).s;
}
}
I think that alphazero is correct here, that Eclipse is just over cautious.

Very weird. For an unknown reason (to me), the eclipse compiler requires an explicit cast:
void foo(Object o) {
String os = ((Test)getClass().cast(o)).s;
}
While the code perfectly compiles without the cast with Sun's JDK (I'm running version 1.6.0_16 on GNU/Linux).

Related

Java: "attempting to assign weaker access privilege error" (Compiling JDK 1.6 Source with 1.8)

Using Gradle, we are trying to compile legacy Java code, which was developed for JDK 1.6 with a JDK 1.8 compiler.
At some point the compilation process quits with the error
attempting to assign weaker access privileges; was public
(The cause of the error itself is obvious: we have a method in an abstract class, which is declared public, but the implementing class declares it as protected.)
Using JDK 1.6 for compiling, we never had any issues with this.
Now for several reasons, we have to compile our code with Java 8, having us run into this issue.
We already tried project setting -PsourceCompatibility=1.6 (also -PtargetCompatibility=1.8) when building, without effect.
At the moment, refactoring the whole product code (expecting more and similar errors to follow) is no option, so we are looking for a solution to build the old code with the new JDK.
Any help for this?
The only explanation to the fact that your system used to work with Java 1.6 is that the method access in the superclass has been changed to public without recompiling the subclass. Lowering accessibility in a subclass has been prohibited from the beginning.
Java Language Specification 1.6 provides this explanation on page 344:
if the package points defines the class Point:
package points;
public class Point {
public int x, y;
protected void print() {
System.out.println("(" + x + "," + y + ")");
}
}
used by the Test program:
class Test extends points.Point {
protected void print() {
System.out.println("Test");
}
public static void main(String[] args) {
Test t = new Test();
t.print();
}
}
then these classes compile and Test executes to produce the output:
Test
If the method print in class Point is changed to be public, and then only the Point class is recompiled, and then executed with the previously existing binary for Test then no linkage error occurs, even though it is improper, at compile time, for a public method to be overridden by a protected method (as shown by the fact that the class Test could not be recompiled using this new Point class unless print were changed to be public.) (emphasis added)
If you must re-create the exact behavior with Java 1.8 compiler, change accessibility in the superclass to protected, compile the superclass and subclass, then change accessibility in the superclass back to public, and compile only the superclass. However, at this point I would strongly recommend changing the subclass to provide proper accessibility.

javac behavior change in JDK 7 regarding private member access

This code compiles OK using javac JDK version 1.6.0_33-b03-424, but doesn't compile using javac JDK version 1.7.0_06.
public class Test {
private final int i = 0;
void test(Object o) {
if (getClass().isInstance(o)) {
System.out.println(getClass().cast(o).i);
}
}
}
javac output is:
Test.java:6: error: i in Test is defined in an inaccessible class or interface
System.out.println(getClass().cast(o).i);
^
1 error
Changing the code to store the result of getClass.cast() in a temporary variable allows the program to compile without error.
This is easy to work around, but I can't find any rationale for this change in the JLS 7, or any mention of a change like this in the JDK 7 release notes. There is a mention of an access change regarding private members of type parameters to a generic, but that doesn't apply here.
Is this a regression in javac? Is it now enforcing a restriction that it wasn't enforcing before?
Well, I'm puzzled by this and the only explanation I can adventure is the conjunction of two things.
1_ getClass() docs say the following:
The actual result type is Class<? extends |X|> where |X| is the
erasure of the static type of the expression on which getClass is
called.
2_ One of the incompatibilities introduced in Java 7 is Compiler No Longer Allows Access to Private Members of Type Variables.
So, the compiler is unsure it the cast is made to the base class or a subclass and it blocks accesing a private member, since if the cast were to be assigned to a subclass it would be illegal even if defined in the original parent class, as shown in the following example:
class BaseTest {
private final int i = 1;
void test(Object o) {
if (getClass().isInstance(o)) {
TestAccess to = TestAccess.class.cast(o);
//System.out.println(to.i); // ERROR: i has private access in BaseTest
}
}
}
class TestAccess extends BaseTest{}
So, I guess it's one more of Java quirks due to rules that make more sense in more complex examples.

Method that exists...does not?

Code (which compiles):
for (Method m : ImmutableList.class.getMethods()) {
System.out.println(m);
}
ImmutableList.copyOf(Arrays.asList(new PlayerLevel[0]));
Output (annotated and shortened):
public final void com.google.common.collect.ImmutableList.add(int,java.lang.Object)
----> public static com.google.common.collect.ImmutableList com.google.common.collect.ImmutableList.copyOf(java.lang.Iterable)
public static com.google.common.collect.ImmutableList com.google.common.collect.ImmutableList.copyOf(java.util.Iterator)
(lots of other methods)
java.lang.NoSuchMethodError: com.google.common.collect.ImmutableList.copyOf(Ljava/util/Collection;)Lcom/google/common/collect/ImmutableList;
Huh?
(If the logs are not clear enough, I get an error saying that ImmutableList.copyOf(List) is not a method, but by looping through all the methods I see there is a copyOf(Iterable), and List implements Iterable.)
Both methods are compatible at compile time. But runtime is another beast. I assume, that your code compiles against an older version of Google Collections but runs against a newer version.
Edit:
What happens in detail:
Given the lines
List<String tmpArray = Arrays.asList(new PlayerLevel[0]);
ImmutableList.copyOf(tmpArray);
the compiler starts to look for a suitable method in ImmutableList with the name copyOf and one parameter compatible to the static type List<String>. The version of the class visible to the compiler offers exactly one match:
ImmutableList.copyOf(Collection<T> arg0);
Please note, that the compiler is not interested in the actual type of tmpArray, only the static type (aka. "formal type") is considered.
The compiler writes the signature of the selected method into the class file.
At runtime the classloader / linker reads the class, finds the signature of the method
ImmutableList.copyOf(Collection<T> arg0);
and performs a lookup (not a search!) on ImmutableList for exactly the given signature. Compatibility does not matter here, that was the job of the compiler. You get the same results, if you use reflection like this:
ImmutableList.class.method("copyOf", Collection.class);
In both cases Java simply performs a lookup using exactly the given type. It does not perform a search like "return method(s) which can be called with the given type".
In your case the runtime classpath and the compile time class are different. So the classloader / linker fails to perform the lookup.
One step back
This issue shows the different levels of compatibility:
Binary compatibility: Throw in a new jar and that's it.
Source compatibility: You have to compile your source but you don't have to change it.
Behavioural compatibility or Semantic compatibility: The client code must be changed.
You can use these keywords to look around this site or on Google for more infos. A good reference for binary compatibility are the three parts of Evolving Java-based APIs.

Java Generics Type Erasure Method Signature Problem

Given the following hypothetical type hierarchy:
BaseElement
+ StringElement
+ ....
+ ....
+ BooleanElement
+ ....
+ ....
+ ...
I have a class interface in the form:
IBaseElementService createElementService(Class<? extends BaseElement> element);
IBooleanElementService createElementService(Class<? extends BooleanElement> element);
This compiles well in eclipse 3.4 but not anymore with eclipse 3.6, failing with the error:
Method ... has the same erasure createElementService(Class<T>) as another method in this type
I'm a little puzzled why this compiles under eclipse 3.4 since the type is removed by the java compiler. But anyways, is there an elegant way to change this without renaming the methods?
Thanks!
EDIT: As it was pointed out by multiple people, this seems to be an eclipse 3.4 - 3.5 bug. eclipse bug report (Thanks denis.solonenko for the link!)
If someone is interested about technical details of this bug, make sure to read the post from PaĆ­lo Ebermann, thx!
Renamed the methods. (but why do you have two methods? maybe the 1st one should be the only public one; it can check the class type and forward to the 2nd method for BooleanElement)
By current language spec, your two methods should compile. see here. I heard in Java 7 such 2 methods cannot coexist anymore. Not sure about the rationale.
You can add a generic to IBaseElementService.
Inside the creaseElementService you need to do some checking to return the right elementservice.
IBaseElementService<T> createElementService(Class<T extends BaseElement> elementClass) {
if (elementClass.equals(BooleanElement.class))
return new IBooleanElementService();
return new IBaseElementService();
}
public class IBooleanElementService implements IBaseElementService<BooleanElement> { ... }
public class IBaseElementService implements IBaseElementService<BaseElement> { ... }
On the VM level, methods signatures include the return type as well. Thus, your two methods have the signature
createElementService(Class):IBooleanElementService and createElementService(Class):IBaseElementService. As long as the compiler knows which method to call, it can put the call to the right method in the bytecode. I think this is what Eclipse 3.4 did.
On the Java language level, a method is differentiated only by name and argument types. Here your methods have the signatures createElementService(Class<? extends BooleanElement>) and createElementService(Class<? extends BaseElement>). But the language specification says about generic types that they will be erased - they both get erased to createElementService(Class), and now they are not different at all, which is not permitted.
I think the 3.4 compiler had a bug here (i.e. it compiled a language which was not really Java).

Java: Cyclic generic type relation doesn't allow cast from supertype (javac bug)

I encounter a totally strange behavior of the Java compiler.
I can't cast a supertype to a subtype when cyclic generic type
relation is involved.
JUnit test case to reproduce the problem:
public class _SupertypeGenericTest {
interface ISpace<S extends ISpace<S, A>, A extends IAtom<S, A>> {
}
interface IAtom<S extends ISpace<S, A>, A extends IAtom<S, A>> {
}
static class Space
implements ISpace<Space, Atom> {
}
static class Atom
implements IAtom<Space, Atom> {
}
public void test() {
ISpace<?, ?> spaceSupertype = new Space();
IAtom<?, ?> atomSupertype = new Atom();
Space space = (Space) spaceSupertype; // cast error
Atom atom = (Atom) atomSupertype; // cast error
}
}
Compiler error output:
_SupertypeGenericTest.java:33: inconvertible types
found : pinetag.data._SupertypeGenericTest.ISpace<capture#341 of ?,capture#820 of ?>
required: pinetag.data._SupertypeGenericTest.Space
Space space = (Space) spaceSupertype;
^
_SupertypeGenericTest.java:34: inconvertible types
found : pinetag.data._SupertypeGenericTest.IAtom<capture#94 of ?,capture#48 of ?>
required: pinetag.data._SupertypeGenericTest.Atom
Atom atom = (Atom) atomSupertype;
^
2 errors
Note: I'm using Netbeans latest trunk, bundled Ant, latest Java 6 release.
I tried using Ant from command line (Netbeans generates a build.xml file)
but it results in same errors.
What is wrong?
Is there an elegant way solve the problem?
The strange thing is: Netbeans doesn't mark errors (not even warnings)
in given code.
EDIT:
No, now I understand nothing!
Eclipse 3.4.1 doesn't mark neither warnings nor errors, and compiles
the code without trouble!!!
How can this be? I thought, using Ant from command line along with
build.xml provided by Netbeans' would be neutral.
Am I missing something?
EDIT 2:
Using JDK7 library and JDK7 code format, netbeans compiles without
errors/warnings!
(I'm using 1.7.0-ea-b55)
EDIT 3:
Changed title to indicate that we're dealing with a javac bug.
I don't claim to easily understand those complex generic types, but if you find some code that compiles in javac and doesn't in ecj (the eclipse compiler), then file a bug report with both Sun and Eclipse and describe the situations cleary (best if you also mention that you filed both bug reports and mention their respective URLs, although for Sun it may take a while before the bug is publicly accessible).
I've done that in the past and got really good responses where
one of the teams figured out what the correct approach was (give compile error, warning or nothing)
and the faulty compiler was fixed
Since both compilers implement the same spec, one of them is by definition wrong, if only one of them compiles the code.
For the record:
I tried to compile the sample code with javac (javac 1.6.0_13) and ecj (Eclipse Java Compiler 0.894_R34x, 3.4.2 release) and javac complained loudly and failed to produce any .class files, while ecj only complained about some unused variables (warnings) and produced all the expected .class files.
I've ended up using non-generics for this:
#Test
public void test() {
ISpace spaceSupertype = new Space();
IAtom atomSupertype = new Atom();
Space space = (Space) spaceSupertype; // ok
Atom atom = (Atom) atomSupertype; // ok
}
what holds you back from using the types without wildcards?
public void test() {
ISpace<Space, Atom> spaceSupertype = new Space();
IAtom<Space, Atom> atomSupertype = new Atom();
Space space = (Space) spaceSupertype; // no error
Atom atom = (Atom) atomSupertype; // no error
}
that way it reads much clearer, plus it compiles and runs :)
i think this would be "an elegant way solve the problem"
The problem might be that you're trying to cast IAtom<?, ?> to Atom (which is an Atom<Atom, Space>). How the heck is the system supposed to know that ? might be Atom and Space or not?
When you don't know the types to stick in where the generics are filled, you usually just leave off the entire thing, as in
ISpace spaceSupertype = new Space();
That generates a compiler warning (not error), but your code will still execute (though if the actual type isn't cast compatible, you'll get a runtime error).
This whole thing doesn't make sense to look at, though. You say you need strong typing, then you stick ? where the types go. Then you turn around and try to cast them.
If you need to be able to cast them to Space and Atom, you should probably just use those to start with. If you can't because you're going to stick other types in those variables eventually, your code is going to break like all heck when you change the runtime type anyway, unless you use a bunch of if/then statements (like at the end of this comment).
Really, though, if you're doing stuff this strange, I think we're looking at poor code design. Rethink how you're structuring this. Maybe you need other classes or interfaces to fulfill the functionality that you've got here.
Ask yourself, "Do I really need the strong types? What does it gain me?" It better not add fields since you're using interfaces. (Keep in mind that if the fields are only accessed through methods, then you're only adding methods to the public interface.) If it adds methods, then using the single interfaces IAtom and ISpace here is a bad idea because you'll only be able to use test() with that subtype anyway. test() won't generalize to other implementations of ISpace/IAtom. If all the implementations you'll be putting in here have the same methods you need for test() but not all implementations of IAtom/ISpace have them, you need an intermediate subinterface:
public interface IAtom2 extends IAtom
{
[additional methods]
}
Then you can use IAtom2 instead of IAtom in test(). Then you've automatically got the typing you need and don't need the generics. Remember, if a set of classes all have a common public interface (set of methods and fields), that public interface is a good candidate for a supertype or interface to have. What I'm describing is something like the parallelogram, rectangle, square relationship, where you're trying to skip the rectangle part.
If you're not going to redesign, the other idea is that you drop the cyclic generics and just do instance testing via instanceof:
if (spaceSupertype instanceof Space)
{
Space space = (Space)spaceSupertype;
...
}
else
...

Categories