(Updated November 19, 2024)
Short-Circuit Evaluation – Advanced Example
The Problem
As we know, with the conditional operators &&
(and) and ||
(or), we can join multiple Boolean expressions together to form a more complex expression. You may also recall that these operators operate in a mode known as short-circuit. This means that once truth is established, the evaluation will not proceed further.
This is both a blessing and a curse, depending on your perspective. Consider a loop that needs to be simple and fast. This loop needs to trim non-letter/non-digit characters from the ends of a string, like in the following:
"word" becomes word or "What!" becomes What or /****/ becomes null
The Design
One approach could be to set pickets at each end of the string, move them inward toward the center as needed, and then take the substring()
of the result. The following shows the setup:
0 1 2 3 4 5 +-----------------------+ s -> | " | W | o | r | d | " | +-----------------------+ b e --> <--
Essentially, we have a string s
and variables b
and e
(begin and end) to represent the extreme ends of the string s
.
Some source code to produce the setup might look something like this:
public static String removeNonLetDig(String s) {
int b = 0, e = s.length()-1;
// Do work here...
}
Since we should move only one picket at a time, we will consider two loops - one for each end - moving a picket toward the center. The next picture depicts the result we are striving for:
0 1 2 3 4 5 +-----------------------+ s -> | " | W | o | r | d | " | +-----------------------+ b e
The loops are simple - move the picket as long as we view a non-letter/non-digit character. The isLetterOrDigit()
method of the Character
wrapper class will do nicely. We will want a condition like:
!Character.isLetterOrDigit(s.charAt(b))
Crossing over of the indexes b and e means we've run out of string to work with, so we should consider that ( b <= e
or e >= b
).
Now, we just move the pickets to the appropriate positions with something like the following:
public static String removeNonLetDig(String s) {
int b = 0, e = s.length()-1;
// Trim from the beginning
while (!Character.isLetterOrDigit(s.charAt(b)) && b <= e)
b++;
// Trim from the end
while (!Character.isLetterOrDigit(s.charAt(e)) && e >= b)
e--;
if (b <= e)
return s.substring(b, e + 1);
else
return null;
}
The code for the method removeNonLetDig()
is small, concise, and perhaps even clever in its simplicity. But we are about to reveal a detail in testing that has been overlooked up to this point.
The New Problem
The following code is complete for testing the new method and includes reasonable test values with predictable results based on the initial discussion at the beginning of the post.
public class ShortCircuit {
// Remove punctuation from ends of a string
public static String removeNonLetDig(String s) {
int b = 0, e = s.length()-1;
// Trim from the beginning
while (!Character.isLetterOrDigit(s.charAt(b)) && b <= e)
b++;
// Trim from the end
while (!Character.isLetterOrDigit(s.charAt(e)) && e >= b)
e--;
if (b <= e)
return s.substring(b, e + 1);
else
return null;
}
public static void main(String[] args) {
String w = "\"word\"";
String x = "\"What!\"";
String p = "/******/";
System.out.println(w + " = " + removeNonLetDig(w));
System.out.println(x + " = " + removeNonLetDig(x));
System.out.println(p + " = " + removeNonLetDig(p));
}
}
The conditions specified in the while loops have been crafted based on our thought process from the design. The problem is when we get to main()
method, we will call removeNonLetDig()
with a value that will cause a StringIndexOutOfBoundsException
in the first while
loop.
Why?
The charAt(b)
method is invoked before we validate that the value of b
is within the supported range.
How are we checking for b
in the valid range?
With the b <= e
test. But it appears later in the condition.
Does that matter?
In this case, very much so. We should rely on the nature of short-circuit evaluation to help us avoid this run-time error. If b > e
, we don't want to try and get a character from the string since we know we would be out of bounds. Having the b <= e
first and evaluate to false
on an && operator means the second half will be ignored - because it only matters if the first half is true
!
The final, corrected version is shown below with the while
loops modified:
public class ShortCircuit {
// Remove punctuation from ends of a string
public static String removeNonLetDig(String s) {
int b = 0, e = s.length()-1;
// Trim from the beginning
while (b <= e && !Character.isLetterOrDigit(s.charAt(b)))
b++;
// Trim from the end
while (e >= b && !Character.isLetterOrDigit(s.charAt(e)))
e--;
if (b <= e)
return s.substring(b, e + 1);
else
return null;
}
public static void main(String[] args) {
String w = "\"word\"";
String x = "\"What!\"";
String p = "/******/";
System.out.println(w + " = " + removeNonLetDig(w));
System.out.println(x + " = " + removeNonLetDig(x));
System.out.println(p + " = " + removeNonLetDig(p));
}
}
Additional Thoughts
Consider how you would fix this problem if we did not have a short-circuit evaluation.
One solution could be introducing a boolean
value to indicate we should continue. The highlighted code shows what is needed for just one of the loops.
public static String removeNonLetDig(String s) {
int b = 0, e = s.length()-1;
boolean ok;
// Trim from the beginning
ok = true;
while (ok && b <= e) {
if (!Character.isLetterOrDigit(s.charAt(b)))
b++;
else
ok = false;
}
// repeat for the other end
}
Another solution could be to use break
to get us out of the loop sooner. Again, the highlighted code shows the changes to just one of the loops.
public static String removeNonLetDig(String s) {
int b = 0, e = s.length()-1;
// Trim from the beginning
while (b <= e) {
if (!Character.isLetterOrDigit(s.charAt(b)))
b++;
else
break;
}
// repeat for the other end
}