is there a good reason for this design decision?
Yes. Because it works.
In the labelled break case, the fact that you don't need to be inside a loop or switch lets you to express things that are harder to express in other ways. (Admittedly, people rarely do use labelled break this way ... but that's not a fault of the language design.)
In the unlabelled break case, the behavior is to break out of the innermost enclosing loop or switch. If it was to break out of the innermost enclosing statement, then a lot of things would be much harder to express, and many would probably require a labelled block. For example:
while (...) {
/* ... */
if (something) break;
/* ... */
}
If break
broke out of the innermost enclosing statement, then it wouldn't break out of the loop.
There is another possible reason / rationale. Remember that Java was a brand new language and a relatively early adopter of exceptions and exception handling.
Consider this:
try {
/* ... code ... */
if (doneExecutingThisBlock())
throw new OuttaHere();
/* ... more code ... */
} catch (OuttaHere e) {
/* do nothing */
}
According to the dogma, that is bad code. You shouldn't use exceptions for "normal" flow control.
(Pragmatically, that it also very inefficient due to the overheads of exception creation and handling. Exceptions performance was improved significantly in Java 8, I think, but that was ~20 years later.)
Now imagine that you are a language designer, and you feel that you have to provide an alternative to the "exceptions as flow control" anti-pattern. The "break
to label" construct does exactly that. Compare the above with the example in the question.
In hindsight, this is unnecessary. The above can be done in other ways; i.e. without labelled break. In practice this construct is used so rarely that many (maybe most) programmers don't even know it exists in Java.