The classic goto
has different forms with various restrictions on them. Be it return
, try{} catch{}
, throw
, break
, or continue
. They are all really goto
with some additional bits around it.
One could refactor the continue
or break
into another looping style with return:
for (int i = 0; i <= max; i++) {
if(someTest) { continue; }
if(someOtherTest) { continue; }
doStuff;
}
Can be written as:
for (int i = 0; i <= max; i++) {
func();
}
....
void func() {
if(someTest) { return; }
if(someOtherTest) { return; }
doStuff;
}
Which is easier to read? Well, I'd tend to argue that the first is because the second loses the context of the loop... but they're otherwise the same. Its just that people tend to see less of continue
than they do of return
in everyday code.
From Java Tutorials : Branching Statements break
and continue
when used with labels have a bit more power than most people realize. It also makes the code a bit more explicit as to which loop is being acted upon by the control statement. Consider:
class ContinueWithLabelDemo {
public static void main(String[] args) {
String searchMe = "Look for a substring in me";
String substring = "sub";
boolean foundIt = false;
int max = searchMe.length() - substring.length();
test:
for (int i = 0; i <= max; i++) {
int n = substring.length();
int j = i;
int k = 0;
while (n-- != 0) {
if (searchMe.charAt(j++) != substring.charAt(k++)) {
continue test;
}
}
foundIt = true;
break test;
}
System.out.println(foundIt ? "Found it" : "Didn't find it");
}
}
Note that the continue
and the break
are used with a label that specifies what loop they continue or break to (the label on break
isn't necessary because its only one level, but helps in the comprehension of the code).
If you really wanted to, you could refactor this code to:
public class Demo {
public static void main(String[] args) {
String searchMe = "Look for a substring in me";
String substring = "sub";
boolean foundIt;
int max = searchMe.length() - substring.length();
foundIt = loop2(searchMe, substring, max);
System.out.println(foundIt ? "Found it" : "Didn't find it");
}
private static boolean loop2(String searchMe, String substring, int max) {
for (int i = 0; i <= max; i++) {
if (loop(searchMe, substring, i)) {
return true;
}
}
return false;
}
private static boolean loop(String searchMe, String substring, int j) {
int n = substring.length();
int k = 0;
while (n-- != 0) {
if (searchMe.charAt(j++) != substring.charAt(k++)) {
return false;
}
}
return true;
}
}
(I think I did that refactoring right though there are certainly bits in there to grumble about) ... but I'm really not sure what that gains you. And those two methods with multiple returns will draw the wrath of the "multiple returns are an anti-pattern" crowd (which have just as valid points as the continue
and break
are anti-patterns crowd - and in the above case, likely more justified wrath too). Yes, this could again be written in a way that makes use of other tests that will make the "arrow is an anti-pattern" people look at you funny.
continue
is a guard statement in a loop. break
is a early return.
Because something isn't used much doesn't make it an anti pattern. It just means that people aren't as familiar with it.
As an aside, goto
is seeing a bit of a comeback in C# where it can be used with restrictions (note that C# doesn't have a labeled break
like Java does and thus without going through the hoops of extracting methods, this is the way to write the code).