Definition

Control Flow is the order in which statements are executed during the course of a running program.

Toolkit

Thus, tools that help us to manipulate and control… the control flow are useful and almost exactly the same as our old favorites from Python!

Conditionals

Definition

Conditionals are used to execute code only when certain criteria are met.

Toolkit

The if-conditional is pretty much exactly like in Python but with a few subtle differences, executing any code in the if-body if the condition is true:

if (condition) {
    // if-body: executes if condition evaluates to true
} else { // [Optionally]
    // else-body: executes if condition evaluates to false
}

Apart from the minor syntactic difference (brackets {} to denote the if-else bodies) there’s one big difference:

Debug

Note that the condition must evaluate to a boolean type; there is no notion of truthy/falsy values in Java.

That means that the following is out:

# Python:
inty = 2
if inty:
    print(inty) # prints 2 here because 2 is truthy

Question

What value for inty in the snippet above would prevent the print statement from executing?

As such, in Java, we would need some sort of comparison in the condition, like inty < 3 because this will always evaluate to a boolean.

// Java
int inty = 2;
// Condition must evaluate to a boolean:
if (inty == 2) {
    // ... do something
}

Iteration

Definition

Iteration is used to repeat some statements until a stopping condition is met.

This too will look a lot like in Python, though with slightly different syntax:

Toolkit

The while-loop continues executing statements in its body until the condition is false.

String s = "ABCD";
while (s.length() > 0) {
    System.out.println(s);
    // Returns a new String without the first char in s,
    // then stores back into s
    s = s.substring(1);
}

Toolkit

The for-each loop iterates over all elements in some iterable collection (like an array) via the syntax: for (type iterator : iterable) {...}.

char[] chars = {'A', 'B', 'C', 'D'};
for (char c : chars) {
    // c is an iterating variable over each char in-sequence
    // in the chars array
    System.out.println(c);
}

Toolkit

The standard-for loop allows you to define a loop pattern of the following format:

for (initialStatement; condition; postLoopStatement) {
    // loop body
}

In the pattern above:

  • The initialStatement is executed once at the start of the loop, generally to initialize some loop variable like an index.
  • The condition is precisely like in the while loop, and is evaluated at the start of each loop: if true, the loop body executes, otherwise, control flow returns to after the for-loop.
  • The postLoopStatement is executed at the end of each loop.
  • Debug

    Warning: Note the semicolons ONLY after the initialStatement and condition in the pattern above!

for (int i = 1; i <= 8; i = i * 2) {
    System.out.println(i);
}

Remark

Rule of thumb: use the for-each syntax when you care about only the items inside some iterable, and the standard-for when you care about indexes attached to each item.

Sometimes, you may wish to terminate a loop or a single iteration during the loop only under certain conditions, for which there are special statements that allow you to preserve the loop syntax but interrupt it at will.

Toolkit

There are 2 primary loop interruptions in Java:

  • break; terminates the loop and returns control flow to after the loop’s body.
  • continue; terminates the current iteration and continues to the next, executing any defined postLoopStatement and checking the condition to determine whether or not to execute the loop body again.
String s = "axbxcxd";
for (char c : s.toCharArray()) {
    if (c == 'x') { continue; }
    System.out.println(c);
}

Toolkit

If used within a nested loop, the break / continue statements will only affect the loop in which they are used.

…and those are the essentials of Java control flow!


Methods

Note how everything we’ve been doing thus far has been assumed to be running in the main method, but plainly, for larger, more complex, projects, we’ll want the ability to define other methods in which to situate our code.

Definition

Methods are just functions: names given to behaviors with specified inputs and outputs (if any).

In Python, you made functions that more or less did this:

# Python function definition
def is_even (num):
    return num % 2 == 0
 
# Python function call
is_even(4)
=> True
 
is_even(1)
=> False

That said, as with most things, Java trades convenience and parsimony for control and customization.

When we define a method in Java we specify a variety of properties that make it unique compared to others in the class; these properties are defined in the method signature.

Method Signatures

Toolkit

Method signatures define the properties of a method by the syntax:

<modifiers> returnType methodName (parameters) {
    // Method body
}

In the pattern above:

  • parameters are a list of 0 or more inputs of the format: type0 paramName0, type1 paramName1, ...
  • The returnType determines the type of the method’s output, if any — if the method does not return anything, we specify a returnType of void.
  • The <modifiers> are similar to those in variable declarations, but we’ll cover these in greater detail later.

Most methods we’ll write initially will have the public static modifiers, but we’ll see later examples that do not.

Returning

Question

Why must we define the data type of whatever gets returned from a method in Java?

Toolkit

Use the return value; keyword to return a value, or simply return; to terminate a method with a return type of void.

Example

Recreate, in Java, the simple is_even method in Python above.

public static boolean isEven (int num) {
    return num % 2 == 0;
}

Toolkit

Once a method’s been defined, we can call it using the traditional methodName(arguments) syntax.

public static void main (String[] args) {
    System.out.println(isEven(4)); // true
    System.out.println(isEven(3)); // false
}

