4 things you didn't know about the labeled statement in Java
Welcome to Objectos Weekly issue #022.
Earlier this week, I saw a tweet from the Red Hat Java team. It shows a variation of "Puzzle 22: Dupe of URL" from the Java Puzzlers book by Joshua Bloch and Neal Gafter. The book was originally published in 2005. So, depending on how long you've been programming in Java, chances are the code did not puzzle you; it is a label from a labeled statement immediately followed by an end-of-line comment.
Though this might have been one leaning towards the trivial side, I must say I am not a fan of code puzzlers. It is ironic since I do own a copy of the book (whose name is "Java Puzzlers"). I think the main reason is that I am very bad at solving them.
On the other hand, I do like to learn and write about the little known bits of the language.
So, in this issue, I will show 4 things you maybe didn't know about the labeled statement in Java.
Let's begin.
1. You can label almost all statements
The first thing to know about labeled statements is that you can put a label on almost any Java statement. There is one exception (or two) which we will see in a bit.
This means that labels are not restricted to e.g. for
or while
statements.
Though, to be fair, labels have no practical use in most of the other statements.
Consider the following Java program:
import static java.lang.System.out;
public class LabeledStatement {
public static void main(String[] args) {
question: out.println("Does this compile?");
branch: if (args.length > 0) {
out.println("It has args!");
} else {
out.println("No args...");
}
assertion: assert args != null : "uh-oh";
empty: ;
block: {
break block;
}
now: break now;
sync: synchronized (LabeledStatement.class) {
out.println("Synchronized statement");
break sync;
}
end: out.println(new LabeledStatement());
}
@Override
public String toString() {
toString: return "Labeled statements!";
}
}
Notice how each statement in the program has a label. Even the empty statement has a label.
It compiles without errors.
When using javac
it also compiles without warnings.
When using the Eclipse JDT compiler you need to suppress the unused
compiler warnings.
It also executes normally. When executed it prints:
Does this compile?
No args...
Synchronized statement
Labeled statements!
This is all defined in the Java Language Specification (JLS).
It is all in the JLS
The labeled statement is defined in Section 14.7 of the JLS:
LabeledStatement:
Identifier : Statement
LabeledStatementNoShortIf:
Identifier : StatementNoShortIf
So any Statement
can be labeled.
But what exactly is under the Statement
production?
It is defined in Section 14.5 of the JLS:
Statement:
StatementWithoutTrailingSubstatement
LabeledStatement
IfThenStatement
IfThenElseStatement
WhileStatement
ForStatement
StatementNoShortIf:
StatementWithoutTrailingSubstatement
LabeledStatementNoShortIf
IfThenElseStatementNoShortIf
WhileStatementNoShortIf
ForStatementNoShortIf
StatementWithoutTrailingSubstatement:
Block
EmptyStatement
ExpressionStatement
AssertStatement
SwitchStatement
DoStatement
BreakStatement
ContinueStatement
ReturnStatement
SynchronizedStatement
ThrowStatement
TryStatement
YieldStatement
So any of the statements listed can be labeled.
2. You can label the substatement of a statement
Some Java statements contain other statements.
For example, one of the productions for the while
statement is the following:
WhileStatement:
while ( Expression ) Statement
Notice how it contains a substatement.
So the following Java while
statement is valid:
while (!interrupted())
action: execute();
This while
statement has a labeled statement as its substatement.
You can do the same with other statements.
An if
statement:
if (enabled())
whenTrue: out.println("It is enabled!");
else
whenFalse: out.println("Not enabled...");
A for
statement:
for (int i = 0; i < args.length; i++)
print: out.println(args[i]);
Which brings us to the labeled statement itself.
3. You can label a labeled statement
So a (sort-of) corollary of items #1 and #2 is that you can label a labeled statement.
The following is valid Java code:
String question;
to: be: or: not: that: is: the: question = "What?";
out.println(question);
The expression statement question = "What?"
is labeled with the:
.
Which in turn is labeled with is:
.
And so on.
This code compiles. When executed it prints:
What?
4. You cannot label a local variable declaration statement
Go back to the Statement
production shown earlier.
You may notice it does not define the local variable declaration statement.
The latter is defined a bit earlier in the JLS. Section 14.2 defines blocks and the following productions:
Block:
{ [BlockStatements] }
BlockStatements:
BlockStatement {BlockStatement}
BlockStatement:
LocalClassOrInterfaceDeclaration
LocalVariableDeclarationStatement
Statement
As we can see, local variable statements are not Statement
but BlockStatement
instead.
And as they are not Statement
they cannot be labeled.
Nor can local class or interface declarations.
To illustrate consider the following Java code:
// does not compile!!!
variable: String msg = "Cannot be labeled...";
local: class Local {
@Override
public String toString() {
return msg;
}
}
It fails to compile:
$ javac src/main/java/post/Item4.java
src/main/java/post/Item4.java:14: error: variable declaration not allowed here
variable: String msg = "Cannot be labeled...";
^
src/main/java/post/Item4.java:16: error: class, interface or enum declaration not allowed here
local: class Local {
^
2 errors
To fix the compilation errors, we just need to remove the labels.
Just because you can doesn't mean you should
While the examples given in this article are valid Java code:
-
they are not idiomatic; and
-
have no practical use.
So you should not write Java code like this unless you have a good reason to.
It is just an interesting piece of information about the Java language.
Examples from the JDK source code
So how does one use the labeled statement in "real life" code?
Here is an example from the java.util.ArrayList
class:
public boolean remove(Object o) {
final Object[] es = elementData;
final int size = this.size;
int i = 0;
found: {
if (o == null) {
for (; i < size; i++)
if (es[i] == null)
break found;
} else {
for (; i < size; i++)
if (o.equals(es[i]))
break found;
}
return false;
}
fastRemove(es, i);
return true;
}
It labels a block in order to break out of it.
The next example comes from the java.util.Collections
class:
nextCand:
for (int candidate = 0; candidate <= maxCandidate; candidate++) {
for (int i=0, j=candidate; i<targetSize; i++, j++)
if (!eq(target.get(i), source.get(j)))
continue nextCand; // Element mismatch, try next cand
return candidate; // All elements of candidate matched target
}
In this case there is a for
statement nested in another one.
The label allows breaking out of the outer loop from the inner loop.
The following is from the java.io.BufferedReader
class:
bufferLoop:
for (;;) {
...
charLoop:
for (i = nextChar; i < nChars; i++) {
c = cb[i];
if ((c == '\n') || (c == '\r')) {
if (term != null) term[0] = true;
eol = true;
break charLoop;
}
}
...
}
So, if I understand it correctly, the labels were used just to make it clear which loop the break
refers to.
In other words, and in this case, the labels are not strictly required.
If you're interested, I used this program to look for these examples.
Until the next issue of Objectos Weekly
So that's it for today. I hope you enjoyed reading.
The source code of all of the examples are in this GitHub repository.
Please send me an e-mail if you have comments, questions or corrections regarding this post.