09 – Flow Control

We’ve managed to create variables and objects and entire programs so far, but if we were to try and write anything but the simplest code we would find ourselves in trouble without the ability to control program flow. That is, what a program does as a result of certain conditions. The most common of these kinds of operations is the if/else construct.

An if essentially defines two blocks of code, one is executed if the tested condition is true, other other is executed if it is false. The else condition is optional, and can also be connected to another if condition to prevent deeply nesting conditions. The basic if block looks like this:


//...
boolean condition = true;
if (condition) {
  // do something
} else {
  // do something else
}
//...

If we have multiple conditions they could be tested like so:


boolean condition1 = true;
boolean condition2 = true;
if (condition1) {
  // do something 1
} else if (condition2) {
  // do something 2
} else {
  // do something 3
}

Hopefully it is clear that if can only act on values that are boolean. This is another feature of the strict typing of Java compared to other languages, for example Perl is perfectly happy applying a String or int value to an if statement and following a set of rules to guess what you meant in doing so. In Java, if the value of the if statement’s test is anything other than boolean you would get an exception trying to compile the code.

This isn’t necessarily a limitation for Java, it merely forces you to be clear (and admittedly more wordy). For example, if a Perl string is empty or null and you test it with an if statement, that string will evaluate to false. In Java to mean the same thing we just have to express it:


if (myString == null) {
  // do something...
}
if ("".equals(myString)) {
  // do something...
}

Of course you can perform boolean logic and comparisons within the if statement, or even evaluate method calls. For example:


if (boolean1 || boolean2) { // logical or
...
if (boolean1 && boolean2) { // logical and
...
if (!boolean1) { // logical not
...
if (int1 > int2) { // integer comparison
...
if (char1 == char2) { // compare two chars for equality
...
if ((boolean1 || boolean2) && boolean3) { // grouped tests
...
if (myObject.isTrue()) { // method that returns boolean

One important thing to note is that with objects you always want to use the built-in equals() method instead of comparing them with the double = comparison (==). This is important because comparing them with a double equals will not cause a warning, but may not do what you think!

In this example we create two Strings that are equivalent, but we can make them not equal using the double equal test. We see that when we test them using the equals() method the test works correctly:


String a = “test”;
String b = “te”;
String c = b+”st”;
if (a == c) { // This is false!
}
if (a.equals(c)) { // This is true!
}

This may be counterintuitive, but the reason behind it is simple. When applied to objects like strings or any other object the double equals checks to see if the objects are equal, not equivalent. In our example above String a is equivalent to String c, but they are in fact two different objects. Java is not the type of language to do what you mean, so watch your comparisons or sooner or later you will remember this the hard way.

The one time it makes sense to use the double equals with an object is to compare that object to null. That proves to be a good test to determine if an object has been instantiated, and can protect you from NullPointerExceptions (often called NPEs).

There is another trick that can help avoid NPEs. When you want to compare an object to an object you know isn’t null, you can use your known object’s equal method.


String s = "test";
if (s.equals(someString)) {
  // do something...
}

The NPE occurs when you call the .equals() method of the object. If the object is null that method can’t exist. In the above test we don’t need to worry about whether or not someString is null – the test won’t throw an exception (though it certainly won’t return true, and we don’t want it to!).

In my own code, and this is more out of habit than anything, I usually explicitly test for null before performing other tests. You could argue that it makes the test more clear, but it also can make your code a bit slower. This works because grouped logic operations “short circuit” (in a good way) as soon as a term is sufficient to demonstrate a given condition. So in this example:


if (myString == null || myString.equals("oh no!")) {
  // do something...
}

If the variable myString is null that “myString.equals(…” won’t ever be called (thus avoiding a NPE). That is because the logical operation is “or” (||) and if the first term is true the second is irrelevant because the whole expression is true. That’s the “short-circuit”, which not only helps to speed up code, but in this case can make a test like this a little simpler.

Flow Control Exercise

Again we will create a class that utilizes our Human class and performs some test on the object we instantiate. Make a new class called FlowTest in the yourinitials.app package. Have it create a human object and set its age to 25. Create an if condition to test if the age is equal to 30 (remember that getAge() returns a primitive and that your integer 30 is a primitive!) Create an else if condition to see if the age is 25, and an else condition. For each condition print out something useful.