02: Java review: Control flow

Announcements

Two TAs: Josh Pikovsky and Tomas Geffner.

Josh’s office hours are Tuesday/Wednesday at 12–2 in LGRT 223/5.

Tomas’s are Tuesday/Thursday 4–6 in LGRT 223/5.

My office hours are Wednesday 11–1 in CS 318.

Please come see any of us, but don’t expect to just open your laptop and say, “my program doesn’t work, can you help me find the problem?” Be ready to tell us what you’ve tried and why you’re stuck.

Please use Piazza for questions! You would be shocked (or maybe not) to know that many students will ask the same question; it saves everyone time if you check Piazza to see if your question has already been asked and answers. And it saves us time if we only have to answer one question. Thus, we’re able to answer more questions, which everyone like.

Please register your iClicker on Moodle. There is a block on the right (by default) that you can use to do so. It’s OK if you haven’t yet; Moodle will backfill the data for you once you do.

Java and control flow

Control flow is the order in which statements in an imperative language are executed. By default, the program starts executing at the main method, and goes line by line, executing a sequence of statements. In Java, this default is overriden in one of several ways:

A method could be invoked. This unconditionally sends the flow of control into that method. Similarly, we can return control from a method to the method that invoked it.

Or, the flow of control could branch conditionally. Most commonly we think of if[/then] and if[/then]/else, but Java also also supports the switch statement as well.

Or, we could execute a sequence of statements zero, one, or more times, as in the while, do/while or for statements. Within these statements, we might break out of the lop

Or, we could throw an exception, unwinding the stack of method calls until either the exception is caught (in other words, control resumes in the exception handler’s except clause) or the main method is reached, in which case the JVM exits and outputs an error message. NullPointerException anyone?

There are a few other ways control flow can by modified, but the above hits most of the highlights.

Let’s talk about each of them in turn.

Method invocation

Method invocations usually looks something like method(parameter1, parameter2), in other words, the name of a method followed by its parameters (either variables or literal values). If there is no preceding variable name, e.g., anObject.method(), the method is resolved first in the context of the current object, then as a static method. Otherwise, the method is resolved in the context of the leading object name. You can also invoke static methods of other classes by prefixing with the class name; more about this in a bit.

return, no matter where it is, exits the current method. For example, you can return from within a loop and the loop will terminate immediately.

Conditional branches

if[/then]/else statements evaluate an expression as a boolean, then branch one way if it is true. If there’s an else, the other way is followed – but with no else and an expression that evaluates to false, the statements are just skipped over.

How does Java delimit which statements are part of the then or else? It executes only the following statement. “But Marc!” Yes, I know, curly braces.

If you enclose a sequence of statements in curly braces, you have created a block. In terms of control flow treats a block as a single statement, so that you can, for example, branch to a sequence of statements after an if. Blocks also serve the purpose of defining a lexical scope, as we mentioned last class.

Speaking of, the fact you don’t need to declare a block using {} after an if statement leads to one of the most common kinds of bugs that we see in first- and second-year programming classes. Students often write code that looks like:

x = y + z;
if (someCondition)
  x = x + 10;
else
  x = x - 10;
System.out.println(x);

And on its face, that’s fine. The problem is when you later realize you need to modify the stuff that happens on the basis of someCondition to include more statements:

x = y + z;
if (someCondition)
  x = x + 10;
else
  x = x - 10;
  x = x * 2
System.out.println(x);

Do you see the problem? Just because you indent the code, and things look like a block, it doesn’t mean they are. Experienced programmers will insert the {} always, so that if later they modify the branch, things work correctly. Many style guides (e.g., Google’s) require this, and IDEs like Eclipse can be set to enforce it (either through warnings or errors).

Your other option is a switch statement, which evaluates an expression then jumps to a branch that’s labeled with the matching value:

int areaCode = getAreaCode();

switch (areaCode) {
  case 413:
    System.out.println("WMass!");
  case 617:
    System.out.println("Boston!");
}

