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.
Related
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.
This question already has answers here:
How do I call a method of a generic type object?
(4 answers)
Closed 7 years ago.
Consider the following C++ program:
#include <iostream>
using namespace std;
template<typename T>
class example
{
public:
void function (T a)
{
std::cout<<a.size ();
}
};
int main() {
example<string> a; // this doesn't
string b = "a";
//example<int> a; This gives an error
a.function (b);
// your code goes here
return 0;
}
And now consider the following Java program:
import java.util.ArrayList;
class example<T> {
public void function (T a)
{
System.out.println (a.toHexString(5)); /* this does not compile even when T is Integer */
}
}
public class Main
{
public static void main (String[] args)
{
example<Integer> a = new example<Integer> ();
Integer b = 2;
a.function(b);
return;
}
}
I have majorly been a C++ developer until now and am learning Java for job purposes. So, coming from a background having worked with templates, generics confuse me.
Coming to my question:
In the above C++ code, the code compiles and runs fine if I pass string as template parameter because string does have a size () method. If I used int as a template parameter, I would've gotten an error, understandably. The point to note here is that C++ lets me compile and run the code if I pass a template parameter that has a method called size().
However, in the Java code, even when I pass Integer as the generic parameter (? is that a term?) which DOES have toHexString(int) method, the program still does not compile. It returns an error:
cannot find symbol
What's the issue here? What prevents me in Java from achieving this behaviour?
Edit: The question was marked as a possible duplicate for another question:
How do I call a method of a generic type object?
I'll copy paste my response to why I think the question's different.
The above question 'potentially' tells me how to get rid of the error. What I'm asking is what prevents me in Java from achieving the above effect? The said question gives me the medicine of the disease, not the cause.
I raised a similar question on ##java and heard of a new term - reification. I was wondering if it had anything to do with this?
Java generics are implemented via type erasure. When you have a class signature like this:
class example<T> { }
.. The class is compiled as a regular Java class. For this, T effectively takes on the type of its upper bound, in this case Object. If you have a method such as the function in your example, with a parameter of type T:
public void function (T a)
... Then this is, at the point that this function is compiled, almost the same as having the parameter be of type Object. As such, you can't call a method such as toHexString on the parameter, because that method is not defined in Object.
In C++ on the other hand, a lot of symbol resolution happens when the template is instantiated rather than when it is first compiled. This is the key difference; in Java, a generic class is compiled to bytecode, and so method calls etc must be resolved when the generic class is compiled (that is, the compiler must be able to decide what class or interface the method comes from). In C++, when the compiler encounters a template, it does not try to resolve references or produce object code unless and until the template is instantiated.
Another way to think about it: in Java, example<String> and example<Integer> are both implemented via the same class. In C++, they would be two separate classes (both which result from instantiation of the template).
This is, in fact, why Java generic classes are not "templates". In C++, a class template allows to instantiate classes (i.e. it serves as a template from which to create classes). In Java, a generic class allows for parametrized types to be implemented by a single class.
A Java generic class can be considered to be quite similar to a non-generic class with the type parameters (eg T) being replaced with the bound type (Object unless otherwise specified) - the main difference being that the compiler will perform additional type checking when you call methods on an instance of the class (which has a full type with type arguments, such that T maps to some other type), and will effectively insert casts (so that you can call a method which returns a T, via a reference where T is mapped to some type, without having to cast the return type).
The problem is that Java generics is nothing like C++ templates and was never designed to be so. Java generics was designed with one specific target - to add strong type checking at compile time. As such you will find the following an approximation to your Java version.
interface Hex {
public String toHexString(int length);
}
class Example<T extends Hex> {
public void function(T a) {
System.out.println(a.toHexString(5));
}
}
class StringWithHex implements Hex {
#Override
public String toHexString(int length) {
return "Hex";
}
}
public void test() {
Example<StringWithHex> e = new Example<>();
e.function(new StringWithHex());
}
See how it is only ensuring that types match.
I've identified what is at least undesirable behavior and at most a bug in the Sun JDK's handling of reflection on Java enums with an abstract method. I've searched for a bug report and StackOverflow answer for this particular behavior and come up dry. You're more or less always wrong when you think you've found an issue like this in such well-used and carefully-tested code, so please sanity check me and tell me where I've gotten this wrong.
The Code
Consider the following code:
a/Greeting.java
package a;
public enum Greeting {
HELLO {
#Override
public void greet() {
System.out.println("Hello!");
}
};
public abstract void greet();
}
b/EnumTest.java
package b;
import java.lang.reflect.Method;
import a.Greeting;
public class EnumTest {
public static void main(String[] args) throws Exception {
Greeting g=Greeting.HELLO;
Method greet=g.getClass().getMethod("greet");
System.out.println("Greeting "+g.getClass()+" ...");
greet.invoke(g);
System.out.println("Greeted!");
}
}
Also, please note that Greeting and EnumTest are in different packages. (This ends up mattering.)
The Error
When you run this code, you expect to get the following output:
Greeting class a.Greeting ...
Hello!
Greeted!
Instead, you get the following output:
Greeting class a.Greeting$1 ...
Exception in thread "main" java.lang.IllegalAccessException: Class b.EnumTest can not access a member of class a.Greeting$1 with modifiers "public"
at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:95)
at java.lang.reflect.AccessibleObject.slowCheckMemberAccess(AccessibleObject.java:261)
at java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:253)
at java.lang.reflect.Method.invoke(Method.java:594)
at b.EnumTest.main(EnumTest.java:13)
Understanding the Behavior
First, please note that Greeting is public and Greeting$greet is public. (Even the error message indicates public access!) So what's going on?
What The Heck is Going On Here?
If you step through the code, you find that the ultimate "problem" is that sun.reflect.Reflection$verifyMemberAccess() returns false. (So, the Reflection API claims we do not have access to this method.) The particular code that fails is here:
public static boolean verifyMemberAccess(Class currentClass,
// Declaring class of field
// or method
Class memberClass,
// May be NULL in case of statics
Object target,
int modifiers)
// ...
if (!Modifier.isPublic(getClassAccessFlags(memberClass))) {
isSameClassPackage = isSameClassPackage(currentClass, memberClass);
gotIsSameClassPackage = true;
if (!isSameClassPackage) {
return false;
}
}
// ...
Essentially, this method determines whether code in currentClass can see members of memberClass with modifiers of modifiers.
Clearly, we should have access. We're calling a public method in a public class! However, this code returns false, in the indicated return statement. Therefore, the class of the value we're trying to invoke the method on is not public. (We know this because the outer test -- !Modifier.isPublic(getClassAccessFlags(memberClass)) -- passes, since the code reaches the inner return.) But Greeting is public!
However, the type of Greeting.HELLO is not a.Greeting. It's a.Greeting$1! (As careful readers will have noticed above.)
enum classes with one or more abstract methods create child classes under the covers (one for each constant). So what's happening is that the "under the covers" child classes are not marked public, so we're not allowed to see public methods on those classes. Bummer.
Confirmation of the Theory
To test this theory, we can invoke the superclass enum's greet() method on the child instead:
public static void main(String[] args) throws Exception {
Greeting g=Greeting.HELLO;
Method greet=g.getClass().getSuperclass().getMethod("greet");
System.out.println("Greeting "+g.getClass()+" ...");
greet.invoke(g);
System.out.println("Greeted!");
}
...and meet with success:
Greeting class a.Greeting$1 ...
Hello!
Greeted!
Also, if we move a.Greeting to b.Greeting (the same package as b.EnumTest), that works too, even without the getSuperclass() call.
So... Bug or No?
So... is this a bug? Or is this simply undesired behavior that is an artifact of the underlying implementation? I checked the relevant section of the Java Language Specification and this syntax is legal. Also, the specification doesn't specify how child classes will be arranged, so while this technically in violation of the standard (or at least the part of the standard I read), I'm inclined to call this a bug.
What does StackOverflow think: is this a bug, or simply undesired behavior? I realize this is a bit of an unconventional question, so please forgive the format.
Also, I'm on a Mac (in case that matters), and java -version prints the following, for anyone who wants to reproduce:
$ java -version
java version "1.7.0_21"
Java(TM) SE Runtime Environment (build 1.7.0_21-b12)
Java HotSpot(TM) 64-Bit Server VM (build 23.21-b01, mixed mode)
EDIT: Interesting to find a bug open for an similar (at least related) issue since 1997: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4071957
EDIT: Per the answer below, the JLS does say that enum classes with an abstract method shall behave like anonymous classes:
The optional class body of an enum constant implicitly defines an anonymous class declaration (§15.9.5) that extends the immediately enclosing enum type. The class body is governed by the usual rules of anonymous classes
Per the bug above, anonymous class handling has been a "bug" since 1997. So with respect to whether this is actually a bug or not is a bit semantic at this point. Bottom line: don't do this, since it doesn't work and it's not likely to in the future. :)
Not a bug.
As a careful examination of the exception message shows, the problem class is a.Greeting$1. That's an anonymous inner class. The method happens to be public and irrelevantly the enclosing class and the static field it is assigned to are public, but the actual class is non-public.
a.Greeting.class and a.Greeting.HELLO.getClass().getSuperclass() should work.
Therefore, the class of the value we're trying to invoke the method on is not public.
Class.getMethod operates on a class (Class) not a value, so that's irrelevant (unless you were trying to get the Field a.Greeting.HELLO.
EDIT: From the Java Language Specification:
"The optional class body of an enum constant implicitly defines an anonymous class declaration (§15.9.5) that extends the immediately enclosing enum type. The class body is governed by the usual rules of anonymous classes; ..."
So the enum is treated as an anonymous class, and this is how anonymous classes work.
I created classes like below in the same package test. It was just to test whether java really allows it. And it does but then it comes in to problems.
package test;
public class E {
}
package test;
public class T {
}
package test;
import test.E;
import test.T;
public class K<E, T> {
E e;
T t;
public K(E e, T t) {
this.e = e;
this.t = t;
}
public static void main(String[] args) {
K k = new K<E, T>(new E(), new T());
}
}
Above code give multiple compilation problems
Multiple markers at this line
- Cannot make a static reference to the non-static type E
- Cannot make a static reference to the non-static type T
- Cannot make a static reference to the non-static type T
- Cannot make a static reference to the non-static type E
- K is a raw type. References to generic type K<E,T> should be
parameterized
It clearly shows compiler is confused between E and class E same for T.
So workaround is define it real types using package.
K k = new K<test.E, test.T>(new test.E(), new test.T());
Now if there all these classes are in default package there is no way to solve this compilation issue.
So Question is should java allow declaration of such classes in default package?
It clearly shows compiler is confused between E and class E same for T.
I think you've got that wrong. I think that if you read the relevant parts of the JLS carefully (I'll look them up later) you will find that they clearly state what E and T should resolve to in the various contexts. I would be very surprised if the compiler is getting the resolution wrong; i.e. not implementing the JLS.
In reality, the confusion is in the mind of the person who wrote the code ...
The problem here is that the rules about what takes precedence over what are probably not what you (and typical programmers) expect. But they are like they are for a good reason.
Normally this doesn't matter, but if you ignore the normal Java naming conventions and use one-letter names for classes, then you might get burnt.
So Question is should java allow declaration of such classes in default package?
Alternatively, should "you" be ignoring the Java class naming conventions?
Frankly, there are a lot of ways that a programmer can hurt themselves if they ignore the style guidelines / recommendations. But if you try to protect the programmer too much, you actually hurt him/her by making it impossible to implement stuff where you need to push the envelope. The best policy is (IMO) to not treat programmers as children. If they really want to juggle with sharp knives ... let them.
This is very nice research.
But, you are still allowed to create a class with name String, Java never complained against using same class name. To differentiate whether you use your String class (or) Java provided String class, you need to append the package (full name).
As Matt Ball said, default packages are shouldn't be used.
Backward compatibility could be another reason why Generic Types not defined as "reserve words"
Whether allow same class name (or) not, I think that is what packages are for. As long as there is a way to differentiate which class we are referring to, I think it is perfectly fine to allow same name.
You can get really confused if you want to. I am not sure its up to the compiler to prevent you from writing confusing code but I do think the compiler should try to be clear in its error messages.
public class T<T> {
public T() {
}
public static <T> T T() {
T T = null;
return T; // which T is this?
}
}
Considering you can write
public class String<Object>{}
Disalowing class with same name as how YOU named type parameter or forbiding you to name type parameter as any existing class would be insane (Class with conflicting name can be from another jar created in future, so any name of type parameter can be same as name of some class, and vice versa)
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).