C1 Java Primier
C1 Java Primier
1 Java Primer
Contents
Reserved Words
abstract default goto package synchronized
assert do if private this
boolean double implements protected throw
break else import public throws
byte enum instanceof return transient
case extends int short true
catch false interface static try
char final long strictfp void
class finally native super volatile
const float new switch while
continue for null
Table 1.1: A listing of the reserved words in Java. These names cannot be used as
class, method, or variable names.
Comments
We will intentionally color all comments in blue in this book, so that they are not
confused with executable code.
While inline comments are limited to one line, Java allows multiline comments
in the form of block comments. Java uses a “/*” to begin a block comment and a
“*/” to close it. For example:
/*
* This is a block comment.
*/
Block comments that begin with “/**” (note the second asterisk) have a special
purpose, allowing a program, called Javadoc, to read these comments and automat-
ically generate software documentation. We discuss the syntax and interpretation
of Javadoc comments in Section 1.9.4.
4 Chapter 1. Java Primer
For the most commonly used data types, Java provides the following base types
(also called primitive types):
A variable having one of these types simply stores a value of that type. Integer
constants, like 14 or 195, are of type int, unless followed immediately by an ‘L’
or ‘l’, in which case they are of type long. Floating-point constants, like 3.1416
or 6.022e23, are of type double, unless followed immediately by an ‘F’ or ‘f’, in
which case they are of type float. Code Fragment 1.1 demonstrates the declaration,
and initialization in some cases, of various base-type variables.
Note that it is possible to declare (and initialize) multiple variables of the same
type in a single statement, as done on lines 2, 6, and 9 of this example. In this code
fragment, variables verbose, debug, i, and j remain uninitialized. Variables declared
locally within a block of code must be initialized before they are first used.
A nice feature of Java is that when base-type variables are declared as instance
variables of a class (see next section), Java ensures initial default values if not ex-
plicitly initialized. In particular, all numeric types are initialized to zero, a boolean
is initialized to false, and a character is initialized to the null character by default.
1.2. Classes and Objects 5
This class includes one instance variable, named count, which is declared at
line 2. As noted on the previous page, the count will have a default value of zero,
unless we otherwise initialize it.
The class includes two special methods known as constructors (lines 3 and
4), one accessor method (line 5), and three update methods (lines 6–8). Unlike
the original Universe class from page 2, our Counter class does not have a main
method, and so it cannot be run as a complete program. Instead, the purpose of the
Counter class is to create instances that might be used as part of a larger program.
6 Chapter 1. Java Primer
Figure 1.2: Illustrating the relationship between objects and object reference vari-
ables. When we assign an object reference (that is, memory address) to a reference
variable, it is as if we are storing that object’s remote control at that variable.
There can, in fact, be many references to the same object, and each reference to
a specific object can be used to call methods on that object. Such a situation would
correspond to our having many remote controls that all work on the same device.
Any of the remotes can be used to make a change to the device (like changing a
channel on a television). Note that if one remote control is used to change the
device, then the (single) object pointed to by all the remotes changes. Likewise, if
one object reference variable is used to change the state of the object, then its state
changes for all the references to it. This behavior comes from the fact that there are
many references, but they all point to the same object.
Returning to our CounterDemo example, the instance constructed at line 9 as
Counter d = new Counter(5);
is a distinct instance from the one identified as c. However, the command at line 11,
Counter e = d;
does not result in the construction of a new Counter instance. This declares a new
reference variable named e, and assigns that variable a reference to the existing
counter instance currently identified as d. At that point, both variables d and e are
aliases for the same object, and so the call to [Link]( ) behaves just as would
[Link]( ). Similarly, the call to update method [Link](2) is affecting the
same object identified by d.
It is worth noting, however, that the aliasing of two reference variables to the
same object is not permanent. At any point in time, we may reassign a reference
variable to a new instance, to a different existing instance, or to null.
1.2. Classes and Objects 9
Modifiers
Immediately before the definition of a class, instance variable, or method in Java,
keywords known as modifiers can be placed to convey additional stipulations about
that definition.
Declaring Methods
A method definition has two parts: the signature, which defines the name and
parameters for a method, and the body, which defines what the method does. The
method signature specifies how the method is called, and the method body specifies
what the object will do when it is called. The syntax for defining a method is as
follows:
[modifiers] returnType methodName(type1 param1 , . . . , typen paramn ) {
// method body . . .
}
Each of the pieces of this declaration has an important purpose. We have al-
ready discussed the significance of modifiers such as public, private, and static.
The returnType designation defines the type of value returned by the method. The
methodName can be any valid Java identifier. The list of parameters and their types
declares the local variables that correspond to the values that are to be passed as
arguments to this method. Each type declaration typei can be any Java type name
and each parami can be any distinct Java identifier. This list of parameters and
their types can be empty, which signifies that there are no values to be passed to
this method when it is invoked. These parameter variables, as well as the instance
variables of the class, can be used inside the body of the method. Likewise, other
methods of this class can be called from inside the body of a method.
When a (nonstatic) method of a class is called, it is invoked on a specific in-
stance of that class and can change the state of that object. For example, the follow-
ing method of the Counter class increases the counter’s value by the given amount.
public void increment(int delta) {
count += delta;
}
Notice that the body of this method uses count, which is an instance variable, and
delta, which is a parameter.
Return Types
A method definition must specify the type of value the method will return. If the
method does not return a value (as with the increment method of the Counter class),
then the keyword void must be used. To return a value in Java, the body of the
method must use the return keyword, followed by a value of the appropriate return
type. Here is an example of a method (from the Counter class) with a nonvoid
return type:
public int getCount( ) {
return count;
}
1.2. Classes and Objects 13
Java methods can return only one value. To return multiple values in Java, we
should instead combine all the values we want to return in a compound object,
whose instance variables include all the values we want to return, and then return a
reference to that compound object. In addition, we can change the internal state of
an object that is passed to a method as another way of “returning” multiple results.
Parameters
Defining Constructors
A constructor is a special kind of method that is used to initialize a newly created
instance of the class so that it will be in a consistent and stable initial state. This
is typically achieved by initializing each instance variable of the object (unless
the default value will suffice), although a constructor can perform more complex
computation. The general syntax for declaring a constructor in Java is as follows:
modifiers name(type0 parameter0 , . . . , typen−1 parametern−1 ) {
// constructor body . . .
}
Constructors are defined in a very similar way as other methods of a class, but there
are a few important distinctions:
1. Constructors cannot be static, abstract, or final, so the only modifiers that
are allowed are those that affect visibility (i.e., public, protected, private,
or the default package-level visibility).
2. The name of the constructor must be identical to the name of the class it
constructs. For example, when defining the Counter class, a constructor must
be named Counter as well.
3. We don’t specify a return type for a constructor (not even void). Nor does
the body of a constructor explicitly return anything. When a user of a class
creates an instance using a syntax such as
Counter d = new Counter(5);
the new operator is responsible for returning a reference to the new instance
to the caller; the responsibility of the constructor method is only to initialize
the state of the new instance.
A class can have many constructors, but each must have a different signature,
that is, each must be distinguished by the type and number of the parameters it
takes. If no constructors are explicitly defined, Java provides an implicit default
constructor for the class, having zero arguments and leaving all instance variables
initialized to their default values. However, if a class defines one or more nondefault
constructors, no default constructor will be provided.
As an example, our Counter class defines the following pair of constructors:
public Counter( ) { }
public Counter(int initial) { count = initial; }
The first of these has a trivial body, { }, as the goal for this default constructor is to
create a counter with value zero, and that is already the default value of the integer
instance variable, count. However, it is still important that we declared such an
explicit constructor, because otherwise none would have been provided, given the
existence of the nondefault constructor. In that scenario, a user would have been
unable to use the syntax, new Counter( ).
1.2. Classes and Objects 15
3. To allow one constructor body to invoke another constructor body. When one
method of a class invokes another method of that same class on the current
instance, that is typically done by using the (unqualified) name of the other
method. But the syntax for calling a constructor is special. Java allows use of
the keyword this to be used as a method within the body of one constructor,
so as to invoke another constructor with a different signature.
This is often useful because all of the initialization steps of one constructor
can be reused with appropriate parameterization. As a trivial demonstra-
tion of the syntax, we could reimplement the zero-argument version of our
Counter constructor to have it invoke the one-argument version sending 0 as
an explicit parameter. This would be written as follows:
public Counter( ) {
this(0); // invoke one-parameter constructor with value zero
}
We will provide a more meaningful demonstration of this technique in a later
example of a CreditCard class in Section 1.7.
[Link]
16 Chapter 1. Java Primer
Unit Testing
When defining a class, such as Counter, that is meant to be used by other classes
rather than as a self-standing program, there is no need to define a main method.
However, a nice feature of Java’s design is that we could provide such a method
as a way to test the functionality of that class in isolation, knowing that it would
not be run unless we specifically invoke the java command on that isolated class.
However, for more robust testing, frameworks such as JUnit are preferred.
1.3. Strings, Wrappers, Arrays, and Enum Types 17
Character Indexing
Each character c within a string s can be referenced by using an index, which is
equal to the number of characters that come before c in s. By this convention, the
first character is at index 0, and the last is at index n − 1, where n is the length of the
string. For example, the string title, defined above, has length 36. The character at
index 2 is 't' (the third character), and the character at index 4 is ' ' (the space
character). Java’s String class supports a method length( ), which returns the length
of a string instance, and a method charAt(k), which returns the character at index k.
Concatenation
The primary operation for combining strings is called concatenation, which takes
a string P and a string Q combines them into a new string, denoted P + Q, which
consists of all the characters of P followed by all the characters of Q. In Java, the
“+” operation performs concatenation when acting on two strings, as follows:
String term = "over" + "load";
This statement defines a variable named term that references a string with value
"overload". (We will discuss assignment statements and expressions such as that
above in more detail later in this chapter.)
18 Chapter 1. Java Primer
An error condition occurs, for both String and StringBuilder classes, if an index k
is out of the bounds of the indices of the character sequence.
The StringBuilder class can be very useful, and it serves as an interesting case
study for data structures and algorithms. We will further explore the empirical ef-
ficiency of the StringBuilder class in Section 4.1 and the theoretical underpinnings
of its implementation in Section 7.2.4.
1.3. Strings, Wrappers, Arrays, and Enum Types 19
Wrapper Types
There are many data structures and algorithms in Java’s libraries that are specif-
ically designed so that they only work with object types (not primitives). To get
around this obstacle, Java defines a wrapper class for each base type. An instance
of each wrapper type stores a single value of the corresponding base type. In Ta-
ble 1.2, we show the base types and their corresponding wrapper class, along with
examples of how objects are created and accessed.
Table 1.2: Java’s wrapper classes. Each class is given with its corresponding base
type and example expressions for creating and accessing such objects. For each
row, we assume the variable obj is declared with the corresponding class name.
1 int j = 8;
2 Integer a = new Integer(12);
3 int k = a; // implicit call to [Link]()
4 int m = j + a; // a is automatically unboxed before the addition
5 a = 3 ∗ m; // result is automatically boxed before assignment
6 Integer b = new Integer("-135"); // constructor accepts a String
7 int n = [Link]("2013"); // using static method of Integer class
Code Fragment 1.4: A demonstration of the use of the Integer wrapper class.
20 Chapter 1. Java Primer
Arrays
A common programming task is to keep track of an ordered sequence of related
values or objects. For example, we may want a video game to keep track of the top
ten scores for that game. Rather than using ten different variables for this task, we
would prefer to use a single name for the group and use index numbers to refer to
the high scores in that group. Similarly, we may want a medical information system
to keep track of the patients currently assigned to beds in a certain hospital. Again,
we would rather not have to introduce 200 variables in our program just because
the hospital has 200 beds.
In such cases, we can save programming effort by using an array, which is a
sequenced collection of variables all of the same type. Each variable, or cell, in an
array has an index, which uniquely refers to the value stored in that cell. The cells
of an array a are numbered 0, 1, 2, and so on. We illustrate an array of high scores
for a video game in Figure 1.3.
High
scores
indices
Figure 1.3: An illustration of an array of ten (int) high scores for a video game.
Arrays in Java are somewhat unusual, in that they are not technically a base type
nor are they instances of a particular class. With that said, an instance of an array is
treated as an object by Java, and variables of an array type are reference variables.
To declare a variable (or parameter) to have an array type, we use an empty
pair of square brackets just after the type of element that the array will store. For
example, we might declare:
int[ ] primes;
Because arrays are a reference type, this declares the variable primes to be a refer-
ence to an array of integer values, but it does not immediately construct any such
array. There are two ways for creating an array.
The first way to create an array is to use an assignment to a literal form when
initially declaring the array, using a syntax as:
elementType[ ] arrayName = {initialValue0 , initialValue1 , . . . , initialValue N−1 };
The elementType can be any Java base type or class name, and arrayName can be
any valid Java identifier. The initial values must be of the same type as the array.
For example, we could initialize the array of primes to contain the first ten prime
numbers as:
int[ ] primes = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29};
When using an initializer, an array is created having precisely the capacity needed
to store the indicated values.
The second way to create an array is to use the new operator. However, because
an array is not an instance of a class, we do not use a typical constructor syntax.
Instead we use the syntax:
new elementType[length]
where length is a positive integer denoting the length of the new array. The new
operator returns a reference to the new array, and typically this would be assigned to
an array variable. For example, the following statement declares an array variable
named measurements, and immediately assigns it a new array of 1000 cells.
double[ ] measurements = new double[1000];
When arrays are created using the new operator, all of their elements are au-
tomatically assigned the default value for the element type. That is, if the element
type is numeric, all cells of the array are initialized to zero, if the element type is
boolean, all cells are false, and if the element type is a reference type (such as with
an array of String instances), all cells are initialized to null.
22 Chapter 1. Java Primer
Enum Types
In olden times, programmers would often define a series of constant integer values
to be used for representing a finite set of choices. For example, in representing a
day of the week, they might declare variable today as an int and then set it with
value 0 for Monday, 1 for Tuesday, and so on.
A slightly better programming style is to define static constants (with the final
keyword), to make the associations, such as:
static final int MON = 0;
static final int TUE = 1;
static final int WED = 2;
...
because then it becomes possible to make assignments such as today = TUE,
rather than the more obscure today = 1. Unfortunately, the variable today is still
declared as an int using such a programming style, and it may not be clear that you
intend for it to represent a day of the week when storing it as an instance variable
or sending it as a parameter.
Java supports a more elegant approach to representing choices from a finite
set by defining what is known as an enumerated type, or enum for short. These
are types that are only allowed to take on values that come from a specified set of
names. They are declared as follows:
where the modifier can be blank, public, protected, or private. The name of
this enum, name, can be any legal Java identifier. Each of the value identifiers,
valueNamei , is the name of a possible value that variables of this enum type can
take on. Each of these name values can also be any legal Java identifier, but the
Java convention is that these should usually be capitalized words. For example, an
enumerated type definition for days of the weak might appear as:
public enum Day { MON, TUE, WED, THU, FRI, SAT, SUN };
Once defined, Day becomes an official type and we may declare variables or pa-
rameters with type Day. A variable of that type can be declared as:
Day today;
and an assignment of a value to that variable can appear as:
today = [Link];
[Link]
1.4. Expressions 23
1.4 Expressions
Variables and constants are used in expressions to define new values and to modify
variables. In this section, we discuss how expressions work in Java in more detail.
Expressions involve the use of literals, variables, and operators. Since we have al-
ready discussed variables, let us briefly focus on literals and then discuss operators
in some detail.
1.4.1 Literals
A literal is any “constant” value that can be used in an assignment or other expres-
sion. Java allows the following kinds of literals:
• The null object reference (this is the only object literal, and it is allowed to
be any reference type).
• Boolean: true and false.
• Integer: The default for an integer like 176, or -52 is that it is of type int,
which is a 32-bit integer. A long integer literal must end with an “L” or “l”,
for example, 176L or -52l, and defines a 64-bit integer.
• Floating Point: The default for floating-point numbers, such as 3.1415 and
135.23, is that they are double. To specify that a literal is a float, it must
end with an “F” or “f”. Floating-point literals in exponential notation are also
allowed, such as 3.14E2 or .19e10; the base is assumed to be 10.
• Character: In Java, character constants are assumed to be taken from the
Unicode alphabet. Typically, a character is defined as an individual symbol
enclosed in single quotes. For example, ’a’ and ’?’ are character constants.
In addition, Java defines the following special character constants:
1.4.2 Operators
Java expressions involve composing literals and variables with operators. We will
survey the operators in Java in this section.
Arithmetic Operators
The following are binary arithmetic operators in Java:
+ addition
− subtraction
∗ multiplication
/ division
% the modulo operator
This last operator, modulo, is also known as the “remainder” operator, because
it is the remainder left after an integer division. We often use “ mod ” to denote the
modulo operator, and we define it formally as
n mod m = r,
such that
n = mq + r,
for an integer q and 0 ≤ r < m.
Java also provides a unary minus (−), which can be placed in front of an arith-
metic expression to invert its sign. Parentheses can be used in any expression to
define the order of evaluation. Java also uses a fairly intuitive operator precedence
rule to determine the order of evaluation when parentheses are not used. Unlike
C++, Java does not allow operator overloading for class types.
String Concatenation
With strings, the (+) operator performs concatenation, so that the code
String rug = "carpet";
String dog = "spot";
String mess = rug + dog;
String answer = mess + " will cost me " + 5 + " hours!";
would have the effect of making answer refer to the string
"carpetspot will cost me 5 hours!"
This example also shows how Java converts nonstring values (such as 5) into strings,
when they are involved in a string concatenation operation.
1.4. Expressions 25
Increment and Decrement Operators
Like C and C++, Java provides increment and decrement operators. Specifically, it
provides the plus-one increment (++) and decrement (−−) operators. If such an
operator is used in front of a variable reference, then 1 is added to (or subtracted
from) the variable and its value is read into the expression. If it is used after a
variable reference, then the value is first read and then the variable is incremented
or decremented by 1. So, for example, the code fragment
int i = 8;
int j = i++; // j becomes 8 and then i becomes 9
int k = ++i; // i becomes 10 and then k becomes 10
int m = i−−; // m becomes 10 and then i becomes 9
int n = 9 + −−i; // i becomes 8 and then n becomes 17
assigns 8 to j, 10 to k, 10 to m, 17 to n, and returns i to value 8, as noted.
Logical Operators
Java supports the standard comparisons operators between numbers:
< less than
<= less than or equal to
== equal to
!= not equal to
>= greater than or equal to
> greater than
The type of the result of any of these comparison is a boolean. Comparisons may
also be performed on char values, with inequalities determined according to the
underlying character codes.
For reference types, it is important to know that the operators == and != are
defined so that expression a == b is true if a and b both refer to the identical
object (or are both null). Most object types support an equals method, such that
[Link](b) is true if a and b refer to what are deemed as “equivalent” instances for
that class (even if not the same instance); see Section 3.5 for further discussion.
Operators defined for boolean values are the following:
! not (prefix)
&& conditional and
|| conditional or
The boolean operators && and | | will not evaluate the second operand (to the right)
in their expression if it is not needed to determine the value of the expression. This
“short circuiting” feature is useful for constructing boolean expressions where we
first test that a certain condition holds (such as an array index being valid) and then
test a condition that could have otherwise generated an error condition had the prior
test not succeeded.
26 Chapter 1. Java Primer
Bitwise Operators
Java also provides the following bitwise operators for integers and booleans:
∼ bitwise complement (prefix unary operator)
& bitwise and
| bitwise or
ˆ bitwise exclusive-or
<< shift bits left, filling in with zeros
>> shift bits right, filling in with sign bit
>>> shift bits right, filling in with zeros
variable = expression
Operators in Java are given preferences, or precedence, that determine the order in
which operations are performed when the absence of parentheses brings up eval-
uation ambiguities. For example, we need a way of deciding if the expression,
“5+2*3,” has value 21 or 11 (Java says it is 11). We show the precedence of the
operators in Java (which, incidentally, is the same as in C and C++) in Table 1.3.
Operator Precedence
Type Symbols
1 array index []
method call ()
dot operator .
2 postfix ops exp++ exp−−
prefix ops ++exp −−exp +exp −exp ˜exp !exp
cast (type) exp
3 mult./div. ∗ / %
4 add./subt. + −
5 shift << >> >>>
6 comparison < <= > >= instanceof
7 equality == !=
8 bitwise-and &
9 bitwise-xor ˆ
10 bitwise-or |
11 and &&
12 or ||
13 conditional booleanExpression ? valueIfTrue : valueIfFalse
14 assignment = += −= ∗= /= %= <<= >>= >>>= &= ˆ= |=
Table 1.3: The Java precedence rules. Operators in Java are evaluated according to
the ordering above if parentheses are not used to determine the order of evaluation.
Operators on the same line are evaluated in left-to-right order (except for assign-
ment and prefix operations, which are evaluated in right-to-left order), subject to
the conditional evaluation rule for boolean && and | | operations. The operations
are listed from highest to lowest precedence (we use exp to denote an atomic or
parenthesized expression). Without parenthesization, higher precedence operators
are performed before lower precedence operators.
We have now discussed almost all of the operators listed in Table 1.3. A notable
exception is the conditional operator, which involves evaluating a boolean expres-
sion and then taking on the appropriate value depending on whether this boolean
expression is true or false. (We discuss the use of the instanceof operator in the
next chapter.)
28 Chapter 1. Java Primer
Explicit Casting
Java supports an explicit casting syntax with the following form:
(type) exp
where type is the type that we would like the expression exp to have. This syntax
may only be used to cast from one primitive type to another primitive type, or
from one reference type to another reference type. We will discuss its use between
primitives here, and between reference types in Section 2.5.1.
Casting from an int to a double is known as a widening cast, as the double
type is more broad than the int type, and a conversion can be performed without
losing information. But a cast from a double to an int is a narrowing cast; we may
lose precision, as any fractional portion of the value will be truncated. For example,
consider the following:
double d1 = 3.2;
double d2 = 3.9999;
int i1 = (int) d1; // i1 gets value 3
int i2 = (int) d2; // i2 gets value 3
double d3 = (double) i2; // d3 gets value 3.0
Although explicit casting cannot directly convert a primitive type to a reference
type, or vice versa, there are other means for performing such type conversions.
We already discussed, as part of Section 1.3, conversions between Java’s primi-
tive types and corresponding wrapper classes (such as int and Integer). For con-
venience, those wrapper classes also provide static methods that convert between
their corresponding primitive type and String values.
For example, the [Link] method accepts an int parameter and returns
a String representation of that integer, while the [Link] method accepts a
String as a parameter and returns the corresponding int value that the string rep-
resents. (If that string does not represent an integer, a NumberFormatException
results.) We demonstrate their use as follows:
String s1 = "2014";
int i1 = [Link](s1); // i1 gets value 2014
int i2 = −35;
String s2 = [Link](i2); // s2 gets value ”-35”
Similar methods are supported by other wrapper types, such as Double.
1.4. Expressions 29
Implicit Casting
There are cases where Java will perform an implicit cast based upon the context
of an expression. For example, you can perform a widening cast between primitive
types (such as from an int to a double), without explicit use of the casting operator.
However, if attempting to do an implicit narrowing cast, a compiler error results.
For example, the following demonstrates both a legal and an illegal implicit cast
via assignment statements:
int i1 = 42;
double d1 = i1; // d1 gets value 42.0
i1 = d1; // compile error: possible loss of precision
The If Statement
The syntax of a simple if statement is as follows:
if (booleanExpression)
trueBody
else
falseBody
if (firstBooleanExpression)
firstBody
else if (secondBooleanExpression)
secondBody
else
thirdBody
If the first boolean expression is false, the second boolean expression will be tested,
and so on. An if statement can have an arbitrary number of else if parts. Braces
can be used for any or all statement bodies to define their extent.
1.5. Control Flow 31
As a simple example, a robot controller might have the following logic:
if ([Link]( ))
[Link]( );
advance( );
Notice that the final command, advance( ), is not part of the conditional body; it
will be executed unconditionally (although after opening a closed door).
We may nest one control structure within another, relying on explicit braces to
mark the extent of the various bodies if needed. Revisiting our robot example, here
is a more complex control that accounts for unlocking a closed door.
if ([Link]( )) {
if ([Link]( ))
[Link]( );
[Link]( );
}
advance( );
The logic expressed by this example can be diagrammed as a traditional flowchart,
as portrayed in Figure 1.4.
false true
[Link]( )
false true
[Link]( )
[Link]( )
[Link]( )
advance( )
Switch Statements
Java provides for multiple-value control flow using the switch statement, which is
especially useful with enum types. The following is an indicative example (based
on a variable d of the Day enum type of Section 1.3).
switch (d) {
case MON:
[Link]("This is tough.");
break;
case TUE:
[Link]("This is getting better.");
break;
case WED:
[Link]("Half way there.");
break;
case THU:
[Link]("I can see the light.");
break;
case FRI:
[Link]("Now we are talking.");
break;
default:
[Link]("Day off!");
}
The switch statement evaluates an integer, string, or enum expression and
causes control flow to jump to the code location labeled with the value of this
expression. If there is no matching label, then control flow jumps to the location la-
beled “default.” This is the only explicit jump performed by the switch statement,
however, so flow of control “falls through” to the next case if the code for a case is
not ended with a break statement (which causes control flow to jump to the end).
1.5. Control Flow 33
1.5.2 Loops
Another important control flow mechanism in a programming language is looping.
Java provides for three types of loops.
While Loops
The simplest kind of loop in Java is a while loop. Such a loop tests that a certain
condition is satisfied and will perform the body of the loop each time this condition
is evaluated to be true. The syntax for such a conditional test before a loop body
is executed is as follows:
while (booleanExpression)
loopBody
As with an if statement, booleanExpression, can be an arbitrary boolean expres-
sion, and the body of the loop can be an arbitrary block of code (including nested
control structures). The execution of a while loop begins with a test of the boolean
condition. If that condition evaluates to true, the body of the loop is performed.
After each execution of the body, the loop condition is retested and if it evaluates
to true, another iteration of the body is performed. If the condition evaluates to
false when tested (assuming it ever does), the loop is exited and the flow of control
continues just beyond the body of the loop.
As an example, here is a loop that advances an index through an array named
data until finding an entry with value target or reaching the end of the array.
int j = 0;
while ((j < [Link]) && (data[j] != target))
j++;
When this loop terminates, variable j’s value will be the index of the leftmost occur-
rence of target, if found, or otherwise the length of the array (which is recognizable
as an invalid index to indicate failure of the search). The correctness of the loop
relies on the short-circuiting behavior of the logical && operator, as described on
page 25. We intentionally test j < [Link] to ensure that j is a valid index,
prior to accessing element data[j]. Had we written that compound condition with
the opposite order, the evaluation of data[j] would eventually throw an ArrayIndex-
OutOfBoundsException if the target is not found. (See Section 2.4 for discussion
of exceptions.)
We note that a while loop will execute its body zero times in the case that the
initial condition fails. For example, our above loop will not increment the value of j
if data[0] matches the target (or if the array has length 0).
34 Chapter 1. Java Primer
Do-While Loops
Java has another form of the while loop that allows the boolean condition to be
checked at the end of each pass of the loop rather than before each pass. This form
is known as a do-while loop, and has syntax shown below:
do
loopBody
while (booleanExpression)
A consequence of the do-while loop is that its body always executes at least
once. (In contrast, a while loop will execute zero times if the initial condition fails.)
This form is most useful for a situation in which the condition is ill-defined until
after at least one pass. Consider, for example, that we want to prompt the user for
input and then do something useful with that input. (We discuss Java input and
output in more detail in Section 1.6.) A possible condition, in this case, for exiting
the loop is when the user enters an empty string. However, even in this case, we
may want to handle that input and inform the user that he or she has quit. The
following example illustrates this case:
String input;
do {
input = getInputString( );
handleInput(input);
} while ([Link]( ) > 0);
For Loops
Another kind of loop is the for loop. Java supports two different styles of for loop.
The first, which we will refer to as the “traditional” style, is patterned after a similar
syntax as for loops in the C and C++ languages. The second style, which is known
as the “for-each” loop, was introduced into Java in 2004 as part of the SE 5 release.
This style provides a more succinct syntax for iterating through elements of an
array or an appropriate container type.
The traditional for-loop syntax consists of four sections—an initialization, a
boolean condition, an increment statement, and the body—although any of those
can be empty. The structure is as follows:
for (initialization; booleanCondition; increment)
loopBody
For example, the most common use of a for loop provides repetition based on an
integer index, such as the following:
for (int j=0; j < n; j++)
// do something
1.5. Control Flow 35
The behavior of a for loop is very similar to the following while loop equivalent:
{
initialization;
while (booleanCondition) {
loopBody;
increment;
}
}
The initialization section will be executed once, before any other portion of the loop
begins. Traditionally, it is used to either initialize existing variables, or to declare
and initialize new variables. Note that any variables declared in the initialization
section only exist in scope for the duration of the for loop.
The booleanCondition will be evaluated immediately before each potential it-
eration of the loop. It should be expressed similar to a while-loop condition, in
that if it is true, the loop body is executed, and if false, the loop is exited and the
program continues to the next statement beyond the for-loop body.
The increment section is executed immediately after each iteration of the formal
loop body, and is traditionally used to update the value of the primary loop vari-
able. However, the incrementing statement can be any legal statement, allowing
significant flexibility in coding.
As a concrete example, here is a method that computes the sum of an array of
double values using a for loop:
public static double sum(double[ ] data) {
double total = 0;
for (int j=0; j < [Link]; j++) // note the use of length
total += data[j];
return total;
}
As one further example, the following method computes the maximum value
within a (nonempty) array.
public static double max(double[ ] data) {
double currentMax = data[0]; // assume first is biggest (for now)
for (int j=1; j < [Link]; j++) // consider all other entries
if (data[j] > currentMax) // if data[j] is biggest thus far...
currentMax = data[j]; // record it as the current max
return currentMax;
}
Notice that a conditional statement is nested within the body of the loop, and that no
explicit “{” and “}” braces are needed for the loop body, as the entire conditional
construct serves as a single statement.
36 Chapter 1. Java Primer
For-Each Loop
where container is an array of the given elementType (or a collection that imple-
ments the Iterable interface, as we will later discuss in Section 7.4.1).
Revisiting a previous example, the traditional loop for computing the sum of
elements in an array of double values can be written as:
When using a for-each loop, there is no explicit use of array indices. The loop
variable represents one particular element of the array. However, within the body
of the loop, there is no designation as to which element it is.
It is also worth emphasizing that making an assignment to the loop variable has
no effect on the underlying array. Therefore, the following method is an invalid
attempt to scale all values of a numeric array.
In order to overwrite the values in the cells of an array, we must make use of
indices. Therefore, this task is best solved with a traditional for loop, such as the
following:
• The class defines five instance variables (lines 3–7), four of which are de-
clared as private and one that is protected. (We will take advantage of the
protected balance member when introducing inheritance in the next chap-
ter.)
• The class defines two different constructor forms. The first version (begin-
ning at line 9) requires five parameters, including an explicit initial balance
for the account. The second constructor (beginning at line 16) accepts only
four parameters; it relies on use of the special this keyword to invoke the
five-parameter version, with an explicit initial balance of zero (a reasonable
default for most new accounts).
• The class defines five basic accessor methods (lines 20–24), and two update
methods (charge and makePayment). The charge method relies on condi-
tional logic to ensure that a charge is rejected if it would have resulted in the
balance exceeding the credit limit on the card.
• The main method includes an array, named wallet, storing CreditCard in-
stances. The main method also demonstrates a while loop, a traditional
for loop, and a for-each loop over the contents of the wallet.
• The main method demonstrates the syntax for calling traditional (nonstatic)
methods—charge, getBalance, and makePayment—as well as the syntax for
invoking the static printSummary method.
42 Chapter 1. Java Primer
1.9.1 Design
For object-oriented programming, the design step is perhaps the most important
phase in the process of developing software. It is in the design step that we decide
how to divide the workings of our program into classes, when we decide how these
classes will interact, what data each will store, and what actions each will perform.
Indeed, one of the main challenges that beginning programmers face is deciding
what classes to define to do the work of their program. While general prescrip-
tions are hard to come by, there are some rules of thumb that we can apply when
determining how to define our classes:
• Responsibilities: Divide the work into different actors, each with a different
responsibility. Try to describe responsibilities using action verbs. These
actors will form the classes for the program.
• Independence: Define the work for each class to be as independent from
other classes as possible. Subdivide responsibilities between classes so that
each class has autonomy over some aspect of the program. Give data (as in-
stance variables) to the class that has jurisdiction over the actions that require
access to this data.
• Behaviors: Define the behaviors for each class carefully and precisely, so
that the consequences of each action performed by a class will be well un-
derstood by other classes that interact with it. These behaviors will define
the methods that this class performs, and the set of behaviors for a class form
the protocol by which other pieces of code will interact with objects from the
class.
Defining the classes, together with their instance variables and methods, are key
to the design of an object-oriented program. A good programmer will naturally
develop greater skill in performing these tasks over time, as experience teaches
him or her to notice patterns in the requirements of a program that match patterns
that he or she has seen before.
1.9. Software Development 47
A common tool for developing an initial high-level design for a project is the
use of CRC cards. Class-Responsibility-Collaborator (CRC) cards are simple in-
dex cards that subdivide the work required of a program. The main idea behind this
tool is to have each card represent a component, which will ultimately become a
class in the program. We write the name of each component on the top of an index
card. On the left-hand side of the card, we begin writing the responsibilities for
this component. On the right-hand side, we list the collaborators for this compo-
nent, that is, the other components that this component will have to interact with to
perform its duties.
The design process iterates through an action/actor cycle, where we first iden-
tify an action (that is, a responsibility), and we then determine an actor (that is, a
component) that is best suited to perform that action. The design is complete when
we have assigned all actions to actors. In using index cards for this process (rather
than larger pieces of paper), we are relying on the fact that each component should
have a small set of responsibilities and collaborators. Enforcing this rule helps keep
the individual classes manageable.
As the design takes form, a standard approach to explain and document the
design is the use of UML (Unified Modeling Language) diagrams to express the
organization of a program. UML diagrams are a standard visual notation to express
object-oriented software designs. Several computer-aided tools are available to
build UML diagrams. One type of UML figure is known as a class diagram.
An example of a class diagram is given in Figure 1.5, corresponding to our
CreditCard class from Section 1.7. The diagram has three portions, with the first
designating the name of the class, the second designating the recommended in-
stance variables, and the third designating the recommended methods of the class.
The type declarations of variables, parameters, and return values are specified in
the appropriate place following a colon, and the visibility of each member is des-
ignated on its left, with the “+” symbol for public visibility, the “#” symbol for
protected visibility, and the “−” symbol for private visibility.
class: CreditCard
fields: − customer : String − limit : int
− bank : String # balance : double
− account : String
methods: + getCustomer( ) : String + getAccount( ) : String
+ getBank( ) : String + getLimit( ) : int
+ charge(price : double) : boolean + getBalance( ) : double
+ makePayment(amount : double)
Figure 1.5: A UML Class diagram for the CreditCard class from Section 1.7.
48 Chapter 1. Java Primer
1.9.2 Pseudocode
As an intermediate step before the implementation of a design, programmers are
often asked to describe algorithms in a way that is intended for human eyes only.
Such descriptions are called pseudocode. Pseudocode is not a computer program,
but is more structured than usual prose. It is a mixture of natural language and
high-level programming constructs that describe the main ideas behind a generic
implementation of a data structure or algorithm. Because pseudocode is designed
for a human reader, not a computer, we can communicate high-level ideas without
being burdened by low-level implementation details. At the same time, we should
not gloss over important steps. Like many forms of human communication, finding
the right balance is an important skill that is refined through practice.
There really is no precise definition of the pseudocode language. At the same
time, to help achieve clarity, pseudocode mixes natural language with standard
programming language constructs. The programming language constructs that we
choose are those consistent with modern high-level languages such as C, C++, and
Java. These constructs include the following:
• Expressions: We use standard mathematical symbols to express numeric and
boolean expressions. To be consistent with Java, we use the equal sign “=”
as the assignment operator in assignment statements, and the “==” relation
to test equivalence in boolean expressions.
• Method declarations: Algorithm name(param1, param2, . . .) declares new
method “name” and its parameters.
• Decision structures: if condition then true-actions [else false-actions]. We
use indentation to indicate what actions should be included in the true-actions
and false-actions.
• While-loops: while condition do actions. We use indentation to indicate
what actions should be included in the loop actions.
• Repeat-loops: repeat actions until condition. We use indentation to indicate
what actions should be included in the loop actions.
• For-loops: for variable-increment-definition do actions. We use indentation
to indicate what actions should be included among the loop actions.
• Array indexing: A[i] represents the i th cell in the array A. The cells of an
n-celled array A are indexed from A[0] to A[n − 1] (consistent with Java).
• Method calls: [Link](args); object is optional if it is understood.
• Method returns: return value. This operation returns the value specified to
the method that called this one.
• Comments: { Comment goes here. }. We enclose comments in braces.
1.9. Software Development 49
1.9.3 Coding
One of the key steps in implementing an object-oriented program is coding the
descriptions of classes and their respective data and methods. In order to accelerate
the development of this skill, we will discuss various design patterns for designing
object-oriented programs (see Section 2.1.3) at various points throughout this text.
These patterns provide templates for defining classes and the interactions between
these classes.
Once we have settled on a design for the classes or our program and their re-
sponsibilities, and perhaps drafted pseudocode for their behaviors, we are ready to
begin the actual coding on a computer. We type the Java source code for the classes
of our program by using either an independent text editor (such as emacs, WordPad,
or vi), or the editor embedded in an integrated development environment (IDE),
such as Eclipse.
Once we have completed coding for a class (or package), we compile this file
into working code by invoking a compiler. If we are not using an IDE, then we
compile our program by calling a program, such as javac, on our file. If we are
using an IDE, then we compile our program by clicking the appropriate compila-
tion button. If we are fortunate, and our program has no syntax errors, then this
compilation process will create files with a “.class” extension.
If our program contains syntax errors, then these will be identified, and we will
have to go back into our editor to fix the offending lines of code. Once we have
eliminated all syntax errors, and created the appropriate compiled code, we can run
our program by either invoking a command, such as “java” (outside an IDE), or
by clicking on the appropriate “run” button (within an IDE). When a Java program
is run in this way, the runtime environment locates the directories containing the
named class and any other classes that are referenced from this class according to
a special operating system environment variable named “CLASSPATH.” This vari-
able defines an order of directories in which to search, given as a list of directories,
which are separated by colons in Unix/Linux or semicolons in DOS/Windows. An
example CLASSPATH assignment in the DOS/Windows operating system could
be the following:
In both cases, the dot (“.”) refers to the current directory in which the runtime
environment is invoked.
50 Chapter 1. Java Primer
Testing
A careful testing plan is an essential part of writing a program. While verifying the
correctness of a program over all possible inputs is usually infeasible, we should
aim at executing the program on a representative subset of inputs. At the very
minimum, we should make sure that every method of a program is tested at least
once (method coverage). Even better, each code statement in the program should
be executed at least once (statement coverage).
Programs often tend to fail on special cases of the input. Such cases need to be
carefully identified and tested. For example, when testing a method that sorts (that
is, puts in order) an array of integers, we should consider the following inputs:
• The array has zero length (no elements).
• The array has one element.
• All the elements of the array are the same.
• The array is already sorted.
• The array is reverse sorted.
In addition to special inputs to the program, we should also consider special
conditions for the structures used by the program. For example, if we use an array to
store data, we should make sure that boundary cases, such as inserting or removing
at the beginning or end of the subarray holding data, are properly handled.
While it is essential to use handcrafted test suites, it is also advantageous to run
the program on a large collection of randomly generated inputs. The Random class
in the [Link] package provides several means for generating pseudorandom
numbers.
There is a hierarchy among the classes and methods of a program induced by
the caller-callee relationship. Namely, a method A is above a method B in the
hierarchy if A calls B. There are two main testing strategies, top-down testing and
bottom-up testing, which differ in the order in which methods are tested.
Top-down testing proceeds from the top to the bottom of the program hierar-
chy. It is typically used in conjunction with stubbing, a boot-strapping technique
that replaces a lower-level method with a stub, a replacement for the method that
simulates the functionality of the original. For example, if method A calls method
B to get the first line of a file, when testing A we can replace B with a stub that
returns a fixed string.
54 Chapter 1. Java Primer
Bottom-up testing proceeds from lower-level methods to higher-level methods.
For example, bottom-level methods, which do not invoke other methods, are tested
first, followed by methods that call only bottom-level methods, and so on. Similarly
a class that does not depend upon any other classes can be tested before another
class that depends on the former. This form of testing is usually described as unit
testing, as the functionality of a specific component is tested in isolation of the
larger software project. If used properly, this strategy better isolates the cause of
errors to the component being tested, as lower-level components upon which it
relies should have already been thoroughly tested.
Java provides several forms of support for automated testing. We have already
discussed how a class’s static main method can be repurposed to perform tests of
the functionality of that class (as was done in Code 1.6 for the CreditCard class).
Such a test can be executed by invoking the Java virtual machine directly on this
secondary class, rather than on the primary class for the entire application. When
Java is started on the primary class, any code within such secondary main methods
will be ignored.
More robust support for automation of unit testing is provided by the JUnit
framework, which is not part of the standard Java toolkit but freely available at
[Link]. This framework allows the grouping of individual test cases into
larger test suites, and provides support for executing those suites, and reporting or
analyzing the results of those tests. As software is maintained, regression testing
should be performed, whereby automation is used to re-execute all previous tests to
ensure that changes to the software do not introduce new bugs in previously tested
components.
Debugging
The simplest debugging technique consists of using print statements to track the
values of variables during the execution of the program. A problem with this ap-
proach is that eventually the print statements need to be removed or commented
out, so they are not executed when the software is finally released.
A better approach is to run the program within a debugger, which is a special-
ized environment for controlling and monitoring the execution of a program. The
basic functionality provided by a debugger is the insertion of breakpoints within
the code. When the program is executed within the debugger, it stops at each
breakpoint. While the program is stopped, the current value of variables can be
inspected. In addition to fixed breakpoints, advanced debuggers allow specifica-
tion of conditional breakpoints, which are triggered only if a given expression is
satisfied.
The standard Java toolkit includes a basic debugger named jdb, which has
a command-line interface. Most IDEs for Java programming provide advanced
debugging environments with graphical user interfaces.