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 conditionmust evaluate to a boolean type; there is no notion of truthy/falsy values in Java.
That means that the following is out:
# Python:inty = 2if 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?
Answer
inty = 0 because 0 is a “falsy” value in Python — Java has no such notion, so attempting the above would actually
be a syntax error!
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.
// Javaint 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 definitiondef is_even (num): return num % 2 == 0# Python function callis_even(4)=> Trueis_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:
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?
Answer
Again due to its manifest typing, if a method, e.g., promises to return an int, and we attempt to store that in a String, that’s an
error that we’d like the compiler to catch.
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:
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.
Answer
package main.variables;/** * Provides tools for assessing variable names on certain * qualities. */public class VarNames { // [!] Define some global constants so we don't have any // magic numbers nestled in the code public static final int MAX_LEN = 16; public static final int MIN_LEN = 4; /** * Tests a varName for quality, returning true if it is * between 4 and 16 characters and begins in a lower-case letter. * * @param varName A String representation of a variable name, * may not be the empty String. * @return Whether or not it meets acceptable standards. */ public static boolean isGoodName (String varName) throws IllegalArgumentException { if (varName.length() == 0) { throw new IllegalArgumentException("varName may not be empty"); } return varName.length() <= MAX_LEN && varName.length() >= MIN_LEN && Character.isLowerCase(varName.charAt(0)); } public static void main (String[] args) { // Good Vars (well, at least by our bad definition): System.out.println(isGoodName("test")); System.out.println(isGoodName("goodVar")); // Bad Vars: System.out.println(isGoodName("o")); System.out.println(isGoodName("reallyExplanatoryVariableName")); System.out.println(isGoodName("LOUD_VAR")); }}
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?
Answer
Running that main method / println outputs requires us to manually and visually scrutinize the output for validity — this is totally
unmanagable as the project and class grow in size!
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:
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.
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).
Use JUnit assertion statements to verify correct functionality. You can get a long way with the methods assertEquals(expected, actual)
or assertTrue(expression).
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.