But this might not do what you think. Control falls through from each matching statement to the next! This is super-dumb backward compatability with how C does it, and is a potential Big Problem with switch statements. You need to insert a break to tell Java you want control to exit the switch statement.

int areaCode = getAreaCode();

switch (areaCode) {
  case 413:
    System.out.println("WMass!");
    break;
  case 617:
    System.out.println("Boston!");
    break;
}

If there is no matching value, nothing happens. Unless you use the default: label, which will be matched if no other is:

int areaCode = getAreaCode();

switch (areaCode) {
  case 413:
    System.out.println("WMass!");
    break;
  case 617:
    System.out.println("Boston!");
    break;
  default:
    System.out.println("What? There are other area codes?");
}

Note that a switch statement can always be rewritten as a sequence of if/else statements, but sometimes switch is clearer.

Loops

Sometimes you want a statement to run zero or more times, as the result of a conditional. You want a while loop.

while (someCondition)
  doTheThing();

Of course, in the above example, doTheThing() had better change someCondition at some point, or the loop will never terminate (through normal means, anyway – it could throw an exception or otherwise exit the program, say, via System.exit()).

Also, why don’t we check if someCondition == true? Because we don’t need to: the if statement is checking if the condition is true already; there’s no need to duplicate the code here.

Just like if statement branches, it’s a super-good idea to always enclose loop bodies in a {} delimited block.

Note that there are two other ways, from within a loop, that we can change control flow. If we want to, we can immediately exit a loop with a break statement:

while (true) { // if you ever see this, you better see some way out of the loop, too
  boolean amDone = doTheThing();
  if (amDone) {
    break;
  }
}

return also exits loops.

boolean done = false;
int x = 1;
while (!done) {
  x = x * 2;
  if (x > 10)
    break;
}

Will this loop terminate?

a. yes b. no c. not enough information to decide.

In addition, you can use the continue statement to return to the “top” of a loop (in a while, it forces re-evaluation of the condition):

while (true) {
  i = i + 1;
  if (!isEligible(words[i])) {
    continue;
  }
  doSomethingWith(words[i]);
}

If you know you want to run the body of the loop at least once, you can use the (much less common) do/while statement:

do {
  something()
} while (!isDone());

This is exactly equivalent to:

something();
while (!isDone()) {
  something();
}

Though the latter is slightly longer, it’s almost always a better idea, unless you’re 100% sure you really want to execute the loop body at least once. Otherwise you’ll run into errors that only show up in odd circumstances, for example, only when an input file is empty, or an array is of length 0, or the like.

Finally, there’s the for loop.

IMHO, when possible, the best way to use a for loop is with the iterator syntax. Arrays (and more importantly, container types that implement the Iterable interface, support the iterator protocol. So you can write something like:

void printInts(int[] theInts) {
  for (int anInt: theInts) {
    System.out.println(anInt);
  }
}

There is another way to write a for loop, where you specify an initial state, a termination condition, and an update statement. Unfortunately this is prone to errors:

void printInts(int[] theInts) {
  for (int i = 0; i <= theInts.length + 1; i++) {
    System.out.println(theInt[i]);
  }
}

See the error(s)?

But you when do need to manually increment a value, for loops like the one above (though with a correct termination condition!) are the way to do it.

In either style, you can, just as in while loops, modify the flow of control with break, continue (which performs the update and checks for termination), and return.

In Java8, there is also new syntax for “lambda expressions” (which sounds scary, but means, in essence, “one line methods”) that you can use with a forEach method. We may see this later, but for now we’ll skip it.

int sum(int[] array) {
  int total = 0;
  for (int i = 0; i <= array.length; i++) {
    total = total + array[i];
  }
  return total;
}

What will this method return?

a. the sum of the integers 1 to array.length b. the sum of the integers stored in array c. it will throw an exception d. it will not compile

Exceptions and other abnormal exits

We’ll cover these more later – for the first part of this course, I won’t be asking you to write code in this class that needs to throw or handle exceptions. We’ll return to them in more detail when the need arises.

End of class reminders

Homeworks generally due every class, usually posted by the end of day the previous class. Don’t forget.