02: Java review: Control flow

Welcome

Welcome to people of all ages, all colors, all cultures, abilities, sexual orientations and gender identities.

Welcome to those who identify as biracial, multi-racial, or multi-ethnic.

Welcome especially to those of you who added the course late. Please be sure to come talk to me or email me if you haven’t already! In particular, you need to check the course web page and Moodle site, as well as sign up for Piazza and Gradescope.

Announcements

Again, the most important thing to know today: the course web site is at http://people.cs.umass.edu/~liberato/courses/2019-spring-compsci186/. It has the syllabus for this class and you are expected to read it in its entirety.

Questions: Feel free to interrupt me. One of the benefits of coming to lecture is you can ask questions. If your question is too specific or off-topic, I’ll answer it after class or by Piazza (which you’ll need to remind me about).

If you have an accommodation due to a recognized disability, you need to contact me to tell me if and how you plan to use it. In particular, things like extra time on homeworks need to be requested in advance of, not after, the due date. Or if you need extra time on an exam, I need time to figure out how and where you’ll take it.

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. Probably you should never use the switch statement – let us never speak of them again! (Well, not much, anyway.)

Or, we could execute a sequence of statements zero, one, or more times, as in the while (or do/while, which we’ll also neglect) or for statements. Within these statements, we might break out of the loop, or return from the entire method.

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 = 5;
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 = 5;
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).

In-class exercise

boolean someCondition = true;
int x = 10;
if (someCondition)
  x = x + 2;
else
  x = x - 2;
  x = x / 2;
System.out.println(x);

What’s the output?

boolean someCondition = false;
int x = 10;
if (someCondition)
  x = x + 2;
else {
  x = x - 2;
  x = x / 2;
}
System.out.println(x);

What’s the output?

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.

In-class exercise

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):

int i = 0;
while (true) {
  i = i + 1;
  if (i < 10) {
    continue;
  }
  System.out.println(i);
  break;
}

This will repeatedly increment i and not reach the println until i == 10; then it will print i and exit the loop. What would happen if we removed the break?

In-class exercise

int i = 0;
while (i < 10) {
  if (i % 2 == 0) {
    System.out.println(i);
    continue;
  }
  i = i + 1;
}

What will this loop do?

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. If you care about the contents of an array (or any iterable), but not about the indices, then you can use the enhanced for loop.

Suppose you have an array you want to iterate over and do something to, like print its members. You can do it the “old fashioned” way:

int[] array = {1, 3, 3, 7};

for (int i = 0; i < array.length; i++) {
  System.out.println(array[i]);
}

Or you could use the enhanced for loop:

int[] array = {1, 3, 3, 7};

for (int e : array) {
  System.out.println(e);
}

Notably, the enhanced for loop prevents you from making indexing errors – it guarantees that it will visit each element of the array exactly once, assigning it to the iterator variable you provide each time through the loop.

The other way (the “old fashioned” way) is more general: 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)? Hard to follow exactly what’s going on with that termination condition, huh?

In-class exercise

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?

Back to for loops

But there are at least two major times when this style comes in handy.

The first is when you need access to the index, not just the element.

And the second (which is related) is when you need to compare different elements within the same array. For example, let’s say you wanted to write a method to check if an array of ints was sorted. You’d need to make sure each element was less than or equal to the element after it (and make sure you didn’t check anything past the end of the array!):

boolean isSorted(int[] array) {
  for (int i = 0; i < array.length - 1; i++) {
    if (array[i] > array[i + 1]) {
      return false;
    }
  }
  return true;
}

In either style of for loop, 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.

There are a few common patterns of for loops you should learn about and master, as they’ll make writing for loops easier for you. We’ve seen some of them already, but let’s talk about patterns. Here they are:

Doing something to every element of an iterable: Suppose we have an array of Things, and we want to doSomething to each one. The enhanced for loop lets us do so in a straightforward manner:

void doAll(Thing[] array) {
  for (Thing t: array) {
    doSomething(t);
  }
}

Testing that everything in an iterable satisfies a property:: Let’s say we want to check if every element in an array is even. The general idea here is to check each element one-by-one. As soon as one thing violates the property (that is, is odd), we can return false. If we get to the end, we can return true:

boolean allEven(int[] array) {
  for (int e : array) {
    if (e % 2 == 1) {
      return false;
    }
  }
  return true;
}

Another example is a linear search, where we scan every element of an iterable and do a thing if an element is found. We do a different thing if it’s not found:

boolean containsValue(int[] array, int value) {
  for (eint e : array) {
    if (e == value) {
      return true;
    }
  }
  return false;
}

The pattern is the same; the test and the return values differ.

Or if we wanted to see if a String contained a particular letter we can iterate over its contents. Probably the easiest way to do this is using the charAt member method of String, as Strings don’t directly support enhanced for loops:

boolean containsChar(String s, char c) {
  for (int i = 0; i < s.length(); i++) {
    if (s.charAt(i) == c) {
      return true;
    }
  }
  return false;
}

Again, the pattern is the same; the test and the return values differ.

Comparing elements in an array for some property: We saw this already in the isSorted method. Here, we cannot easily use the enhanced for loop, since we need access to the index (to check the current against the next value):

boolean isSorted(int[] array) {
  for (int i = 0; i < array.length - 1; i++) {
    if (array[i] > array[i + 1]) {
      return false;
    }
  }
  return true;
}

The pattern is similar to before, but we need to sure we access the right elements of the array, and we need to be careful with bounds. You could rewrite it as:

boolean isSorted(int[] array) {
  for (int i = 1; i < array.length; i++) {
    if (array[i - 1] > array[i]) {
      return false;
    }
  }
  return true;
}

but I find the former more natural.

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!

There’s usually a programming assignment due Fridays, including tomorrow.

There’s a quiz Monday in discussion, don’t miss it!