I have the following piece of code-
{s = "Hello";}
String s;
This compiles fine, implying that the variable definitions are executed before the instance blocks.
However, if I use the following code instead, it does not compile ("error: illegal forward reference").
{s = "Hello"; String ss = s;}
String s;
So it is not possible to use the value of 's' on the right-hand side of a statement in an instance block that comes BEFORE the variable
definition. Is there a sane explanation of what is happening behind the scenes, or is this simply an idiosyncratic feature of Java?
P.S. I have seen a similar question asked before, the only explanation given there is that it is a feature of Java.
I am writing this to ask the community if that is indeed the final word on this question.
JLS ยง8.3.3 ("Forward References During Field Initialization") sheds some light here:
Use of instance variables whose declarations appear textually after the use is sometimes restricted, even though these instance variables are in scope. Specifically, it is a compile-time error if all of the following are true:
The declaration of an instance variable in a class or interface C appears textually after a use of the instance variable;
The use is a simple name in either an instance variable initializer of C or an instance initializer of C;
The use is not on the left hand side of an assignment;
C is the innermost class or interface enclosing the use.
The first bullet would apply to your example.
As for the "why" part, "why" is usually a tricky question with language design, but in this case they helpfully added this note further down:
The restrictions above are designed to catch, at compile time, circular or otherwise malformed initializations.
In second case you will get compilation error IllegalForwardReference due to the fact that usage of the variable s is not on the left hand side of an assignment and JLS explicitly states it as one of the reason to get IllegalForwardReference error
I just can tell you this: The compiler moves all the initialization code to the end of the constructors' body (repeating code for every constructor). Decompiling an example would show it off.
So, you must realise that, in this way, the declaration order of some variable matters when evaluating it as a right-hand side, even if it doesn't matter when evaluating it as a left-hand side.
One way to get rid off these incoherences is to pre-pend the scope "this" to every instance variable.
Related
I just started coding. I want to use a switch statement twice for the same variable, and I was told that to do this the variable would have to be 'in scope'.
Being a beginner, I have no idea what that means. So what does being in scope mean? And, if a variable isn't in scope, how do I make it in scope?
A local variable1 is "in scope" if code can access it and out of scope if it can't. In Java, variables are scoped to the block ({}) they're declared in. So:
void foo() {
int a = 42;
if (/*some condition*/) {
String q = "Life, the Universe, and Everything";
// 1. Both `a` and `q` are in scope here
System.out.println(a);
System.out.println(q);
if (/*another condition*/) {
// 2. Both `a` and `q` are in scope here, too
System.out.println(a);
System.out.println(q);
}
}
// 3. Only `a` is in scope here
System.out.println(a);
System.out.println(q); // ERROR, `q` is not in scope
}
Note (1), (2), and (3) above:
The code can access q because q is declared in the same block as the code; tt can access a because it's declared in the containing block.
The code can access q because it's declared in the containing block; it can access a because it's in the next block out.
The code can access a, but not q, because q isn't declared in the block or any of the blocks (or a couple of other things) containing it.
When figuring out what an unqualified identifier (like a or q above, as opposed to the foo in this.foo or the toLowerCase in q.toLowerCase, which are qualified) is, the Java compiler will look in each of these places, one after the other, until it finds a match:
For a variable with that name in the innermost block
For a variable with that name in the next block out, and so on
For a field2 or method (generally: member) with that name in the current class
For a class with that name from a package that's been imported
For a package with that name
There are a few others for that list (I'm not going to get into static imports with a beginner).
There's a lot more to scope, I suggest working through some tutorials and/or a beginning Java book for more.
1 "local variable" vs. "variable" - The Java Language Specification uses "variable" in a more general way than most people do in common speech. When I say "variable" in this answer, I mean what the JLS calls a "local variable".
2 "field" - The JLS calls fields "variables" in some places (and "fields" in other places), hence (1) above. :-)
From Section 6.3 of the Java Language Specification:
The scope of a declaration is the region of the program within which the entity declared by the declaration can be referred to using a simple name, provided it is visible.
This concept of scope applies to many kinds of entities in Java: everything from local variables to top-level classes and packages. Even when just talking about variables, there are many cases, from local variables to statically imported fields from another class to the parameter of an exception handler in a catch clause of a try statement. For details, read the JLS or search the web for "Java scope" and read one or more of the many tutorials on the subject that show up.
I just started coding. I want to use a switch statement twice for the same variable, and I was told that to do this the variable would have to be 'in scope'.
Being a beginner, I have no idea what that means. So what does being in scope mean? And, if a variable isn't in scope, how do I make it in scope?
A local variable1 is "in scope" if code can access it and out of scope if it can't. In Java, variables are scoped to the block ({}) they're declared in. So:
void foo() {
int a = 42;
if (/*some condition*/) {
String q = "Life, the Universe, and Everything";
// 1. Both `a` and `q` are in scope here
System.out.println(a);
System.out.println(q);
if (/*another condition*/) {
// 2. Both `a` and `q` are in scope here, too
System.out.println(a);
System.out.println(q);
}
}
// 3. Only `a` is in scope here
System.out.println(a);
System.out.println(q); // ERROR, `q` is not in scope
}
Note (1), (2), and (3) above:
The code can access q because q is declared in the same block as the code; tt can access a because it's declared in the containing block.
The code can access q because it's declared in the containing block; it can access a because it's in the next block out.
The code can access a, but not q, because q isn't declared in the block or any of the blocks (or a couple of other things) containing it.
When figuring out what an unqualified identifier (like a or q above, as opposed to the foo in this.foo or the toLowerCase in q.toLowerCase, which are qualified) is, the Java compiler will look in each of these places, one after the other, until it finds a match:
For a variable with that name in the innermost block
For a variable with that name in the next block out, and so on
For a field2 or method (generally: member) with that name in the current class
For a class with that name from a package that's been imported
For a package with that name
There are a few others for that list (I'm not going to get into static imports with a beginner).
There's a lot more to scope, I suggest working through some tutorials and/or a beginning Java book for more.
1 "local variable" vs. "variable" - The Java Language Specification uses "variable" in a more general way than most people do in common speech. When I say "variable" in this answer, I mean what the JLS calls a "local variable".
2 "field" - The JLS calls fields "variables" in some places (and "fields" in other places), hence (1) above. :-)
From Section 6.3 of the Java Language Specification:
The scope of a declaration is the region of the program within which the entity declared by the declaration can be referred to using a simple name, provided it is visible.
This concept of scope applies to many kinds of entities in Java: everything from local variables to top-level classes and packages. Even when just talking about variables, there are many cases, from local variables to statically imported fields from another class to the parameter of an exception handler in a catch clause of a try statement. For details, read the JLS or search the web for "Java scope" and read one or more of the many tutorials on the subject that show up.
I just made a small code change to silence a FindBugs warning which required moving some code to an anonymous inner class. In order to access some variables, I had to declare those as final. So this is the code snippet after the change:
final File[] libPath; // libPath is final but assignment takes place later
if (libraryPath != null) {
libPath = pathToFiles(libraryPath);
} else {
libPath = new File[0];
}
This compiles just fine with language set to Java 6 in current Eclipse (Version 3.7.1). However I'm quite sure this used to give an error in some previous version. Seems the compiler accepts this construct when it can determine that there will be.
My question is: is this legal in Java 6 or is it something that now works due to a side effect of Java 7 support being added to eclipse 3.7.1? We have seen such side effects with certain usage of generics that works in 3.7.1 but didn't compile in 3.7.0.
this is ok. it is called blank final
quote from wiki:
A final variable can only be initialized once, either via an
initializer or an assignment statement. It need not be initialized at
the point of declaration: this is called a "blank final" variable. A
blank final instance variable of a class must be definitely assigned
at the end of every constructor of the class in which it is declared;
similarly, a blank final static variable must be definitely assigned
in a static initializer of the class in which it is declared:
otherwise, a compile-time error occurs in both cases. [4] (Note: If
the variable is a reference, this means that the variable cannot be
re-bound to reference another object. But the object that it
references is still mutable, if it was originally mutable.)
Blank final
The blank final, which was introduced in Java 1.1, is a final variable
whose declaration lacks an initializer. [5][6] A blank final can only
be assigned once and must be unassigned when an assignment occurs. In
order to do this, a Java compiler runs a flow analysis to ensure that,
for every assignment to a blank final variable, the variable is
definitely unassigned before the assignment; otherwise a compile-time
error occurs.[7]
In general, a Java compiler will ensure that the blank final is not
used until it is assigned a value and that once assigned a value, the
now final variable cannot be reassigned another value.[8]
link: http://en.wikipedia.org/wiki/Final_%28Java%29
This was allowed and worked fine since Java 1.1 and will not get you in trouble with other compilers or IDEs.
It is standard behaviour in Java and was first formally specified in the Java Language Specification 2nd Edition.
Java Language Specification contains a whole chapter dedicated to this behaviour (Chapter 16
Definite Assignment).
This behaviour is thoroughly defined, so that I think you misinterpret something when you say that used to produce an error in previous versions.
It's fine. The variable does not have a value, and is assigned only once. It would fail if you have given it a null value initially.
I'd strongly suggest to use this code instead:
final File[] libPath = ibraryPath == null ? new File[0] : pathToFiles(libraryPath);
This does not depend on any compiler version, but is 100% supported Java with a clear meaning.
Yes, this will work and is safe to use in all java versions I've seen (1.3+).
final means that you cannot change the value of the object once it has been initialized, if you placed a null upon declaration it would've broke.
I have this code
private static Set<String> myField;
static {
myField = new HashSet<String>();
myField.add("test");
}
and it works. But when I flip the order, I get an illegal forward reference error.
static {
myField = new HashSet<String>();
myField.add("test"); // illegal forward reference
}
private static Set<String> myField;
I'm a little bit shocked, I didn't expect something like this from Java. :)
What happens here? Why is the order of declarations important? Why does the assignment work but not the method call?
First of all, let's discuss what a "forward reference" is and why it is bad. A forward reference is a reference to a variable that has not yet been initialized, and it is not confined only to static initalizers. These are bad simply because, if allowed, they'd give us unexpected results. Take a look at this bit of code:
public class ForwardRef {
int a = b; // <--- Illegal forward reference
int b = 10;
}
What should j be when this class is initialized? When a class is initialized, initializations are executed in order the first to the last encountered. Therefore, you'd expect the line
a = b;
to execute prior to:
b = 10;
In order to avoid this kind of problems, Java designers completely disallowed such uses of forward references.
EDIT
this behaviour is specified by section 8.3.2.3 of Java Language Specifications:
The declaration of a member needs to appear before it is used only if the member is an instance (respectively static) field of a class or interface C and all of the following conditions hold:
The usage occurs in an instance (respectively static) variable initializer of C or in an instance (respectively static) initializer of C.
The usage is not on the left hand side of an assignment.
C is the innermost class or interface enclosing the usage.
A compile-time error occurs if any of the three requirements above are not met.
try this:
class YourClass {
static {
myField = new HashSet<String>();
YourClass.myField.add("test");
}
private static Set<String> myField;
}
it should compile without errors according the JLS...
(don't really help, or?)
In Java, all initializers, static or otherwise, are evaluated in the order in which they appear in the class definition.
See the rules for forward references in the JLS. You cannot use forward references if:
The usage occurs in an instance (respectively static) variable initializer of C or in an instance (respectively static) initializer of C.
The usage is not on the left hand side of an assignment.
The usage is via a simple name.
C is the innermost class or interface enclosing the usage.
Since all of these hold for your example, the forward reference is illegal.
To elaborate on DFA's answer:
I think what's tripping you up is the "left hand side" rule in the second bullet point in JLS 8.2.3.2. In your initialization, myField is on the left-hand side. In your call to add, it's on the right-hand side. The code here is implicitly:
boolean result = myField.add('test')
You're not evaluating the result, but the compiler still acts as if it's there. That's why your initialization passes while your call to add() fails.
As for why this is so, I have no idea. It may well be for the convenience of the JVM developers, for all I know.
I think the method call is problematic because the compiler cannot determine which add() method to use without a reference type for myField.
At runtime, the method used will be determined by the object type, but the compiler only knows about the reference type.
Consider this:
public class TestClass {
private String a;
private String b;
public TestClass()
{
a = "initialized";
}
public void doSomething()
{
String c;
a.notify(); // This is fine
b.notify(); // This is fine - but will end in an exception
c.notify(); // "Local variable c may not have been initialised"
}
}
I don't get it. "b" is never initialized but will give the same run-time error as "c", which is a compile-time error. Why the difference between local variables and members?
Edit: making the members private was my initial intention, and the question still stands...
The language defines it this way.
Instance variables of object type default to being initialized to null.
Local variables of object type are not initialized by default and it's a compile time error to access an undefined variable.
See section 4.12.5 for SE7 (same section still as of SE14)
http://docs.oracle.com/javase/specs/jls/se7/html/jls-4.html#jls-4.12.5
Here's the deal. When you call
TestClass tc = new TestClass();
the new command performs four important tasks:
Allocates memory on the heap for the new object.
Initiates the class fields to their default values (numerics to 0, boolean to false, objects to null).
Calls the constructor (which may re-initiate the fields, or may not).
Returns a reference to the new object.
So your fields 'a' and 'b' are both initiated to null, and 'a' is re-initiated in the constructor. This process is not relevant for method calling, so local variable 'c' is never initialized.
For the gravely insomniac, read this.
The rules for definite assignment are quite difficult (read chapter 16 of JLS 3rd Ed). It's not practical to enforce definite assignment on fields. As it stands, it's even possible to observe final fields before they are initialised.
The compiler can figure out that c will never be set. The b variable could be set by someone else after the constructor is called, but before doSomething(). Make b private and the compiler may be able to help.
The compiler can tell from the code for doSomething() that c is declared there and never initialized. Because it is local, there is no possibility that it is initialized elsewhere.
It can't tell when or where you are going to call doSomething(). b is a public member. It is entirely possible that you would initialize it in other code before calling the method.
Member-variables are initialized to null or to their default primitive values, if they are primitives.
Local variables are UNDEFINED and are not initialized and you are responsible for setting the initial value. The compiler prevents you from using them.
Therefore, b is initialized when the class TestClass is instantiated while c is undefined.
Note: null is different from undefined.
You've actually identified one of the bigger holes in Java's system of generally attempting to find errors at edit/compile time rather than run time because--as the accepted answer said--it's difficult to tell if b is initialized or not.
There are a few patterns to work around this flaw. First is "Final by default". If your members were final, you would have to fill them in with the constructor--and it would use path-analysis to ensure that every possible path fills in the finals (You could still assign it "Null" which would defeat the purpose but at least you would be forced to recognize that you were doing it intentionally).
A second approach is strict null checking. You can turn it on in eclipse settings either by project or in default properties. I believe it would force you to null-check your b.notify() before you call it. This can quickly get out of hand so it tends to go with a set of annotations to make things simpler:
The annotations might have different names but in concept once you turn on strict null checking and the annotations the types of variables are "nullable" and "NotNull". If you try to place a Nullable into a not-null variable you must check it for null first. Parameters and return types are also annotated so you don't have to check for null every single time you assign to a not-null variable.
There is also a "NotNullByDefault" package level annotation that will make the editor assume that no variable can ever have a null value unless you tag it Nullable.
These annotations mostly apply at the editor level--You can turn them on within eclipse and probably other editors--which is why they aren't necessarily standardized. (At least last time I check, Java 8 might have some annotations I haven't found yet)