JavaDocs

It turns out that writing the actual methods is only part of the job of a good programmer — it’s important that we communicate how to use them as well!

Remember those cool tooltips that our Java IDEs can give us for certain method calls?

…well, it’s time we learned how to make those ourselves!

Definition

JavaDocs are special comments that provide documentation on how to use classes, methods, and a variety of other facets of a Java project / package.

Remark

For now, we’ll examine how to properly document a method — every method you write should be appropriately documented!

Toolkit

The general format of a JavaDoc is as follows:

/**
 * Plain-English description of method's purpose.
 *
 * @param parameterName Description of parameters
 * @return Description of returned value, if any
 */
<methodBeingDocumentedHere>

Example

Adding JavaDocs to our isEven method:

/**
 * Determines whether or not a given integer is even.
 *
 * @param num The number to test.
 * @return boolean of whether or not num is even.
 */
public static boolean isEven (int num) {
    return num % 2 == 0;
}

Even cooler, if other users try to employ our isEven method, their IDE will provide the tooltip similar to the above String methods given the JavaDocs we write.

JavaDocs can also be used to generate a whole website with information on your class, as we’ll see later.

There’s a lot more you can do with JavaDocs, which you can read more about here:

Assignment


Practice

Example

Design a simple warm-up program VarNames.java in package main.variables that defines:

  • A function public static boolean isGoodName(String varName) that takes as input a String representation of a variable name, and then returns whether or not it is “good” based on some criteria (to be discussed).
  • A main method that tests the function with a few cases.

Of course, you must be asking, “What makes a ‘good’ variable name when programming?” Well, there’s only some agreement on that, often arriving at amusing reflections like the following:

For our purposes (a demo of setting up an IDE to execute a simple program), let’s define a “good” name as:

  • Having no fewer than 4 letters and no more than 16.
  • Does not start with a capital letter.

Question

Click for sample solution.

Some other remarks on the above:

  • In Java, exceptions represent different types of errors that we can manually throw if we need to terminate a method in error. There are many types of exceptions, the IllegalArgumentException(errorMessage) is appropriate when an argument violates an assumption of the method’s inputs.
  • We also add the throws IllegalArgumentException tag to the method signature to signal that this method throws those intentionally (not essential, but good programming style).
  • No, there’s no irony in MAX_LEN, MIN_LEN violating our “good variable name criteria” since these are constants, which by convention are named in all-caps and underscore-separated words.
  • Testing via the main method feel arduous? Don’t worry, it is — let’s talk about some better ways to test!

Test-Driven Development

Question

What’s the problem with testing your code in the main method with print statements like in the above?

Toolkit

Test Driven Development (TDD) is a software engineering practice of preceding development by writing tests that validate some expected behavior vs the actual behavior produced by your code.

TDD helps to formalize expectations for what needs to be implemented since it forces you to see concrete examples of expected inputs / outputs.

Unit Testing

Arguably the most useful but most ignored aspect of undergraduate software development (at least in a programmer’s formative years), test driven development through unit testing is a valuable habit to learn and practice early.

Definition

Unit tests verify the correct functionality of small, testable components of a class to verify that it will function properly overall; these generally focus on testing individual methods for proper functionality.

Toolkit

Luckily, Java has an amazing test framework called JUnit that we will demonstrate herein.

Remark

Note: JUnit is not the end-all be-all of unit testing. It is merely a convenient way to perform unit tests to ensure that we are producing quality classes.

Remark

Note: we will demo JUnit in IntelliJ IDEA during class, but you may use JUnit in any development environment. See the following JUnit tutorials:

Assignment

In brief, JUnit operates as follows:

  1. We create a new test class / source file that will contain all of our unit tests, generally, in a package that specifies test for some class in the main package.
  2. Using JUnit 4 (make sure to pick this version, not JUnit 5, when IntelliJ offers to add it), you may then annotate individual test methods in this class to test various aspects of the class (e.g., verifying correct functionality of each method).
  3. Use JUnit assertion statements to verify correct functionality. You can get a long way with the methods assertEquals(expected, actual) or assertTrue(expression).
  4. If you have any errors, then you can catch and fix them! Voila! Unit testing complete.

Here’s an example of a JUnit test file for our VarNames class:

package test.varnames;
 
import static org.junit.Assert.*;
import org.junit.Test;
 
public class VarNameTests {
 
    @Test
    public void test_isGoodName() {
        assertTrue(VarNames.isGoodName("test"));
        assertTrue(VarNames.isGoodName("goodVar"));
 
        assertFalse(VarNames.isGoodName("o"));
        assertFalse(VarNames.isGoodName("reallyExplanatoryVariableName"));
        assertFalse(VarNames.isGoodName("LOUD_VAR"));
    }
 
}

When we run the above JUnit tests, we get a nice IntelliJ interface — the Run tool window — to show us if anything’s wrong, and if so, where!

Toolkit

Note: double-clicking on any unit test zooms to that test in the suite, and will show which assertions failed along with why.