Statements and Functions
Selection, Iteration and Static Methods
PREREQUISITES — You should already…
- have access to a JDK 8, and be able to create and run simple Java Programs;
- understand and have read Java Types, Values and Variables;
- know how to use the command line (Command Prompt, PowerShell or a POSIX shell).
Selection Statements
The logical and comparison operators provide a means for us to calculate boolean
results. They are generally only useful, if we use these results to execute different statements, i.e. select which statements to execute. By the same token, boolean
results can be used to determine whether we must or must not, repeat a series of statements. But, first, the selection statements…
NOTATION — Boolean Expression & Statements
Since boolean
type expressions will be common, we shall use the notation:
- ‹bool-expr› means any “expression, regardless of complexity, resulting in a
boolean
”.
Furthermore, we remind you of the following:
- ‹statement› means any statement (normally terminated with a semicolon), including a
- compound statement curly-brace delimited block, with zero or more ‹statement›s.
It is a recursive definition — statements can contain statements can contain statements…
If Statement
The if
statement (tutorial), can be used to conditionally execute statements, in other words, it may jump over the statements, depending on a boolean
condition.
Syntax — If Statement
if (
‹bool-expr›)
‹statement›- ‹bool-expr› any expression resulting in a
boolean
value.
Since the if
statement can contain one ‹statement›, we often “cheat” and use a compound statement at that position, because a compound statement is one statement, which satisfies the syntax, but it can contain multiple statements. Some programmers consistently use a compound statement, whether it is required or not. This is not a bad convention for several reasons, so you might consider it.
Pattern — If Statement with Compound Statement
if (
‹bool-expr›) {
‹statement›
…
}
The ‹statement› part of an if
statement, is only executed when the ‹bool-expr› results in true
.
java.io.Console con = java.lang.System.console();
if (con == null) {
java.lang.System.err.println("No console. Terminating.");
java.lang.System.exit(0);
}
We fully-qualified all methods, so you can be reminded of where they reside, but it is the if
statement that is of importance here.
If-Else Statement
The if
statement by itself is useful, but sometimes we want to execute A or B, i.e. “either this A, or that B”. For that scenario, we have the if
-else
statement (tutorial), where the else
clause belongs to the closest if
part.
Syntax — If-Else Statement
if (
‹bool-expr›)
‹statementA›
else
‹statementB›
As before, either ‹statementA›, or ‹statementB›, or both, can be compound statements. We suggest that if one is a compound statement, make the other also a compound statement regardless. If you follow the convention that you always use compound statements, you are covered.
One problem with if
-else
, is when we want to have multiple instances of it, a multi-way select in other words. This means we want to execute only one of: either statement A, or statement B, or statement C,… This often lead to programmers writing the construct with a staircase effect:
if (condition_a)
statementA();
else
if (condition_b)
statementB();
else
if (condition_c)
statementC();
else
statementD();
This is completely unnecessary and serves not purpose. We suggest that logically, you must think of situations when an if
follows and else
as one concept, an elseif
clause, if you will, except that is not a keyword, and you must write it: else if
:
if (condition_a)
statementA();
else if (condition_b)
statementB();
else if (condition_c)
statementC();
else
statementD();
This much neater, more readable, more maintainable, more consistent and less wasteful of space. If you used compound statements, it would look as follows (using our style):
if (condition_a) {
statementA();
}
else if (condition_b) {
statementB();
}
else if (condition_c) {
statementC();
}
else {
statementD();
}
Obviously, in the above case, you could have had multiple statements inside of each compound statement.
Switch Statement
Sometimes, a multi-way select can be represented more succinctly, with the switch
statement (tutorial). Understand that whatever logic you use a switch
statement for, can be implemented with if
-else
statements, which means using a switch
statement is sometimes just somebody’s preference. The reverse is not true — you cannot rewrite every series of if
-else
statements in terms of a switch
statement.
Syntax — Switch Statement
The ‹expr› is matched agains the ‹label?› expressions, and when a match is found, execution transfers (jumps) to the first statement following the matching label. Any break
statement encountered afterwards, will cause execution to transfer to the statement(s) after the closing curly-brace.
Notice that the break
statements are optional. If they are omitted, execution will fall-through to the next ‹statement›, even if past another case
label. If you compile with -Xlint
and/or the -Xdiags:verbose
switches to javac
, you will get warnings if you omit the break
statements, since they are often forgotten, and more commonly an error, than by design:
warning: [fallthrough] possible fall-through into case
Apart from the warning, the code will still compile and execute as desired.
int i = 5;
switch (i) {
case 1: out.println("one");
case 2: out.println("two");
case 3: out.println("three");
default: out.println("too many");
}
too many
The default
label is optional, and can appear anywhere although surprisingly few programmers know that, so rather always put it last, if present. When omitted, you must of course not have any ‹statementX› either.
int i = 5;
switch (i) {
default: out.println("too many");
case 0: out.println("zero");
case 1: out.println("one");
case 2: out.println("two");
case 3: out.println("three");
}
too many zero one two three
The ellipses after ‹statement?›… implies that you can have any number of statements, of any kind before the break
statement.
int i = 3;
switch (i) {
case 0: out.println("zero");
case 1: out.println("one");
case 2: out.println("two");
case 3: out.println("three");
default: out.println("too many");
}
three too many
Of course, with break
statements, the code snippet would look like this:
int i = 3;
switch (i) {
case 0: out.println("zero"); break;
case 1: out.println("one"); break;
case 2: out.println("two"); break;
case 3: out.println("three"); break;
default: out.println("too many"); break;
}
three
Iteration Statements
Now that we can control the flow of our program with selection statements, we want to consider iteration (“loops” for us plebs). An iteration statement will continuously execute a set of statements while some condition remains true
, i.e., some ‹bool-expr›ession. It is possible for an iteration to continue indefinitely, and is generally a defect, and is called an infinite loop.
While Loop
This simplest of the iteration statements, has a similar syntax pattern to the if
statement:
Syntax — While Iteration Statement
while (
‹bool-expr›)
‹statement›
Exactly like the if
statement, the ‹statement› part can also be a compound statement (block), and some programmers always use a compound statement as a convention.
out.print("\nloop 9 times (1..9) :\t"); {
int i = 0;
while (++i < 10) out.format("%2d ", i);
}
out.print("\nloop 10 times (1..10):\t"); {
int i = 0;
while (i++ < 10) out.format("%2d ", i);
}
out.print("\nloop 10 times (0..9) :\t"); {
int i = 0;
while (i < 10) out.format("%2d ", i++);
}
out.print("\nloop 10 times (0..9) :\t"); {
int i = 0;
while (i < 10) {
out.format("%2d ", i);
++i; //←or: `i++;`
}
}
loop 9 times (1..9) : 1 2 3 4 5 6 7 8 9
loop 10 times (1..10): 1 2 3 4 5 6 7 8 9 10
loop 10 times (0..9) : 0 1 2 3 4 5 6 7 8 9
loop 10 times (0..9) : 0 1 2 3 4 5 6 7 8 9
Do-While Loop
In some algorithms, we want the ‹statement› part of a loop to execute at least once, guaranteed. With the normal while
loop, it is not guaranteed to execute the ‹statement› even once. In such rare occasions, you may consider the do
-while
loop:
Syntax — Do-While Loop
do
‹statement›
while (
‹bool-expr);
Even if ‹bool-expr› is false
, the ‹statement› will execute once. Style-wise, it does represent some consistency problems when using a compound statement; we provide some ideas:
do { do { do {
‹statement(s)› ‹statement(s)› ‹statement(s)›
} } while (‹bool-expr›); }
while (‹expr›); while (‹bool-expr›);
Personally, we suggest you choose between the first two.
MultLoop.java
— If, While and Do-While Statements Example
import java.lang.*;
import static java.lang.System.*;
public class MultLoop {
final static int MIN_MULT = 1;
final static int MAX_MULT = 12;
public static void main (String[] args) {
// Prompt user, and input a string, converting it immediately to `int`.
//
int mult = -1;
do {
out.format("Enter multiplier [%d..%d]: ", MIN_MULT, MAX_MULT);
mult = Integer.parseInt(con.readLine());
if (mult == 0)
exit(0);
}
while (mult < MIN_MULT);
// Validation. Check if `int` is in range, if not exit with message.
//
if (mult > MAX_MULT) {
err.format(
"Multiplier not in range [%d..%d]", MIN_MULT, MAX_MULT);
exit(2);
}
// Have valid multiplier, print multiplication series for it.
//
int num = MIN_MULT;
while (num <= MAX_MULT) {
err.format("%2d × %2d = %3d\n", num, mult, num * mult);
++num;
}
exit(0);
}
static java.io.Console con = console();
static {
if (con == null) {
err.println("No console. Terminating");
exit(1);
}
}
}//class
Although in all the examples above, we knew beforehand how many iterations were required, it is not the most common use of a while
loop. We generally use it when we do not know how many times to loop beforehand, like reading lines from a file.
Exercise: Program that outputs a complete multiplication table grid for 1
…12
, no input required.
For Loop
Although with our while
loop examples, we knew beforehand how may times to iterate, the for
loop is generally the iteration statement to use when we know the count.
Syntax — For Loop Statement
for (
[‹init›];
[‹bool-expr›];
[‹iter-expr›])
‹statement›- ‹init› expression and/or variable definition executed once.
- ‹bool-expr› condition that controls when iteration will terminate.
- ‹iter-expr› expression to evaluate at end of each iteration.
Note that each of ‹init›, ‹bool-expr› and ‹iter-expr› are optional; however, if ‹bool-expr› is not present, Java will substitute true
, which makes it an infinite loop. If both ‹init› and ‹iter-expr› are omitted, you effectively have a weird-looking while
loop.
Consider the elements of the while
loop in the previous example:
int num = MIN_MULT; //←initialise controlling variable.
while (num <= MAX_MULT) { //←check iteration condition.
err.format("%2d × %2d = %3d\n", num, mult, num * mult);
++num; //←modify controlling variable.
}
We can now rewrite it as a for
loop, which is much more succinct, and all elements that control the loop: 1) initialising the controlling variable(s), 2) checking the condition, and 3) modifying the controlling variable(s), are all in one place.
for (int num = MIN_MULT; num < MAX_MULT; ++num)
err.format("%2d × %2d = %3d\n", num, mult, num * mult);
It is not a requirement to define variables in the ‹init› part of the for
loop, but is certainly common. The scope of any definition there, is limited to the ‹statement› part of the for
loop.
One can define more than one variable in the ‹init› part of the for
loop, and also evaluate more than one ‹iter-expr›ession, by utilising the comma operator (which is the only place you can use it in Java):
For-Each Loop
For completeness, we include the “for-each” loop here, which Java calls the enhanced for statement, but we shall find use for it in the next section, when we deal with multiple values (arrays an collections). But for now, here is the syntax:
Syntax — For-Each / Enhanced For Loop
\u2502 \u2500 \u253C \u252C \u2534 \u2524 \u251C \u250C \u2518 \u2514 \u2510
Abstractly, the construct pattern: ‘for(
‹item›:
‹collection›)
’ means: “for each ‹item› in this ‹collection…”, which is why we call it the “for-each” loop.
Break and Continue
The break
statement can also be used inside iteration statements. It will effectively terminate the iteration summarily — transfer execution to the first statement following the body of the loop. This means, it is practically only useful after a selection statement.
A statement that has the opposite effect: immediately start the next iteration; is the continue
statement.
Labelled Statements
Any iteration statement can be labelled with ‹label›:
, where ‹label› is just an ‹ident›ifier, really. Java has not goto
, but the label can be used by the continue
and break
statements, in other words, you write: ‘break
‹label›;
’, which means that the break
will apply to the labelled iteration statement, not necessarily the potentially nested loop that contains the break. The same syntax applies to continue
.
static Double readDouble (java.io.Console con, String prompt) {
Double result = null;
INPUT: for (;;) { // input forever
try{
do {
out.format("%s (`Q` to quit)?: ", prompt);
String inp = con.readLine();
if (inp.equals("Q") || inp.equals("q"))
break INPUT;
if (inp != null && !inp.equals(""))
result = Double.parseDouble(inp);
}
while(result == null);
break;
}
catch (java.lang.Exception ex) {
// just ignore & try again.
}
}
return result;
}
We use a label (INPUT
) to break out of the outer for
loop. We could simply have used a return
statement, but this way we could still perform some tasks just before the return
statement. It is all a bit contrived, but you can at least see it in action. Since we also wanted the function to be practical, we did use a try
-catch
block, which will deal with later. Now, even if the user enters garbage that cannot be parsed to a Double
, the program will not terminate.
Other Statements
Here follows a number of other statement types, or topics involving statements. They are all specialised and simpler than the major statements we already described.
Null or Empty Statement
If the syntax requires a statement, and for some reason you have not useful statement to employ at that point, you can simply write a semicolon (;
). In that context, it is called a null, or empty, statement.
Statement Nesting
Anywhere we used ‹statement› you can use any construct we called a statement. Practically, this means that inside while
loop, you can have another while
loop, which contains for example, an if
statement, which contains a try
-catch
statement, etc. In other words, statements can nest to arbitrary depth.
Expression Statements
Although not as forgiving as C or C++, any expression that affects state, or that calls a method, can be made a statement, simply by appending a semicolon.
Assert Statements
The assert
statement can be use to facilitate the abstractions of preconditions and postconditions. In other words, we do not use assert
statements for input or runtime validation. It is intended to guard against programmer errors.
Syntax — Assert Statement
assert
‹bool-expr›;
assert
‹bool-expr1›:
≪bool-expr2›;
Since many assertions (generally a good sign), can be expensive (bad thing), we can enable or disable assertions with arguments to the javac
command line compiler. You can en/disable assertions globally, or for a package.
-enableassertsions
‖-ea
enable assertions for all classes.-disableassertions
‖-da
disable assertions for all classes.-ea:...
enable assertions for unnamed package in current directory.-da:...
disable assertions for unnamed package in current directory.-ea:
‹pkg-name› enable assertions for a package.-da:
‹pkg-name› disable assertions for a package.
Functions / Methods
A major component in a programmer’s toolset, is the ability to name a group of statements. We may want to do that because it could make a program more readable (as long as the name suggests properly what is going to happen), or to reuse a group of statements in several places. In Java the technical mechanism for this, is a function (a construct that takes arguments and returns one value), although because of the OOP influence, Java acolytes prefer the term method.
Other languages call their implementation of the concept: subprogram, subroutine or procedure. We will use the terms function when we discuss syntax and technicalities, and call it method, when we focus on its object-oriented features and behaviour.
Function Definition
A function definition is a syntax that conforms to a pattern in certain locations in your program. There is no keyword to define functions, just like there is not keyword to define variables.
You cannot define functions inside other functions; definitions can therefore only appear a class level. Unless they have the static
specifier attached, they are instance functions, but they cannot be called until you have an object (instance of the class). Syntax-wise, the only difference is the static
specifier, which is why we show it as optional in the syntax:
Syntax — Function Definition
- [
static
] ‹ret-type›‖void
‹ident›(
[‹param›])
function header
[‹thow-spec›] unchecked exceptions “thrown”.
{
function body block.
[‹statement›…]
[return;
‖return
‹expr›;
]
}
- ‹ret-type› the type of the value the function will
return
.- ‹ident› name of the function.
void
the function will notreturn
any value, but may usereturn;
.return;
only legal on functions withvoid
return type.return
‹expr›;
required when using a ‹ret-type› that is notvoid
.- ‹param› optional ‹param›eters..
- ‹throw-spec› list of unchecked exceptions that can “escape” the function.
IMPORTANT — Function Signatures ≡ Overloading
A function ‹ident›ifier, types and numbers of ‹param›eters together, constitutes its signature. The function signature is what identifies a function uniquely; you can think of it as the ID of the function. A function ‹ident›ifier by itself, is thus ambiguous, and does not identify a function. It also means that functions may have the same ‹ident›ifier, as long as the signatures are different.
This is overloading — functions with the same names, taking different arguments.
Notice that ‹statement›s are optional, which means it is legal to have a function with an empty body — as long as it returns void
, otherwise you must have at least a ‘return
‹expr›;
’ statement.
Returns
A function can return only one value. This may be a composite value, but nevertheless one value. The ‹type› of the ‹expr›ession return
ed, must match the function’s ‹ret-type› (return type in the definition of the function).
Although it goes against the grain of structured programming, you may have multiple return
statements in a function. Since it is an execution transfer statement, no statement after a return
statement, will execute. The following function has multiple return
statements, which is not in line with structured programming, but is certainly easy to understand:
static String F (int i) {
switch (i) {
case 0: return "zero";
case 1: return "one";
case 2: return "two";
case 3: return "three";
default: return "too many";
}
}
Note that the compiler is intelligent enough to realise that all execution paths will have a return
statement, which is why it will not complain that there is not return
statement at the end of the function body block. You could take out the default:
label, and move its return
statement to the end of the function, and you will still have the same logic.
Returning Multiple Values
A function, by definition, can only return a single value. This value can be of any ‹type›, even a type containing many values, so this is never a problem, but does require syntax and concepts we have not yet discussed. In many languages, including C++ and C#, the standard libraries provide some sort of tuple for ad hoc grouping of arbitrary type and number of values. Java does not, but a search for tuples will bring up many implementations, articles and libraries.
The simplest (and least flexible) solution below should not be too difficult to follow, even if we have not yet discussed classes and encapsulation. We want a function to return two values, one of type String
, and another of type double
, so we create a simple class TupleSD
containing instance members representing two value with these types.
TuplePair.java
— Simple Pair Tuple Example
import java.lang.*;
import static java.lang.System.*;
public class TupleSimple {
public static void main (String[] args) {
TupleSD sd = TupleSD.make("Some String", 12.34);
out.format("Pair: \"%s\", %.4f\n", sd.str, sd.dbl);
sd = SomeFunc();
out.format("Pair: \"%s\", %.4f\n", sd.str, sd.dbl);
exit(0);
}
static TupleSD SomeFunc () {
return TupleSD.make("Another String", 56.78);
}
}//class
class TupleSD {
public String str = "";
public double dbl = 0.0;
public static TupleSD make (String str, double dbl) {
TupleSD tuple = new TupleSD();
tuple.str = str; tuple.dbl = dbl;
return tuple;
}
}//class
Pair: "Some String", 12.3400
Pair: "Another String", 56.7800
For each combination of types and number of values, you will have to make a new class, but the pattern is simple. More realistic implementations will use generics, but we leave that for later.
Parameters
To make functions more generic, so that its functionality can be applied to different values, we need the ability to pass arguments. The syntax that enables this ability, is called parameters, or “formal parameters”. Parameters are in every respect like local variables:
- Parameters have the same scope as local variables, hence no local variable can have the same name as a parameter
- Parameters have the same lifetime as local variables. They too are destroyed when the function returns.
The only difference, is that parameters are initialised by the caller of the function at run time.
NOTE — Parameters vs Arguments
These two term are unfortunately used interchangeably by too many authors. They are not synonyms. Arguments are ‹expressions›, while a parameter involves a concept, and a syntax in the definition of functions (methods). That is how we use these two terms religiously.
Since parameters are variables, they have the same syntax as variable definitions, except that no initialisation clause is possible. And unlike variables, you must repeat the ‹type› for each parameter. Here is the basic syntax for ‹param›eters:
Syntax — Function Parameters
- ‹type› ‹ident› single parameter.
- ‹type› ‹ident1›
,
‹type› ‹ident2›,
… multiple parameters, with the same ‹type›.- ‹type1› ‹ident1›
,
‹type2› ‹ident2›,
… multiple parameters with different ‹type›s.- ‹type›
...
‹ident› variable number of ‹type› arguments can be passed.- ‹type1› ‹ident1›
,
‹type2›...
‹ident› one fixed argument, followed by zero
or more arguments can be passed.
In the last case, you can have any number of parameters before the last variable-length parameter. The three dots together are called ellipsis, but they are three period characters (...
), not the Unicode ellipsis character: (…).
VarLenParam.java
— Variable Length Parameters Example
import java.lang.*;
import static java.lang.System.*;
public class VarLenParam {
public static void main (String[] args) {
out.println(F(0));
out.println(F(1, "A"));
out.println(F(2, "B", "C"));
out.println(F(3, "D", "E", "F"));
out.println(F(4, "G", "H", "I", "J"));
exit(0);
}
static String F (int i, String... str) {
String r = "";
for (String s : str)
r += s;
switch (i) {
case 0: return "zero :''";
case 1: return "one :" + r;
case 2: return "two :" + r;
case 3: return "three:" + r;
default: return "many :" + r;
}
}
}//class
zero :'' │ one :A │ two :BC │ three:DEF │ many :GHIJ
Arguments are passed by position to the respective parameters. Java possesses no syntax for named arguments, nor default arguments. You have to make do with function overloading (different signatures), and variable argument list parameters.
The function F
above, can take any number of String
arguments after the first argument (which must be int
). If you wanted to pass arbitrary ‹type›s of arguments after the first, you have to make it an Object
.
ObjVarArgs.java
— Object Variable Length Parameter
import java.lang.*;
import static java.lang.System.*;
public class ObjVarParm {
public static void main (String[] args) {
out.println(F(0));
out.println(F(1, "A"));
out.println(F(2, 123, "B"));
out.println(F(3, "D", 45.67, 'F'));
out.println(F(4, 99, true, 12.0, "J"));
exit(0);
}
static String F (int i, Object... obj) {
String r = "";
for (Object s : obj) {
r += "<" + s.toString();
if (s instanceof Integer) r += " (int)";
else if (s instanceof Boolean) r += " (boolean)";
else if (s instanceof Double) r += " (double)";
else if (s instanceof Character) r += " (char)";
else if (s instanceof String) r += " (String)";
else r += " ('other')";
r += "> ";
}
switch (i) {
case 0: return "zero \"\"";
case 1: return "one : " + r;
case 2: return "two : " + r;
case 3: return "three : " + r;
default: return "too many: " + r;
}
}
}//class
zero ""
one : <A (String)>
two : <123 (int)> <B (String)>
three : <D (String)> <45.67 (double)> <F (char)>
too many: <99 (int)> <true (boolean)> <12.0 (double)> <J (String)>
This example also illustrates use of the instanceof
operator, which is an example of simple reflection. You should not depend on reflection too much, if at all, and hence try to not design with the instanceof
operator in mind.
As a matter of interest, for this particular example, we could have asked Java to get use the ‹type› name of each argument, using either the getName
method, or the getSimpleName
method from the java.lang.Class<T>
type returned by the getClass
method of java.lang.Object
:
These two methods are really only useful for debugging or learning; they do occur much, if at all, in production code or non-trivial programs.
Only Pass-by-Value
It is a common cause of confusion for newcomers to Java, but arguments are always passed by value, even if that value is a reference value. Pass-by-value as a concept, means “pass a copy” of the argument expression. Java offers no syntax or concept for pass-by-reference; just something to be aware of. If you want a function to modify some argument you pass, a few patterns are available to achieve the effect.
Consider a simple example: You want a function that takes a double
as parameter, which it must modify, e.g. it must increase its value by 50%. A common solution, is to put the single double
in an array variable and pass the array. Since a reference will be passed, the function can modify the first (and only) element. This is a bit of a hack, but the alternative is to wrap the double
(only) in a class.
NoPassByRef.java
— Alternative Pass-by-Reference Solutions
import static java.lang.System.*;
public class NoPassByRef {
public static void main (String[] args) {
/* assign return result to variable after modification.
*/ {
double da = 100.00; //←variable to by modified by function.
da = retFiftyPercent(da); //←function *returns* modification.
out.format("da = %.2f\n", da);
}
/* put `double` in an array, and pass array.
*/ {
double da = 100.00; //←variable to be modified by function.
double[] aa = { da }; //←temporarily store in an array.
addFiftyPercent(aa); //←pass the array.
da = aa[0]; //←assign new value from array.
out.format("da = %.2f\n", da);
}
/* wrap `double` in a class: `DoubleRef`, defined below.
*/ {
double da = 100.00; //←variable to be modified by function.
DoubleRef dr //←initialise new `DoubleRef` variable
= new DoubleRef(da); // with the `double` to modify.
refFiftyPercent(dr); //←pass a reference to `dr`.
da = dr.data; //←assign modified member to `da`.
out.format("da = %.2f\n", da);
}
exit(0);
}
static double retFiftyPercent (double val ) { return val * 1.50; }
static void addFiftyPercent (double[] val ) { val[0] *= 1.50; }
static void refFiftyPercent (DoubleRef val) { val.data *= 1.50; }
}//class
class DoubleRef {
public double data; //←accessible by all code.
DoubleRef (double data) { //←convenience constructor.
this.data = data;
}
}//class
Clearly, the preferable option is to have a function return a modified value, and assign that back to the variable which was also passed as argument. This is only an option though, if the function is to “modify” one argument. For multiple argument modifications with this method, you have to wrap the arguments in a class, so that you can still pass one argument, and return one result.
The option to put it in an array, is fine, except that the function looks a bit odd (we pass an array, but for the purposes of modifying the only element).
Abstractly, the best solution is to create a wrapper class. It does add more code, but the meaning of your program is clear. This is jumping the gun a bit, since we have not really discussed encapsulation, instance methods, and constructors yet, but it is a simple pattern nevertheless.
Recursion
Java functions can call themselves, and this is recursion. Any recursive algorithm can be rewritten iteratively, so you should not feel compelled to use recursion. The common refrain is that recursive algorithms can be more elegant. Recursion is basically just another way to iterate. It is also an easy way to get results in reverse order.
Recurse.java
— Simple Recursion Example
import java.lang.*;
import static java.lang.System.*;
public class Recurse {
public static void main (String[] args) {
out.print("\nR1: "); R1(5);
out.print("\nR2: "); R2(5);
out.print("\nR3: "); R3(5);
out.print("\nR4: "); R4(5);
out.print("\nR5: "); R5(5);
out.println();
}
static void R1 (int i) {
if (i > 0) {
R1(--i);
out.format("%d ", i);
}
}
static void R2 (int i) {
if (i > 0) {
out.format("%d ", i);
R2(--i);
}
}
static void R3 (int i) {
out.format("%d ", i);
if (i > 0)
R3(--i);
}
static void R4 (int i) {
if (i > 0)
R4(--i);
out.format("%d ", i);
}
static void R5 (int i) {
if (i > 0) {
R1(--i);
out.format("%d ", i);
R5(i);
}
}
}//class
R1: 0 1 2 3 4
R2: 5 4 3 2 1
R3: 5 4 3 2 1 0
R4: 0 0 1 2 3 4
R5: 0 1 2 3 4 0 1 2 3 0 1 2 0 1 0
As you can see, you get different effects and order, depending on where you do the actual work with the parameter; out.format("%d ", i)
in our case. The examples are not particularly useful, but does illustrate that recursion is a simple concept in principle. It simply uses the stack to create new copies of variables, which an iterative solution has to manage itself. The following iteration snippet, will produce exactly same result, for example, as R5(5)
:
And as final thoughts: in general, recursive algorithms are slower their iterative counterparts, potentially use more memory, and has the likelihood of exhausting the stack. So, use it only if you really think that your recursive solution is safe, and more comprehensible (or elegant).
Exception Handling
Ahem. It has come to this. To understand exceptions, you have to understand several prerequisite concepts. Exceptions concern “exceptional and unfortunate circumstances we hope will never come to pass”. It is not a mechanism for controlling the flow of a program. But events outside a program’s control may cause errors, so can bugs in code. Both are “unfortunate circumstances”, but we cannot just “hope”, we must be prepared for these events, even if they may seem unlikely.
Java and other languages encapsulates this concept under the umbrella term “exceptions”, and build a whole vocabulary of terms around it. For example, code may “throw” or “raise” an exception. Other parts of the code may “catch” or “handle” the exception. We shall use throw and catch exclusively, because Java has throw
and catch
keywords related to exceptions.
Thread of Execution
We can visualise the step-wise execution of a program as a thread that is fixed at the start of a program’s startup code: it unwinds with a call to main()
; then unwinds ever more as statements are executed. Calls to functions also unwind this proverbial thread, and calls to other functions within them, unwind it even further — to an arbitrary depth. Normally, only return
statements from functions can rewind the thread back up again. This trail that the thread leaves, is recorded on the stack. This is why this rewinding of the thread is often referred to as “unwinding the stack”, because the stack size decreases (it really should have been called “rewinding the thread”).
Exception handling can use this trail and follow it right back to the start, if necessary. In the process of retracing the thread, the exception handling mechanism will look for a handler in the current function. If not found, it will perform normal local variable clean-up, before it rewinds the thread to the caller, at which time it repeats the process, until it potentially reaches main()
. If still not caught in main()
, the program will terminate with an “unhandled exception” message.
Inheritance and Upcasting
Consider a class Deriv
, that inherits from (or extends) class Base
. In Java and other OOP languages, the one significant feature that prevails: is that a variable of type Base
, can store a reference to a Deriv
object, without an explicit cast (type conversion). This is sometimes referred to as an “up-cast”, simply because the convention in UML (Unified Modelling Language), is to draw the Base
class higher than Deriv
ed classes. Go figure.
If a class, let us call it Downward
, in turns inherits from Deriv
, a Base
variable can reference a Downward
object as well, without an explicit cast, because the conversion is still “upwards”.
Exception Objects
In order to throw
and exception, code cannot just throw anything: the type of the object must be “throwable” (java.lang.Throwable
class), which in practical terms mean that code can throw any type of object, as long as it inherits directly, or indirectly, from Throwable
. In the Java library classes, the java.lang.Exception
class is the major class that inherits from Throwable
, and almost all other classes that represent exceptions, in turn inherits from Exception
.
Practically, this means if you have a variable or parameter of type Exception
, you can pass any object that has the Exception
class in its ancestry (upwards). All methods that throw exception objects, document this fact, which is why the Java SE 8 Documentation is indispensable. And all will throw exceptions derived from Exception
(well, about 99%).
Catching Exceptions
To alert Java that your code is prepared to catch
an exception object, your code that has the potential to cause an exception to be throw
n, must be enclosed in a try
block. Following, the try
block, will be one or more catch
blocks, and optionally, a finally
block:
Syntax — Try-Catch-Finally Statement
try {
‹statement›…
}
catch (
‹type1› ‹ident›) {
‹statement›…
}
catch (
‹type2› ‹ident›) {
‹statement›…
}
⋯
finally {
‹statement›…
}
To avoid clutter, we did not show in the syntax that finally
is optional, and that only one catch
block is required. Multiple catch
blocks are optional, and the ‹type› must be different from other catch
block ‹type›s. The curly-brace blocks are mandatory, and forms a scope. The ‹ident›ifiers act logically like parameter to each catch
block, so you can use the same name in all catch
block parameters. Many use e
or ex
consistently.
In fact, unless the ‹type› is your own custom exception type, you must make sure that the deepest derived ‹type1› comes before the least-derived ‹type2›. That is because Java only looks for a viable catch
block for a throw
n object, not the best match. And if your custom ‹type› inherits from Exception
, directly or indirectly, you must still adhere to this rule. The main problem is that the Java compiler does not alert you to this potential problem (it could, it just doesn’t).
The finally
block is very special. If it is present, the statements inside this block, is guaranteed to execute… regardless if an exception was thrown or not; regardless if the function has a return
statement in the try
block, or not. A try
block may have only a finally
block (no catch
blocks); this is often used to close files, or dispose of resources that cannot be retained indefinitely.
Of course, statements inside a catch
or finally
block can also throw
exceptions, or cause exceptions to be throw
n. If code in a catch
block throws
‹ident› (the catch
block parameter), it is called a “re-throw”, and will restart the process of looking for a “catcher”, just not in the current function. Sometimes we catch exceptions for purposes other than “handling” the exception (or “dealing with the problem”), so we can do your thing, and just “re-throw” the exception, as if nothing happened. And yes, finally
code will still execute.
Exception Summary
This section exists to introduce you to the concepts of exception handling. It does not show any examples because that will obscure from the concepts we are trying to impart, but it does mean we shall be using exception handling more and more in example code. Because our code will have to become more sophisticated very soon, our examples will have to deal with, and even throw, exceptions.
2017-12-29: Edited. [jjc]
2017-12-28: Created. [brx]