Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
0% found this document useful (0 votes)
309 views

Data Structures and Algorithms in Java 6th Edition 101 150

This document discusses try-catch statements in Java for exception handling. It explains the syntax of try-catch statements with multiple catch blocks. When an exception occurs in the guardedBody, execution jumps to the matching catch block. If no exceptions occur, execution continues past the try-catch statement. The document also provides an example try-catch statement that catches ArrayIndexOutOfBoundsException and NumberFormatException when parsing a command line argument.

Uploaded by

longle18704
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
309 views

Data Structures and Algorithms in Java 6th Edition 101 150

This document discusses try-catch statements in Java for exception handling. It explains the syntax of try-catch statements with multiple catch blocks. When an exception occurs in the guardedBody, execution jumps to the matching catch block. If no exceptions occur, execution continues past the try-catch statement. The document also provides an example try-catch statement that catches ArrayIndexOutOfBoundsException and NumberFormatException when parsing a command line argument.

Uploaded by

longle18704
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 50

2.4.

Exceptions 83
A typical syntax for a try-catch statement in Java is as follows:
try {
guardedBody
} catch (exceptionType 1 variable1 ) {
remedyBody1
} catch (exceptionType 2 variable2 ) {
remedyBody2
} ...
...

Each exceptionType i is the type of some exception, and each variablei is a valid
Java variable name.
The Java runtime environment begins performing a try-catch statement such as
this by executing the block of statements, guardedBody. If no exceptions are gen-
erated during this execution, the flow of control continues with the first statement
beyond the last line of the entire try-catch statement.
If, on the other hand, the block, guardedBody, generates an exception at some
point, the execution of that block immediate terminates and execution jumps to the
catch block whose exceptionType most closely matches the exception thrown (if
any). The variable for this catch statement references the exception object itself,
which can be used in the block of the matching catch statement. Once execution of
that catch block completes, control flow continues with the first statement beyond
the entire try-catch construct.
If an exception occurs during the execution of the block, guardedBody, that
does not match any of the exception types declared in the catch statements, that
exception is rethrown in the surrounding context.
There are several possible reactions when an exception is caught. One possi-
bility is to print out an error message and terminate the program. There are also
some interesting cases in which the best way to handle an exception is to quietly
catch and ignore it (this can be done by having an empty body as a catch block).
Another legitimate way of handling exceptions is to create and throw another ex-
ception, possibly one that specifies the exceptional condition more precisely.
We note briefly that try-catch statements in Java support a few advanced tech-
niques that we will not use in this book. There can be an optional finally clause
with a body that will be executed whether or not an exception happens in the origi-
nal guarded body; this can be useful, for example, to close a file before proceeding
onward. Java SE 7 introduced a new syntax known as a “try with resource” that
provides even more advanced cleanup techniques for resources such as open files
that must be properly cleaned up. Also as of Java SE 7, each catch statement can
designate multiple exception types that it handles; previously, a separate clause
would be needed for each one, even if the same remedy were applied in each case.

www.it-ebooks.info
84 Chapter 2. Object-Oriented Design
1 public static void main(String[ ] args) {
2 int n = DEFAULT;
3 try {
4 n = Integer.parseInt(args[0]);
5 if (n <= 0) {
6 System.out.println("n must be positive. Using default.");
7 n = DEFAULT;
8 }
9 } catch (ArrayIndexOutOfBoundsException e) {
10 System.out.println("No argument specified for n. Using default.");
11 } catch (NumberFormatException e) {
12 System.out.println("Invalid integer argument. Using default.");
13 }
14 }

Code Fragment 2.13: A demonstration of catching an exception.

As a tangible example of a try-catch statement, we consider the simple applica-


tion presented in Code Fragment 2.13. This main method attempts to interpret the
first command-line argument as a positive integer. (Command-line arguments were
introduced on page 16.)
The statement at risk of throwing an exception, at line 4, is the command
n = Integer.parseInt(args[0]). That command may fail for one of two reasons.
First, the attempt to access args[0] will fail if the user did not specify any argu-
ments, and thus, the array args is empty. An ArrayIndexOutOfBoundsException
will be thrown in that case (and caught by us at line 9). The second potential ex-
ception is when calling the Integer.parseInt method. That command succeeds so
long as the parameter is a string that is a legitimate integer representation, such as
"2013". Of course, since a command-line argument can be any string, the user
might provide an invalid integer representation, in which case the parseInt method
throws a NumberFormatException (caught by us at line 11).
A final condition we wish to enforce is that the integer specified by the user
is positive. To test this property, we rely on a traditional conditional statement
(lines 5–8). However, notice that we have placed that conditional statement within
the primary body of the try-catch statement. That conditional statement will only be
evaluated if the command at line 4 succeeded without exception; had an exception
occurred at line 4, the primary try block is terminated, and control proceeds directly
to the exception handling for the appropriate catch statement.
As an aside, if we had been willing to use the same error message for the two
exceptional cases, we can use a single catch clause with the following syntax:
} catch (ArrayIndexOutOfBoundsException | NumberFormatException e) {
System.out.println("Using default value for n.");
}

www.it-ebooks.info
2.4. Exceptions 85

2.4.2 Throwing Exceptions


Exceptions originate when a piece of Java code finds some sort of problem during
execution and throws an exception object. This is done by using the throw keyword
followed by an instance of the exception type to be thrown. It is often convenient
to instantiate an exception object at the time the exception has to be thrown. Thus,
a throw statement is typically written as follows:

throw new exceptionType(parameters);


where exceptionType is the type of the exception and the parameters are sent to
that type’s constructor; most exception types offer a version of a constructor that
accepts an error message string as a parameter.
As an example, the following method takes an integer parameter, which it ex-
pects to be positive. If a negative integer is sent, an IllegalArgumentException is
thrown.
public void ensurePositive(int n) {
if (n < 0)
throw new IllegalArgumentException("That's not positive!");
// ...
}
The execution of a throw statement immediately terminates the body of a method.

The Throws Clause


When a method is declared, it is possible to explicitly declare, as part of its sig-
nature, the possibility that a particular exception type may be thrown during a call
to that method. It does not matter whether the exception is directly from a throw
statement in that method body, or propagated upward from a secondary method call
made from within the body.
The syntax for declaring possible exceptions in a method signature relies on the
keyword throws (not to be confused with an actual throw statement). For example,
the parseInt method of the Integer class has the following formal signature:
public static int parseInt(String s) throws NumberFormatException;
The designation “throws NumberFormatException” warns users about the possi-
bility of an exceptional case, so that they might be better prepared to handle an
exception that may arise. If one of many exception types may possibly be thrown,
all such types can be listed, separated with commas. Alternatively, it may be pos-
sible to list an appropriate superclass that encompasses all specific exceptions that
may be thrown.

www.it-ebooks.info
86 Chapter 2. Object-Oriented Design
The use of a throws clause in a method signature does not take away the re-
sponsibility of properly documenting all possible exceptions through the use of the
@throws tag within a javadoc comment (see Section 1.9.4). The type and reasons
for any potential exceptions should always be properly declared in the documenta-
tion for a method.
In contrast, the use of the throws clause in a method signature is optional
for many types of exceptions. For example, the documentation for the nextInt( )
method of the Scanner class makes clear that three different exception types may
arise:
• An IllegalStateException, if the scanner has been closed
• A NoSuchElementException, if the scanner is active, but there is currently
no token available for input
• An InputMismatchException, if the next available token does not represent
an integer
However, no potential exceptions are formally declared within the method signa-
ture; they are only noted in the documentation.
To better understand the functional purpose of the throws declaration in a
method signature, it is helpful to know more about the way Java organizes its hier-
archy of exception types.

2.4.3 Java’s Exception Hierarchy


Java defines a rich inheritance hierarchy of all objects that are deemed Throwable.
We show a small portion of this hierarchy in Figure 2.7. The hierarchy is intention-
ally divided into two subclasses: Error and Exception. Errors are typically thrown
only by the Java Virtual Machine and designate the most serious situations that are
unlikely to be recoverable, such as when the virtual machine is asked to execute
a corrupt class file, or when the system runs out of memory. In contrast, excep-
tions designate situations in which a running program might reasonably be able to
recover, for example, when unable to open a data file.

Checked and Unchecked Exceptions


Java provides further refinement by declaring the RuntimeException class as an
important subclass of Exception. All subtypes of RuntimeException in Java are
officially treated as unchecked exceptions, and any exception type that is not part
of the RuntimeException is a checked exception.
The intent of the design is that runtime exceptions occur entirely due to mis-
takes in programming logic, such as using a bad index with an array, or sending an
inappropriate value as a parameter to a method. While such programming errors

www.it-ebooks.info
2.4. Exceptions 87
Throwable

Error Exception

VirtualMachineError IOError ... RuntimeException IOException ...

OutofMemoryError
IllegalArgumentException FileNotFoundException EOFException

IndexOutOfBoundsException

NumberFormatException NullPointerException

ArrayIndexOutOfBoundsException NoSuchElementException ClassCastException

Figure 2.7: A small portion of Java’s hierarchy of Throwable types.

will certainly occur as part of the software development process, they should pre-
sumably be resolved before software reaches production quality. Therefore, it is
not in the interest of efficiency to explicitly check for each such mistake at runtime,
and thus these are designated as “unchecked” exceptions.
In contrast, other exceptions occur because of conditions that cannot easily be
detected until a program is executing, such as an unavailable file or a failed network
connection. Those are typically designated as “checked” exceptions in Java (and
thus, not a subtype of RuntimeException).
The designation between checked and unchecked exceptions plays a significant
role in the syntax of the language. In particular, all checked exceptions that might
propagate upward from a method must be explicitly declared in its signature.
A consequence is that if one method calls a second method declaring checked
exceptions, then the call to that second method must either be guarded within a
try-catch statement, or else the calling method must itself declare the checked ex-
ceptions in its signature, since there is risk that such an exception might propagate
upward from the calling method.
Defining New Exception Types
In this book, we will rely entirely on existing RuntimeException types to designate
various requirements on the use of our data structures. However, some libraries
define new classes of exceptions to describe more specific conditions. Specialized
exceptions should inherit either from the Exception class (if checked), from the
RuntimeException class (if unchecked), or from an existing Exception subtype
that is more relevant.

www.it-ebooks.info
88 Chapter 2. Object-Oriented Design

2.5 Casting and Generics


In this section, we discuss casting among reference variables, as well as a technique,
called generics, that allows us to define methods and classes that work with a variety
of data types without the need for explicit casting.

2.5.1 Casting
We begin our discussion with methods for type conversions for objects.

Widening Conversions
A widening conversion occurs when a type T is converted into a “wider” type U .
The following are common cases of widening conversions:
• T and U are class types and U is a superclass of T .
• T and U are interface types and U is a superinterface of T .
• T is a class that implements interface U .
Widening conversions are automatically performed to store the result of an ex-
pression into a variable, without the need for an explicit cast. Thus, we can directly
assign the result of an expression of type T into a variable v of type U when the
conversion from T to U is a widening conversion. When discussing polymorphism
on page 68, we gave the following example of an implicit widening cast, assigning
an instance of the narrower PredatoryCreditCard class to a variable of the wider
CreditCard type:
CreditCard card = new PredatoryCreditCard(...); // parameters omitted
The correctness of a widening conversion can be checked by the compiler and its
validity does not require testing by the Java runtime environment during program
execution.

Narrowing Conversions
A narrowing conversion occurs when a type T is converted into a “narrower”
type S. The following are common cases of narrowing conversions:
• T and S are class types and S is a subclass of T .
• T and S are interface types and S is a subinterface of T .
• T is an interface implemented by class S.
In general, a narrowing conversion of reference types requires an explicit cast.
Also, the correctness of a narrowing conversion may not be verifiable by the com-
piler. Thus, its validity should be tested by the Java runtime environment during
program execution.

www.it-ebooks.info
2.5. Casting and Generics 89
The example code fragment below shows how to use a cast to perform a nar-
rowing conversion from type PredatoryCreditCard to type CreditCard.
CreditCard card = new PredatoryCreditCard(...); // widening
PredatoryCreditCard pc = (PredatoryCreditCard) card; // narrowing
Although variable card happens to reference an instance of a PredatoryCreditCard,
the variable has declared type, CreditCard. Therefore, the assignment pc = card
is a narrowing conversion and requires an explicit cast that will be evaluated at
runtime (as not all cards are predatory).

Casting Exceptions
In Java, we can cast an object reference o of type T into a type S, provided the
object o is referring to is actually of type S. If, on the other hand, object o is not
also of type S, then attempting to cast o to type S will throw an exception called
ClassCastException. We illustrate this rule in the following code fragment, using
Java’s Number abstract class, which is a superclass of both Integer and Double.
Number n;
Integer i;
n = new Integer(3);
i = (Integer) n; // This is legal
n = new Double(3.1415);
i = (Integer) n; // This is illegal
To avoid problems such as this and to avoid peppering our code with try-catch
blocks every time we perform a cast, Java provides a way to make sure an object
cast will be correct. Namely, it provides an operator, instanceof, that allows us to
test whether an object variable is referring to an object that belongs to a particular
type. The syntax for this operator is objectReference instanceof referenceType,
where objectReference is an expression that evaluates to an object reference and
referenceType is the name of some existing class, interface, or enum (Section 1.3).
If objectReference is indeed an instance satisfying referenceType, then the operator
returns true; otherwise, it returns false. Thus, we can avoid a ClassCastException
from being thrown in the code fragment above by modifying it as follows:
Number n;
Integer i;
n = new Integer(3);
if (n instanceof Integer)
i = (Integer) n; // This is legal
n = new Double(3.1415);
if (n instanceof Integer)
i = (Integer) n; // This will not be attempted

www.it-ebooks.info
90 Chapter 2. Object-Oriented Design
Casting with Interfaces

Interfaces allow us to enforce that objects implement certain methods, but using
interface variables with concrete objects sometimes requires casting. Suppose we
declare a Person interface as shown in Code Fragment 2.14. Note that method
equals of the Person interface takes one parameter of type Person. Thus, we can
pass an object of any class implementing the Person interface to this method.
1 public interface Person {
2 public boolean equals(Person other); // is this the same person?
3 public String getName( ); // get this person’s name
4 public int getAge( ); // get this person’s age
5 }
Code Fragment 2.14: Interface Person.

In Code Fragment 2.15, we show a class, Student, that implements Person.


Because the parameter to equals is a Person, the implementation must not assume
that it is necessarily of type Student. Instead, it first uses the instanceof operator
at line 15, returning false if the argument is not a student (since it surely is not
the student in question). Only after verifying that the parameter is a student, is it
explicitly cast to a Student, at which point its id field can be accessed.

1 public class Student implements Person {


2 String id;
3 String name;
4 int age;
5 public Student(String i, String n, int a) { // simple constructor
6 id = i;
7 name = n;
8 age = a;
9 }
10 protected int studyHours( ) { return age/2;} // just a guess
11 public String getID( ) { return id;} // ID of the student
12 public String getName( ) { return name; } // from Person interface
13 public int getAge( ) { return age; } // from Person interface
14 public boolean equals(Person other) { // from Person interface
15 if (!(other instanceof Student)) return false; // cannot possibly be equal
16 Student s = (Student) other; // explicit cast now safe
17 return id.equals(s.id); // compare IDs
18 }
19 public String toString( ) { // for printing
20 return "Student(ID:" + id + ", Name:" + name + ", Age:" + age + ")";
21 }
22 }
Code Fragment 2.15: Class Student implementing interface Person.

www.it-ebooks.info
2.5. Casting and Generics 91

2.5.2 Generics
Java includes support for writing generic classes and methods that can operate on a
variety of data types while often avoiding the need for explicit casts. The generics
framework allows us to define a class in terms of a set of formal type parameters,
which can then be used as the declared type for variables, parameters, and return
values within the class definition. Those formal type parameters are later specified
when using the generic class as a type elsewhere in a program.
To better motivate the use of generics, we consider a simple case study. Often,
we wish to treat a pair of related values as a single object, for example, so that
the pair can be returned from a method. A solution is to define a new class whose
instances store both values. This is our first example of an object-oriented design
pattern known as the composition design pattern. If we know, for example, that we
want a pair to store a string and a floating-point number, perhaps to store a stock
ticker label and a price, we could easily design a custom class for that purpose.
However, for another purpose, we might want to store a pair that consists of a Book
object and an integer that represents a quantity. The goal of generic programming
is to be able to write a single class that can represent all such pairs.
The generics framework was not a part of the original Java language; it was
added as part of Java SE 5. Prior to that, generic programming was implemented
by relying heavily on Java’s Object class, which is the universal supertype of all
objects (including the wrapper types corresponding to primitives). In that “classic”
style, a generic pair might be implemented as shown in Code Fragment 2.16.
1 public class ObjectPair {
2 Object first;
3 Object second;
4 public ObjectPair(Object a, Object b) { // constructor
5 first = a;
6 second = b;
7 }
8 public Object getFirst( ) { return first; }
9 public Object getSecond( ) { return second;}
10 }
Code Fragment 2.16: Representing a generic pair of objects using a classic style.

An ObjectPair instance stores the two objects that are sent to the constructor,
and provides individual accessors for each component of the pair. With this defini-
tion, a pair can be declared and instantiated with the following command:
ObjectPair bid = new ObjectPair("ORCL", 32.07);
This instantiation is legal because the parameters to the constructor undergo widen-
ing conversions. The first parameter, "ORCL", is a String, and thus also an Object.

www.it-ebooks.info
92 Chapter 2. Object-Oriented Design
The second parameter is a double, but it is automatically boxed into a Double,
which then qualifies as an Object. (For the record, this is not quite the “classic”
style, as automatic boxing was not introduced until Java SE 5.)
The drawback of the classic approach involves use of the accessors, both of
which formally return an Object reference. Even if we know that the first object is
a string in our application, we cannot legally make the following assignment:
String stock = bid.getFirst( ); // illegal; compile error
This represents a narrowing conversion from the declared return type of Object to
the variable of type String. Instead, an explicit cast is required, as follows:
String stock = (String) bid.getFirst( ); // narrowing cast: Object to String
With the classic style for generics, code became rampant with such explicit casts.

Using Java’s Generics Framework


With Java’s generics framework, we can implement a pair class using formal type
parameters to represent the two relevant types in our composition. An implemen-
tation using this framework is given in Code Fragment 2.17.
1 public class Pair<A,B> {
2 A first;
3 B second;
4 public Pair(A a, B b) { // constructor
5 first = a;
6 second = b;
7 }
8 public A getFirst( ) { return first; }
9 public B getSecond( ) { return second;}
10 }
Code Fragment 2.17: Representing a pair of objects with generic type parameters.

Angle brackets are used at line 1 to enclose the sequence of formal type parameters.
Although any valid identifier can be used for a formal type parameter, single-letter
uppercase names are conventionally used (in this example, A and B). We may then
use these type parameters within the body of the class definition. For example, we
declare instance variable, first, to have type A; we similarly use A as the declared
type for the first constructor parameter and for the return type of method, getFirst.
When subsequently declaring a variable with such a parameterize type, we must
explicitly specify actual type parameters that will take the place of the generic
formal type parameters. For example, to declare a variable that is a pair holding a
stock-ticker string and a price, we write the following:
Pair<String,Double> bid;

www.it-ebooks.info
2.5. Casting and Generics 93
Effectively, we have stated that we wish to have String serve in place of type A,
and Double serve in place of type B for the pair known as bid. The actual types for
generic programming must be object types, which is why we use the wrapper class
Double instead of the primitive type double. (Fortunately, the automatic boxing
and unboxing will work in our favor.)

We can subsequently instantiate the generic class using the following syntax:
bid = new Pair<>("ORCL", 32.07); // rely on type inference

After the new operator, we provide the name of the generic class, then an empty
set of angle brackets (known as the “diamond”), and finally the parameters to the
constructor. An instance of the generic class is created, with the actual types for
the formal type parameters determined based upon the original declaration of the
variable to which it is assigned (bid in this example). This process is known as type
inference, and was introduced to the generics framework in Java SE 7.

It is also possible to use a style that existed prior to Java SE 7, in which the
generic type parameters are explicitly specified between angle brackets during in-
stantiation. Using that style, our previous example would be implemented as:
bid = new Pair<String,Double>("ORCL", 32.07); // give explicit types

However, it is important that one of the two above styles be used. If angle
brackets are entirely omitted, as in the following example,
bid = new Pair("ORCL", 32.07); // classic style

this reverts to the classic style, with Object automatically used for all generic type
parameters, and resulting in a compiler warning when assigning to a variable with
more specific types.

Although the syntax for the declaration and instantiation of objects using the
generics framework is slightly more cluttered than the classic style, the advantage
is that there is no longer any need for explicit narrowing casts from Object to a
more specific type. Continuing with our example, since bid was declared with
actual type parameters <String,Double>, the return type of the getFirst( ) method
is String, and the return type of the getSecond( ) method is Double. Unlike the
classic style, we can make the following assignments without any explicit casting
(although there is still an automatic unboxing of the Double):
String stock = bid.getFirst( );
double price = bid.getSecond( );

www.it-ebooks.info
94 Chapter 2. Object-Oriented Design
Generics and Arrays

There is an important caveat related to generic types and the use of arrays. Although
Java allows the declaration of an array storing a parameterized type, it does not
technically allow the instantiation of new arrays involving those types. Fortunately,
it allows an array defined with a parameterized type to be initialized with a newly
created, nonparametric array, which can then be cast to the parameterized type.
Even so, this latter mechanism causes the Java compiler to issue a warning, because
it is not 100% type-safe.
We will see this issue arise in two ways:

• Code outside a generic class may wish to declare an array storing instances
of the generic class with actual type parameters.
• A generic class may wish to declare an array storing objects that belong to
one of the formal parameter types.
As an example of the first use case, we continue with our stock market example
and presume that we would like to keep an array of Pair<String,Double> objects.
Such an array can be declared with a parameterized type, but it must be instantiated
with an unparameterized type and then cast back to the parameterized type. We
demonstrate this usage in the following:
Pair<String,Double>[ ] holdings;
holdings = new Pair<String,Double>[25]; // illegal; compile error
holdings = new Pair[25]; // correct, but warning about unchecked cast
holdings[0] = new Pair<>("ORCL", 32.07); // valid element assignment
As an example of the second use case, assume that we want to create a generic
Portfolio class that can store a fixed number of generic entries in an array. If the
class uses <T> as a parameterized type, it can declare an array of type T[ ], but
it cannot directly instantiate such an array. Instead, a common approach is to in-
stantiate an array of type Object[ ], and then make a narrowing cast to type T[ ], as
shown in the following:
public class Portfolio<T> {
T[ ] data;
public Portfolio(int capacity) {
data = new T[capacity]; // illegal; compiler error
data = (T[ ]) new Object[capacity]; // legal, but compiler warning
}
public T get(int index) { return data[index]; }
public void set(int index, T element) { data[index] = element; }
}

www.it-ebooks.info
2.5. Casting and Generics 95
Generic Methods
The generics framework allows us to define generic versions of individual methods
(as opposed to generic versions of entire classes). To do so, we include a generic
formal type declaration among the method modifiers.
For example, we show below a nonparametric GenericDemo class with a pa-
rameterized static method that can reverse an array containing elements of any
object type.
public class GenericDemo {
public static <T> void reverse(T[ ] data) {
int low = 0, high = data.length − 1;
while (low < high) { // swap data[low] and data[high]
T temp = data[low];
data[low++] = data[high]; // post-increment of low
data[high−−] = temp; // post-decrement of high
}
}
}
Note the use of the <T> modifier to declare the method to be generic, and the use
of the type T within the method body, when declaring the local variable, temp.
The method can be called using the syntax, GenericDemo.reverse(books), with
type inference determining the generic type, assuming books is an array of some
object type. (This generic method cannot be applied to primitive arrays, because
autoboxing does not apply to entire arrays.)
As an aside, we note that we could have implemented a reverse method equally
well using a classic style, acting upon an Object[ ] array.

Bounded Generic Types


By default, when using a type name such as T in a generic class or method, a
user can specify any object type as the actual type of the generic. A formal pa-
rameter type can be restricted by using the extends keyword followed by a class
or interface. In that case, only a type that satisfies the stated condition is allowed
to substitute for the parameter. The advantage of such a bounded type is that it
becomes possible to call any methods that are guaranteed by the stated bound.
As an example, we might declare a generic ShoppingCart that could only be
instantiated with a type that satisfied the Sellable interface (from Code Fragment 2.8
on page 77). Such a class would be declared beginning with the line:
public class ShoppingCart<T extends Sellable> {
Within that class definition, we would then be allowed to call methods such as
description( ) and lowestPrice( ) on any instances of type T.

www.it-ebooks.info
96 Chapter 2. Object-Oriented Design

2.6 Nested Classes


Java allows a class definition to be nested inside the definition of another class.
The main use for nesting classes is when defining a class that is strongly affili-
ated with another class. This can help increase encapsulation and reduce undesired
name conflicts. Nested classes are a valuable technique when implementing data
structures, as an instance of a nested use can be used to represent a small portion
of a larger data structure, or an auxiliary class that helps navigate a primary data
structure. We will use nested classes in many implementations within this book.
To demonstrate the mechanics of a nested class, we consider a new Transaction
class to support logging of transactions associated with a credit card. That new class
definition can be nested within the CreditCard class using a style as follows:
public class CreditCard {
private static class Transaction { /∗ details omitted ∗/ }

// instance variable for a CreditCard


Transaction[ ] history; // keep log of all transactions for this card
}
The containing class is known as the outer class. The nested class is formally a
member of the outer class, and its fully qualified name is OuterName.NestedName.
For example, with the above definition the nested class is CreditCard.Transaction,
although we may refer to it simply as Transaction from within the CreditCard class.
Much like packages (see Section 1.8), the use of nested classes can help re-
duce name collisions, as it is perfectly acceptable to have another class named
Transaction nested within some other class (or as a self-standing class).
A nested class has an independent set of modifiers from the outer class. Visi-
bility modifiers (e.g., public, private) effect whether the nested class definition is
accessible beyond the outer class definition. For example, a private nested class
can be used by the outer class, but by no other classes.
A nested class can also be designated as either static or (by default) nonstatic,
with significant consequences. A static nested class is most like a traditional class;
its instances have no association with any specific instance of the outer class.
A nonstatic nested class is more commonly known as an inner class in Java.
An instance of an inner class can only be created from within a nonstatic method of
the outer class, and that inner instance becomes associated with the outer instance
that creates it. Each instance of an inner class implicitly stores a reference to its
associated outer instance, accessible from within the inner class methods using the
syntax OuterName.this (as opposed to this, which refers to the inner instance).
The inner instance also has private access to all members of its associated outer
instance, and can rely on the formal type parameters of the outer class, if generic.

www.it-ebooks.info
2.7. Exercises 97

2.7 Exercises
Reinforcement
R-2.1 Give three examples of life-critical software applications.
R-2.2 Give an example of a software application in which adaptability can mean the
difference between a prolonged lifetime of sales and bankruptcy.
R-2.3 Describe a component from a text-editor GUI and the methods that it encapsu-
lates.
R-2.4 Assume that we change the CreditCard class (see Code Fragment 1.5) so that
instance variable balance has private visibility. Why is the following implemen-
tation of the PredatoryCreditCard.charge method flawed?
public boolean charge(double price) {
boolean isSuccess = super.charge(price);
if (!isSuccess)
charge(5); // the penalty
return isSuccess;
}

R-2.5 Assume that we change the CreditCard class (see Code Fragment 1.5) so that
instance variable balance has private visibility. Why is the following implemen-
tation of the PredatoryCreditCard.charge method flawed?
public boolean charge(double price) {
boolean isSuccess = super.charge(price);
if (!isSuccess)
super.charge(5); // the penalty
return isSuccess;
}

R-2.6 Give a short fragment of Java code that uses the progression classes from Sec-
tion 2.2.3 to find the eighth value of a Fibonacci progression that starts with 2
and 2 as its first two values.
R-2.7 If we choose an increment of 128, how many calls to the nextValue method from
the ArithmeticProgression class of Section 2.2.3 can we make before we cause a
long-integer overflow?
R-2.8 Can two interfaces mutually extend each other? Why or why not?
R-2.9 What are some potential efficiency disadvantages of having very deep inheritance
trees, that is, a large set of classes, A, B, C, and so on, such that B extends A, C
extends B, D extends C, etc.?
R-2.10 What are some potential efficiency disadvantages of having very shallow inheri-
tance trees, that is, a large set of classes, A, B, C, and so on, such that all of these
classes extend a single class, Z?

www.it-ebooks.info
98 Chapter 2. Object-Oriented Design
R-2.11 Consider the following code fragment, taken from some package:
public class Maryland extends State {
Maryland( ) { /∗ null constructor ∗/ }
public void printMe( ) { System.out.println("Read it."); }
public static void main(String[ ] args) {
Region east = new State( );
State md = new Maryland( );
Object obj = new Place( );
Place usa = new Region( );
md.printMe( );
east.printMe( );
((Place) obj).printMe( );
obj = md;
((Maryland) obj).printMe( );
obj = usa;
((Place) obj).printMe( );
usa = md;
((Place) usa).printMe( );
}
}
class State extends Region {
State( ) { /∗ null constructor ∗/ }
public void printMe( ) { System.out.println("Ship it."); }
}
class Region extends Place {
Region( ) { /∗ null constructor ∗/ }
public void printMe( ) { System.out.println("Box it."); }
}
class Place extends Object {
Place( ) { /∗ null constructor ∗/ }
public void printMe( ) { System.out.println("Buy it."); }
}

What is the output from calling the main( ) method of the Maryland class?

R-2.12 Draw a class inheritance diagram for the following set of classes:
• Class Goat extends Object and adds an instance variable tail and methods
milk( ) and jump( ).
• Class Pig extends Object and adds an instance variable nose and methods
eat(food) and wallow( ).
• Class Horse extends Object and adds instance variables height and color,
and methods run( ) and jump( ).
• Class Racer extends Horse and adds a method race( ).
• Class Equestrian extends Horse and adds instance variable weight and is-
Trained, and methods trot( ) and isTrained( ).

www.it-ebooks.info
2.7. Exercises 99
R-2.13 Consider the inheritance of classes from Exercise R-2.12, and let d be an object
variable of type Horse. If d refers to an actual object of type Equestrian, can it
be cast to the class Racer? Why or why not?
R-2.14 Give an example of a Java code fragment that performs an array reference that
is possibly out of bounds, and if it is out of bounds, the program catches that
exception and prints the following error message:
“Don’t try buffer overflow attacks in Java!”
R-2.15 If the parameter to the makePayment method of the CreditCard class (see Code
Fragment 1.5) were a negative number, that would have the effect of raising
the balance on the account. Revise the implementation so that it throws an
IllegalArgumentException if a negative amount is sent as a parameter.

Creativity
C-2.16 Suppose you are on the design team for a new e-book reader. What are the
primary classes and methods that the Java software for your reader will need?
You should include an inheritance diagram for this code, but you don’t need to
write any actual code. Your software architecture should at least include ways for
customers to buy new books, view their list of purchased books, and read their
purchased books.
C-2.17 Most modern Java compilers have optimizers that can detect simple cases when
it is logically impossible for certain statements in a program to ever be executed.
In such cases, the compiler warns the programmer about the useless code. Write
a short Java method that contains code for which it is provably impossible for
that code to ever be executed, yet the Java compiler does not detect this fact.
C-2.18 The PredatoryCreditCard class provides a processMonth( ) method that models
the completion of a monthly cycle. Modify the class so that once a customer has
made ten calls to charge during a month, each additional call to that method in
the current month results in an additional $1 surcharge.
C-2.19 Modify the PredatoryCreditCard class so that a customer is assigned a minimum
monthly payment, as a percentage of the balance, and so that a late fee is assessed
if the customer does not subsequently pay that minimum amount before the next
monthly cycle.
C-2.20 Assume that we change the CreditCard class (see Code Fragment 1.5) so that
instance variable balance has private visibility, but a new protected method is
added, with signature setBalance(newBalance). Show how to properly imple-
ment the method PredatoryCreditCard.processMonth( ) in this setting.
C-2.21 Write a program that consists of three classes, A, B, and C, such that B extends
A and that C extends B. Each class should define an instance variable named “x”
(that is, each has its own variable named x). Describe a way for a method in C
to access and set A’s version of x to a given value, without changing B or C’s
version.

www.it-ebooks.info
100 Chapter 2. Object-Oriented Design
C-2.22 Explain why the Java dynamic dispatch algorithm, which looks for the method
to invoke for a call obj.foo( ), will never get into an infinite loop.
C-2.23 Modify the advance method of the FibonacciProgression class so as to avoid use
of any temporary variable.
C-2.24 Write a Java class that extends the Progression class so that each value in the pro-
gression is the absolute value of the difference between the previous two values.
You should include a default constructor that starts with 2 and 200 as the first two
values and a parametric constructor that starts with a specified pair of numbers
as the first two values.
C-2.25 Redesign the Progression class to be abstract and generic, producing a sequence
of values of generic type T, and supporting a single constructor that accepts an
initial value. Make all corresponding modifications to the rest of the classes in
our hierarchy so that they remain as nongeneric classes, while inheriting from the
new generic Progression class.
C-2.26 Use a solution to Exercise C-2.25 to create a new progression class for which
each value is the square root of the previous value, represented as a Double.
You should include a default constructor that has 65, 536 as the first value and a
parametric constructor that starts with a specified number as the first value.
C-2.27 Use a solution to Exercise C-2.25 to reimplement the FibonacciProgression sub-
class to rely on the BigInteger class, in order to avoid overflows all together.
C-2.28 Write a set of Java classes that can simulate an Internet application in which one
party, Alice, is periodically creating a set of packets that she wants to send to
Bob. An Internet process is continually checking if Alice has any packets to
send, and if so, it delivers them to Bob’s computer; Bob is periodically checking
if his computer has a packet from Alice, and if so, he reads and deletes it.
C-2.29 Write a Java program that inputs a polynomial in standard algebraic notation and
outputs the first derivative of that polynomial.

Projects
P-2.30 Write a Java program that inputs a document and then outputs a bar-chart plot of
the frequencies of each alphabet character that appears within that document.
P-2.31 Write a Java program to simulate an ecosystem containing two types of creatures,
bears and fish. The ecosystem consists of a river, which is modeled as a relatively
large array. Each cell of the array should contain an Animal object, which can
be a Bear object, a Fish object, or null. In each time step, based on a random
process, each animal either attempts to move into an adjacent array cell or stay
where it is. If two animals of the same type are about to collide in the same
cell, then they stay where they are, but they create a new instance of that type
of animal, which is placed in a random empty (i.e., previously null) cell in the
array. If a bear and a fish collide, however, then the fish dies (i.e., it disappears).
Use actual object creation, via the new operator, to model the creation of new
objects, and provide a visualization of the array after each time step.

www.it-ebooks.info
Chapter Notes 101
P-2.32 Write a simulator as in the previous project, but add a boolean gender field and
a floating-point strength field to each Animal object. Now, if two animals of
the same type try to collide, then they only create a new instance of that type of
animal if they are of different genders. Otherwise, if two animals of the same
type and gender try to collide, then only the one of larger strength survives.
P-2.33 Write a Java program that simulates a system that supports the functions of an e-
book reader. You should include methods for users of your system to “buy” new
books, view their list of purchased books, and read their purchased books. Your
system should use actual books, which have expired copyrights and are available
on the Internet, to populate your set of available books for users of your system
to “purchase” and read.
P-2.34 Define a Polygon interface that has methods area( ) and perimeter( ). Then im-
plement classes for Triangle, Quadrilateral, Pentagon, Hexagon, and Octagon,
which implement this interface, with the obvious meanings for the area( ) and
perimeter( ) methods. Also implement classes, IsoscelesTriangle, Equilateral-
Triangle, Rectangle, and Square, which have the appropriate inheritance rela-
tionships. Finally, write a simple user interface, which allows users to create
polygons of the various types, input their geometric dimensions, and then out-
put their area and perimeter. For extra effort, allow users to input polygons by
specifying their vertex coordinates and be able to test if two such polygons are
similar.
P-2.35 Write a Java program that inputs a list of words, separated by whitespace, and
outputs how many times each word appears in the list. You need not worry about
efficiency at this point, however, as this topic is something that will be addressed
later in this book.
P-2.36 Write a Java program that can “make change.” Your program should take two
numbers as input, one that is a monetary amount charged and the other that is
a monetary amount given. It should then return the number of each kind of bill
and coin to give back as change for the difference between the amount given and
the amount charged. The values assigned to the bills and coins can be based on
the monetary system of any current or former government. Try to design your
program so that it returns the fewest number of bills and coins as possible.

Chapter Notes
For a broad overview of developments in computer science and engineering, we refer the
reader to The Computer Science and Engineering Handbook [89]. For more information
about the Therac-25 incident, please see the paper by Leveson and Turner [65].
The reader interested in studying object-oriented programming further is referred to the
books by Booch [16], Budd [19], and Liskov and Guttag [67]. Liskov and Guttag also pro-
vide a nice discussion of abstract data types, as does the book chapter by Demurjian [28] in
the The Computer Science and Engineering Handbook [89]. Design patterns are described
in the book by Gamma et al. [37].

www.it-ebooks.info
www.it-ebooks.info
Chapter

3 Fundamental Data Structures

Contents

3.1 Using Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . 104


3.1.1 Storing Game Entries in an Array . . . . . . . . . . . . . . 104
3.1.2 Sorting an Array . . . . . . . . . . . . . . . . . . . . . . . 110
3.1.3 java.util Methods for Arrays and Random Numbers . . . . 112
3.1.4 Simple Cryptography with Character Arrays . . . . . . . . 115
3.1.5 Two-Dimensional Arrays and Positional Games . . . . . . 118
3.2 Singly Linked Lists . . . . . . . . . . . . . . . . . . . . . . . 122
3.2.1 Implementing a Singly Linked List Class . . . . . . . . . . 126
3.3 Circularly Linked Lists . . . . . . . . . . . . . . . . . . . . . 128
3.3.1 Round-Robin Scheduling . . . . . . . . . . . . . . . . . . 128
3.3.2 Designing and Implementing a Circularly Linked List . . . 129
3.4 Doubly Linked Lists . . . . . . . . . . . . . . . . . . . . . . 132
3.4.1 Implementing a Doubly Linked List Class . . . . . . . . . 135
3.5 Equivalence Testing . . . . . . . . . . . . . . . . . . . . . . 138
3.5.1 Equivalence Testing with Arrays . . . . . . . . . . . . . . 139
3.5.2 Equivalence Testing with Linked Lists . . . . . . . . . . . 140
3.6 Cloning Data Structures . . . . . . . . . . . . . . . . . . . 141
3.6.1 Cloning Arrays . . . . . . . . . . . . . . . . . . . . . . . . 142
3.6.2 Cloning Linked Lists . . . . . . . . . . . . . . . . . . . . . 144
3.7 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145

www.it-ebooks.info
104 Chapter 3. Fundamental Data Structures

3.1 Using Arrays


In this section, we explore a few applications of arrays—the concrete data structures
introduced in Section 1.3 that access their entries using integer indices.

3.1.1 Storing Game Entries in an Array


The first application we study is storing a sequence of high score entries for a video
game in an array. This is representative of many applications in which a sequence
of objects must be stored. We could just as easily have chosen to store records for
patients in a hospital or the names of players on a football team. Nevertheless, let
us focus on storing high score entries, which is a simple application that is already
rich enough to present some important data-structuring concepts.
To begin, we consider what information to include in an object representing a
high score entry. Obviously, one component to include is an integer representing
the score itself, which we identify as score. Another useful thing to include is the
name of the person earning this score, which we identify as name. We could go
on from here, adding fields representing the date the score was earned or game
statistics that led to that score. However, we omit such details to keep our example
simple. A Java class, GameEntry, representing a game entry, is given in Code
Fragment 3.1.

1 public class GameEntry {


2 private String name; // name of the person earning this score
3 private int score; // the score value
4 /∗∗ Constructs a game entry with given parameters.. ∗/
5 public GameEntry(String n, int s) {
6 name = n;
7 score = s;
8 }
9 /∗∗ Returns the name field. ∗/
10 public String getName( ) { return name; }
11 /∗∗ Returns the score field. ∗/
12 public int getScore( ) { return score; }
13 /∗∗ Returns a string representation of this entry. ∗/
14 public String toString( ) {
15 return "(" + name + ", " + score + ")";
16 }
17 }
Code Fragment 3.1: Java code for a simple GameEntry class. Note that we include
methods for returning the name and score for a game entry object, as well as a
method for returning a string representation of this entry.

www.it-ebooks.info
3.1. Using Arrays 105
A Class for High Scores

To maintain a sequence of high scores, we develop a class named Scoreboard. A


scoreboard is limited to a certain number of high scores that can be saved; once that
limit is reached, a new score only qualifies for the scoreboard if it is strictly higher
than the lowest “high score” on the board. The length of the desired scoreboard
may depend on the game, perhaps 10, 50, or 500. Since that limit may vary, we
allow it to be specified as a parameter to our Scoreboard constructor.
Internally, we will use an array named board to manage the GameEntry in-
stances that represent the high scores. The array is allocated with the specified
maximum capacity, but all entries are initially null. As entries are added, we will
maintain them from highest to lowest score, starting at index 0 of the array. We
illustrate a typical state of the data structure in Figure 3.1, and give Java code to
construct such a data structure in Code Fragment 3.2.

Figure 3.1: An illustration of an array of length ten storing references to six


GameEntry objects in the cells with indices 0 to 5; the rest are null references.

1 /∗∗ Class for storing high scores in an array in nondecreasing order. ∗/


2 public class Scoreboard {
3 private int numEntries = 0; // number of actual entries
4 private GameEntry[ ] board; // array of game entries (names & scores)
5 /∗∗ Constructs an empty scoreboard with the given capacity for storing entries. ∗/
6 public Scoreboard(int capacity) {
7 board = new GameEntry[capacity];
8 }
... // more methods will go here
36 }
Code Fragment 3.2: The beginning of a Scoreboard class for maintaining a set of
scores as GameEntry objects. (Completed in Code Fragments 3.3 and 3.4.)

www.it-ebooks.info
106 Chapter 3. Fundamental Data Structures
Adding an Entry
One of the most common updates we might want to make to a Scoreboard is to add
a new entry. Keep in mind that not every entry will necessarily qualify as a high
score. If the board is not yet full, any new entry will be retained. Once the board is
full, a new entry is only retained if it is strictly better than one of the other scores,
in particular, the last entry of the scoreboard, which is the lowest of the high scores.
Code Fragment 3.3 provides an implementation of an update method for the
Scoreboard class that considers the addition of a new game entry.

9 /∗∗ Attempt to add a new score to the collection (if it is high enough) ∗/
10 public void add(GameEntry e) {
11 int newScore = e.getScore( );
12 // is the new entry e really a high score?
13 if (numEntries < board.length | | newScore > board[numEntries−1].getScore( )) {
14 if (numEntries < board.length) // no score drops from the board
15 numEntries++; // so overall number increases
16 // shift any lower scores rightward to make room for the new entry
17 int j = numEntries − 1;
18 while (j > 0 && board[j−1].getScore( ) < newScore) {
19 board[j] = board[j−1]; // shift entry from j-1 to j
20 j−−; // and decrement j
21 }
22 board[j] = e; // when done, add new entry
23 }
24 }
Code Fragment 3.3: Java code for inserting a GameEntry object into a Scoreboard.

When a new score is considered, the first goal is to determine whether it quali-
fies as a high score. This will be the case (see line 13) if the scoreboard is below its
capacity, or if the new score is strictly higher than the lowest score on the board.
Once it has been determined that a new entry should be kept, there are two
remaining tasks: (1) properly update the number of entries, and (2) place the new
entry in the appropriate location, shifting entries with inferior scores as needed.
The first of these tasks is easily handled at lines 14 and 15, as the total number
of entries can only be increased if the board is not yet at full capacity. (When full,
the addition of a new entry will be counteracted by the removal of the entry with
lowest score.)
The placement of the new entry is implemented by lines 17–22. Index j is
initially set to numEntries − 1, which is the index at which the last GameEntry will
reside after completing the operation. Either j is the correct index for the newest
entry, or one or more immediately before it will have lesser scores. The while loop
checks the compound condition, shifting entries rightward and decrementing j, as
long as there is another entry at index j − 1 with a score less than the new score.

www.it-ebooks.info
3.1. Using Arrays 107

Figure 3.2: Preparing to add Jill’s GameEntry object to the board array. In order to
make room for the new reference, we have to shift any references to game entries
with smaller scores than the new one to the right by one cell.

Figure 3.2 shows an example of the process, just after the shifting of existing
entries, but before adding the new entry. When the loop completes, j will be the
correct index for the new entry. Figure 3.3 shows the result of a complete operation,
after the assignment of board[j] = e, accomplished by line 22 of the code.
In Exercise C-3.19, we explore how game entry addition might be simplified
for the case when we don’t need to preserve relative orders.

Figure 3.3: Adding a reference to Jill’s GameEntry object to the board array. The
reference can now be inserted at index 2, since we have shifted all references to
GameEntry objects with scores less than the new one to the right.

www.it-ebooks.info
108 Chapter 3. Fundamental Data Structures
Removing an Entry

Suppose some hot shot plays our video game and gets his or her name on our high
score list, but we later learn that cheating occurred. In this case, we might want
to have a method that lets us remove a game entry from the list of high scores.
Therefore, let us consider how we might remove a reference to a GameEntry object
from a Scoreboard.
We choose to add a method to the Scoreboard class, with signature remove(i),
where i designates the current index of the entry that should be removed and re-
turned. When a score is removed, any lower scores will be shifted upward, to fill in
for the removed entry. If index i is outside the range of current entries, the method
will throw an IndexOutOfBoundsException.
Our implementation for remove will involve a loop for shifting entries, much
like our algorithm for addition, but in reverse. To remove the reference to the object
at index i, we start at index i and move all the references at indices higher than i
one cell to the left. (See Figure 3.4.)

Figure 3.4: An illustration of the removal of Paul’s score from index 3 of an array
storing references to GameEntry objects.

Our implementation of the remove method for the Scoreboard class is given
in Code Fragment 3.4. The details for doing the remove operation contain a few
subtle points. The first is that, in order to remove and return the game entry (let’s
call it e) at index i in our array, we must first save e in a temporary variable. We
will use this variable to return e when we are done removing it.

www.it-ebooks.info
3.1. Using Arrays 109

25 /∗∗ Remove and return the high score at index i. ∗/


26 public GameEntry remove(int i) throws IndexOutOfBoundsException {
27 if (i < 0 | | i >= numEntries)
28 throw new IndexOutOfBoundsException("Invalid index: " + i);
29 GameEntry temp = board[i]; // save the object to be removed
30 for (int j = i; j < numEntries − 1; j++) // count up from i (not down)
31 board[j] = board[j+1]; // move one cell to the left
32 board[numEntries −1 ] = null; // null out the old last score
33 numEntries−−;
34 return temp; // return the removed object
35 }
Code Fragment 3.4: Java code for performing the Scoreboard.remove operation.

The second subtle point is that, in moving references higher than i one cell to
the left, we don’t go all the way to the end of the array. First, we base our loop
on the number of current entries, not the capacity of the array, because there is
no reason for “shifting” a series of null references that may be at the end of the
array. We also carefully define the loop condition, j < numEntries − 1, so that the
last iteration of the loop assigns board[numEntries−2] = board[numEntries−1].
There is no entry to shift into cell board[numEntries−1], so we return that cell to
null just after the loop. We conclude by returning a reference to the removed entry
(which no longer has any reference pointing to it within the board array).

Conclusions
In the version of the Scoreboard class that is available online, we include an im-
plementation of the toString( ) method, which allows us to display the contents of
the current scoreboard, separated by commas. We also include a main method that
performs a basic test of the class.
The methods for adding and removing objects in an array of high scores are
simple. Nevertheless, they form the basis of techniques that are used repeatedly
to build more sophisticated data structures. These other structures may be more
general than the array structure above, of course, and often they will have a lot
more operations that they can perform than just add and remove. But studying the
concrete array data structure, as we are doing now, is a great starting point to un-
derstanding these other structures, since every data structure has to be implemented
using concrete means.
In fact, later in this book, we will study a Java collections class, ArrayList,
which is more general than the array structure we are studying here. The ArrayList
has methods to operate on an underlying array; yet it also eliminates the error that
occurs when adding an object to a full array by automatically copying the objects
into a larger array when necessary. We will discuss the ArrayList class in far more
detail in Section 7.2.

www.it-ebooks.info
110 Chapter 3. Fundamental Data Structures

3.1.2 Sorting an Array


In the previous subsection, we considered an application for which we added an
object to an array at a given position while shifting other elements so as to keep the
previous order intact. In this section, we use a similar technique to solve the sorting
problem, that is, starting with an unordered array of elements and rearranging them
into nondecreasing order.

The Insertion-Sort Algorithm


We study several sorting algorithms in this book, most of which are described in
Chapter 12. As a warm-up, in this section we describe a simple sorting algorithm
known as insertion-sort. The algorithm proceeds by considering one element at
a time, placing the element in the correct order relative to those before it. We
start with the first element in the array, which is trivially sorted by itself. When
considering the next element in the array, if it is smaller than the first, we swap
them. Next we consider the third element in the array, swapping it leftward until it
is in its proper order relative to the first two elements. We continue in this manner
with the fourth element, the fifth, and so on, until the whole array is sorted. We can
express the insertion-sort algorithm in pseudocode, as shown in Code Fragment 3.5.

Algorithm InsertionSort(A):
Input: An array A of n comparable elements
Output: The array A with elements rearranged in nondecreasing order
for k from 1 to n − 1 do
Insert A[k] at its proper location within A[0], A[1], . . ., A[k].
Code Fragment 3.5: High-level description of the insertion-sort algorithm.

This is a simple, high-level description of insertion-sort. If we look back to


Code Fragment 3.3 in Section 3.1.1, we see that the task of inserting a new entry
into the list of high scores is almost identical to the task of inserting a newly con-
sidered element in insertion-sort (except that game scores were ordered from high
to low). We provide a Java implementation of insertion-sort in Code Fragment 3.6,
using an outer loop to consider each element in turn, and an inner loop that moves
a newly considered element to its proper location relative to the (sorted) subarray
of elements that are to its left. We illustrate an example run of the insertion-sort
algorithm in Figure 3.5.
We note that if an array is already sorted, the inner loop of insertion-sort does
only one comparison, determines that there is no swap needed, and returns back
to the outer loop. Of course, we might have to do a lot more work than this if the
input array is extremely out of order. In fact, we will have to do the most work if
the input array is in decreasing order.

www.it-ebooks.info
3.1. Using Arrays 111

1 /∗∗ Insertion-sort of an array of characters into nondecreasing order ∗/


2 public static void insertionSort(char[ ] data) {
3 int n = data.length;
4 for (int k = 1; k < n; k++) { // begin with second character
5 char cur = data[k]; // time to insert cur=data[k]
6 int j = k; // find correct index j for cur
7 while (j > 0 && data[j−1] > cur) { // thus, data[j-1] must go after cur
8 data[j] = data[j−1]; // slide data[j-1] rightward
9 j−−; // and consider previous j for cur
10 }
11 data[j] = cur; // this is the proper place for cur
12 }
13 }
Code Fragment 3.6: Java code for performing insertion-sort on a character array.
cur no move

C B C D A E H G F
0 1 2 3 4 5 6 7
no move

D B C D A E H G F
0 1 2 3 4 5 6 7 insert
move move A move

A B C D E H G F B C D E H G F B C D E H G F
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
no move

E A B C D E H G F
0 1 2 3 4 5 6 7
no move

H A B C D E H G F
0 1 2 3 4 5 6 7 G
move no move insert

G A B C D E H F A B C D E H F
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 F
move move no move insert

F A B C D E G H A B C D E G H A B C D E G H
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7

A B C D E F G H Done!
0 1 2 3 4 5 6 7

Figure 3.5: Execution of the insertion-sort algorithm on an array of eight charac-


ters. Each row corresponds to an iteration of the outer loop, and each copy of the
sequence in a row corresponds to an iteration of the inner loop. The current element
that is being inserted is highlighted in the array, and shown as the cur value.

www.it-ebooks.info
112 Chapter 3. Fundamental Data Structures

3.1.3 java.util Methods for Arrays and Random Numbers


Because arrays are so important, Java provides a class, java.util.Arrays, with a
number of built-in static methods for performing common tasks on arrays. Later in
this book, we will describe the algorithms that several of these methods are based
upon. For now, we provide an overview of the most commonly used methods of
that class, as follows (more discussion is in Section 3.5.1):
equals(A, B): Returns true if and only if the array A and the array B are
equal. Two arrays are considered equal if they have the
same number of elements and every corresponding pair
of elements in the two arrays are equal. That is, A and B
have the same values in the same order.
fill(A, x): Stores value x in every cell of array A, provided the type
of array A is defined so that it is allowed to store the
value x.
copyOf(A, n): Returns an array of size n such that the first k elements of
this array are copied from A, where k = min{n, A.length}.
If n > A.length, then the last n − A.length elements in
this array will be padded with default values, e.g., 0 for
an array of int and null for an array of objects.
copyOfRange(A, s, t): Returns an array of size t − s such that the elements of
this array are copied in order from A[s] to A[t − 1], where
s < t, padded as with copyOf( ) if t > A.length.
toString(A): Returns a String representation of the array A, beginning
with [, ending with ], and with elements of A displayed
separated by string ", ". The string representation of
an element A[i] is obtained using String.valueOf(A[i]),
which returns the string "null" for a null reference and
otherwise calls A[i].toString( ).
sort(A): Sorts the array A based on a natural ordering of its el-
ements, which must be comparable. Sorting algorithms
are the focus of Chapter 12.
binarySearch(A, x): Searches the sorted array A for value x, returning the
index where it is found, or else the index of where it
could be inserted while maintaining the sorted order. The
binary-search algorithm is described in Section 5.1.3.
As static methods, these are invoked directly on the java.util.Arrays class, not
on a particular instance of the class. For example, if data were an array, we
could sort it with syntax, java.util.Arrays.sort(data), or with the shorter syntax
Arrays.sort(data) if we first import the Arrays class (see Section 1.8).

www.it-ebooks.info
3.1. Using Arrays 113
PseudoRandom Number Generation
Another feature built into Java, which is often useful when testing programs dealing
with arrays, is the ability to generate pseudorandom numbers, that is, numbers that
appear to be random (but are not necessarily truly random). In particular, Java
has a built-in class, java.util.Random, whose instances are pseudorandom number
generators, that is, objects that compute a sequence of numbers that are statistically
random. These sequences are not actually random, however, in that it is possible
to predict the next number in the sequence given the past list of numbers. Indeed,
a popular pseudorandom number generator is to generate the next number, next,
from the current number, cur, according to the formula (in Java syntax):
next = (a ∗ cur + b) % n;
where a, b, and n are appropriately chosen integers, and % is the modulus opera-
tor. Something along these lines is, in fact, the method used by java.util.Random
objects, with n = 248 . It turns out that such a sequence can be proven to be statis-
tically uniform, which is usually good enough for most applications requiring ran-
dom numbers, such as games. For applications, such as computer security settings,
where unpredictable random sequences are needed, this kind of formula should not
be used. Instead, ideally a sample from a source that is actually random should be
used, such as radio static coming from outer space.
Since the next number in a pseudorandom generator is determined by the pre-
vious number(s), such a generator always needs a place to start, which is called its
seed. The sequence of numbers generated for a given seed will always be the same.
The seed for an instance of the java.util.Random class can be set in its constructor
or with its setSeed( ) method.
One common trick to get a different sequence each time a program is run is
to use a seed that will be different for each run. For example, we could use some
timed input from a user or we could set the seed to the current time in milliseconds
since January 1, 1970 (provided by method System.currentTimeMillis).
Methods of the java.util.Random class include the following:

nextBoolean( ): Returns the next pseudorandom boolean value.


nextDouble( ): Returns the next pseudorandom double value, between
0.0 and 1.0.
nextInt( ): Returns the next pseudorandom int value.
nextInt(n): Returns the next pseudorandom int value in the range
from 0 up to but not including n.
setSeed(s): Sets the seed of this pseudorandom number generator to
the long s.

www.it-ebooks.info
114 Chapter 3. Fundamental Data Structures
An Illustrative Example
We provide a short (but complete) illustrative program in Code Fragment 3.7.

1 import java.util.Arrays;
2 import java.util.Random;
3 /∗∗ Program showing some array uses. ∗/
4 public class ArrayTest {
5 public static void main(String[ ] args) {
6 int data[ ] = new int[10];
7 Random rand = new Random( ); // a pseudo-random number generator
8 rand.setSeed(System.currentTimeMillis( )); // use current time as a seed
9 // fill the data array with pseudo-random numbers from 0 to 99, inclusive
10 for (int i = 0; i < data.length; i++)
11 data[i] = rand.nextInt(100); // the next pseudo-random number
12 int[ ] orig = Arrays.copyOf(data, data.length); // make a copy of the data array
13 System.out.println("arrays equal before sort: "+Arrays.equals(data, orig));
14 Arrays.sort(data); // sorting the data array (orig is unchanged)
15 System.out.println("arrays equal after sort: " + Arrays.equals(data, orig));
16 System.out.println("orig = " + Arrays.toString(orig));
17 System.out.println("data = " + Arrays.toString(data));
18 }
19 }
Code Fragment 3.7: A simple test of some built-in methods in java.util.Arrays.

We show a sample output of this program below:


arrays equal before sort: true
arrays equal after sort: false
orig = [41, 38, 48, 12, 28, 46, 33, 19, 10, 58]
data = [10, 12, 19, 28, 33, 38, 41, 46, 48, 58]
In another run, we got the following output:
arrays equal before sort: true
arrays equal after sort: false
orig = [87, 49, 70, 2, 59, 37, 63, 37, 95, 1]
data = [1, 2, 37, 37, 49, 59, 63, 70, 87, 95]
By using a pseudorandom number generator to determine program values, we
get a different input to our program each time we run it. This feature is, in fact, what
makes pseudorandom number generators useful for testing code, particularly when
dealing with arrays. Even so, we should not use random test runs as a replacement
for reasoning about our code, as we might miss important special cases in test runs.
Note, for example, that there is a slight chance that the orig and data arrays will be
equal even after data is sorted, namely, if orig is already ordered. The odds of this
occurring are less than 1 in 3 million, so it’s unlikely to happen during even a few
thousand test runs; however, we need to reason that this is possible.

www.it-ebooks.info
3.1. Using Arrays 115

3.1.4 Simple Cryptography with Character Arrays


An important application of character arrays and strings is cryptography, which
is the science of secret messages. This field involves the process of encryption,
in which a message, called the plaintext, is converted into a scrambled message,
called the ciphertext. Likewise, cryptography studies corresponding ways of per-
forming decryption, turning a ciphertext back into its original plaintext.
Arguably the earliest encryption scheme is the Caesar cipher, which is named
after Julius Caesar, who used this scheme to protect important military messages.
(All of Caesar’s messages were written in Latin, of course, which already makes
them unreadable for most of us!) The Caesar cipher is a simple way to obscure a
message written in a language that forms words with an alphabet.
The Caesar cipher involves replacing each letter in a message with the letter that
is a certain number of letters after it in the alphabet. So, in an English message, we
might replace each A with D, each B with E, each C with F, and so on, if shifting by
three characters. We continue this approach all the way up to W, which is replaced
with Z. Then, we let the substitution pattern wrap around, so that we replace X
with A, Y with B, and Z with C.

Converting Between Strings and Character Arrays


Given that strings are immutable, we cannot directly edit an instance to encrypt
it. Instead, our goal will be to generate a new string. A convenient technique for
performing string transformations is to create an equivalent array of characters, edit
the array, and then reassemble a (new) string based on the array.
Java has support for conversions from strings to character arrays and vice versa.
Given a string S, we can create a new character array matching S by using the
method, S.toCharArray( ). For example, if s="bird", the method returns the char-
acter array A={'b', 'i', 'r', 'd'}. Conversely, there is a form of the String
constructor that accepts a character array as a parameter. For example, with char-
acter array A={'b', 'i', 'r', 'd'}, the syntax new String(A) produces "bird".

Using Character Arrays as Replacement Codes


If we were to number our letters like array indices, so that A is 0, B is 1, C is 2, then
we can represent the replacement rule as a character array, encoder, such that A is
mapped to encoder[0], B is mapped to encoder[1], and so on. Then, in order to find
a replacement for a character in our Caesar cipher, we need to map the characters
from A to Z to the respective numbers from 0 to 25. Fortunately, we can rely on the
fact that characters are represented in Unicode by integer code points, and the code
points for the uppercase letters of the Latin alphabet are consecutive (for simplicity,
we restrict our encryption to uppercase letters).

www.it-ebooks.info
116 Chapter 3. Fundamental Data Structures
Java allows us to “subtract” two characters from each other, with an integer
result equal to their separation distance in the encoding. Given a variable c that is
known to be an uppercase letter, the Java computation, j = c − 'A' produces the
desired index j. As a sanity check, if character c is 'A', then j = 0. When c is 'B',
the difference is 1. In general, the integer j that results from such a calculation can
be used as an index into our precomputed encoder array, as illustrated in Figure 3.6.
encoder array

D E F G H I J K L M N O P Q R S T U V W X Y Z A B C
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25

'T' − 'A'
Using 'T' as an index
= 84 − 65 Here is the
In Unicode replacement for 'T'
= 19
Figure 3.6: Illustrating the use of uppercase characters as indices, in this case to
perform the replacement rule for Caesar cipher encryption.

The process of decrypting the message can be implemented by simply using


a different character array to represent the replacement rule—one that effectively
shifts characters in the opposite direction.
In Code Fragment 3.8, we present a Java class that performs the Caesar cipher
with an arbitrary rotational shift. The constructor for the class builds the encoder
and decoder translation arrays for the given rotation. We rely heavily on modular
arithmetic, as a Caesar cipher with a rotation of r encodes the letter having index k
with the letter having index (k + r) mod 26, where mod is the modulo operator,
which returns the remainder after performing an integer division. This operator is
denoted with % in Java, and it is exactly the operator we need to easily perform
the wraparound at the end of the alphabet, for 26 mod 26 is 0, 27 mod 26 is 1,
and 28 mod 26 is 2. The decoder array for the Caesar cipher is just the opposite—
we replace each letter with the one r places before it, with wraparound; to avoid
subtleties involving negative numbers and the modulus operator, we will replace
the letter having code k with the letter having code (k − r + 26) mod 26.
With the encoder and decoder arrays in hand, the encryption and decryption
algorithms are essentially the same, and so we perform both by means of a private
utility method named transform. This method converts a string to a character ar-
ray, performs the translation diagrammed in Figure 3.6 for any uppercase alphabet
symbols, and finally returns a new string, constructed from the updated array.
The main method of the class, as a simple test, produces the following output:
Encryption code = DEFGHIJKLMNOPQRSTUVWXYZABC
Decryption code = XYZABCDEFGHIJKLMNOPQRSTUVW
Secret: WKH HDJOH LV LQ SODB; PHHW DW MRH’V.
Message: THE EAGLE IS IN PLAY; MEET AT JOE’S.

www.it-ebooks.info
3.1. Using Arrays 117

1 /∗∗ Class for doing encryption and decryption using the Caesar Cipher. ∗/
2 public class CaesarCipher {
3 protected char[ ] encoder = new char[26]; // Encryption array
4 protected char[ ] decoder = new char[26]; // Decryption array
5 /∗∗ Constructor that initializes the encryption and decryption arrays ∗/
6 public CaesarCipher(int rotation) {
7 for (int k=0; k < 26; k++) {
8 encoder[k] = (char) ('A' + (k + rotation) % 26);
9 decoder[k] = (char) ('A' + (k − rotation + 26) % 26);
10 }
11 }
12 /∗∗ Returns String representing encrypted message. ∗/
13 public String encrypt(String message) {
14 return transform(message, encoder); // use encoder array
15 }
16 /∗∗ Returns decrypted message given encrypted secret. ∗/
17 public String decrypt(String secret) {
18 return transform(secret, decoder); // use decoder array
19 }
20 /∗∗ Returns transformation of original String using given code. ∗/
21 private String transform(String original, char[ ] code) {
22 char[ ] msg = original.toCharArray( );
23 for (int k=0; k < msg.length; k++)
24 if (Character.isUpperCase(msg[k])) { // we have a letter to change
25 int j = msg[k] − 'A'; // will be value from 0 to 25
26 msg[k] = code[j]; // replace the character
27 }
28 return new String(msg);
29 }
30 /∗∗ Simple main method for testing the Caesar cipher ∗/
31 public static void main(String[ ] args) {
32 CaesarCipher cipher = new CaesarCipher(3);
33 System.out.println("Encryption code = " + new String(cipher.encoder));
34 System.out.println("Decryption code = " + new String(cipher.decoder));
35 String message = "THE EAGLE IS IN PLAY; MEET AT JOE'S.";
36 String coded = cipher.encrypt(message);
37 System.out.println("Secret: " + coded);
38 String answer = cipher.decrypt(coded);
39 System.out.println("Message: " + answer); // should be plaintext again
40 }
41 }

Code Fragment 3.8: A complete Java class for performing the Caesar cipher.

www.it-ebooks.info
118 Chapter 3. Fundamental Data Structures

3.1.5 Two-Dimensional Arrays and Positional Games


Many computer games, be they strategy games, simulation games, or first-person
conflict games, involve objects that reside in a two-dimensional space. Software for
such positional games needs a way of representing objects in a two-dimensional
space. A natural way to do this is with a two-dimensional array, where we use two
indices, say i and j, to refer to the cells in the array. The first index usually refers
to a row number and the second to a column number. Given such an array, we can
maintain two-dimensional game boards and perform other kinds of computations
involving data stored in rows and columns.
Arrays in Java are one-dimensional; we use a single index to access each cell
of an array. Nevertheless, there is a way we can define two-dimensional arrays in
Java—we can create a two-dimensional array as an array of arrays. That is, we can
define a two-dimensional array to be an array with each of its cells being another
array. Such a two-dimensional array is sometimes also called a matrix. In Java, we
may declare a two-dimensional array as follows:

int[ ][ ] data = new int[8][10];


This statement creates a two-dimensional “array of arrays,” data, which is 8 × 10,
having 8 rows and 10 columns. That is, data is an array of length 8 such that each
element of data is an array of length 10 of integers. (See Figure 3.7.) The following
would then be valid uses of array data and int variables i, j, and k:

data[i][i+1] = data[i][i] + 3;
j = data.length; // j is 8
k = data[4].length; // k is 10
Two-dimensional arrays have many applications to numerical analysis. Rather
than going into the details of such applications, however, we explore an application
of two-dimensional arrays for implementing a simple positional game.
0 1 2 3 4 5 6 7 8 9
0 22 18 709 5 33 10 4 56 82 440
1 45 32 830 120 750 660 13 77 20 105
2 4 880 45 66 61 28 650 7 510 67
3 940 12 36 3 20 100 306 590 0 500
4 50 65 42 49 88 25 70 126 83 288
5 398 233 5 83 59 232 49 8 365 90
6 33 58 632 87 94 5 59 204 120 829
7 62 394 3 4 102 140 183 390 16 26

Figure 3.7: Illustration of a two-dimensional integer array, data, which has 8 rows
and 10 columns. The value of data[3][5] is 100 and the value of data[6][2] is 632.

www.it-ebooks.info
3.1. Using Arrays 119
Tic-Tac-Toe

As most school children know, Tic-Tac-Toe is a game played in a three-by-three


board. Two players—X and O—alternate in placing their respective marks in the
cells of this board, starting with player X. If either player succeeds in getting three
of his or her marks in a row, column, or diagonal, then that player wins.
This is admittedly not a sophisticated positional game, and it’s not even that
much fun to play, since a good player O can always force a tie. Tic-Tac-Toe’s saving
grace is that it is a nice, simple example showing how two-dimensional arrays can
be used for positional games. Software for more sophisticated positional games,
such as checkers, chess, or the popular simulation games, are all based on the same
approach we illustrate here for using a two-dimensional array for Tic-Tac-Toe.
The basic idea is to use a two-dimensional array, board, to maintain the game
board. Cells in this array store values that indicate if that cell is empty or stores an
X or O. That is, board is a three-by-three matrix, whose middle row consists of the
cells board[1][0], board[1][1], and board[1][2]. In our case, we choose to make the
cells in the board array be integers, with a 0 indicating an empty cell, a 1 indicating
an X, and a −1 indicating an O. This encoding allows us to have a simple way of
testing if a given board configuration is a win for X or O, namely, if the values
of a row, column, or diagonal add up to 3 or −3, respectively. We illustrate this
approach in Figure 3.8.

Figure 3.8: An illustration of a Tic-Tac-Toe board and the two-dimensional integer


array, board, representing it.

We give a complete Java class for maintaining a Tic-Tac-Toe board for two
players in Code Fragments 3.9 and 3.10. We show a sample output in Figure 3.9.
Note that this code is just for maintaining the Tic-Tac-Toe board and register-
ing moves; it doesn’t perform any strategy or allow someone to play Tic-Tac-Toe
against the computer. The details of such a program are beyond the scope of this
chapter, but it might nonetheless make a good course project (see Exercise P-8.67).

www.it-ebooks.info
120 Chapter 3. Fundamental Data Structures

1 /∗∗ Simulation of a Tic-Tac-Toe game (does not do strategy). ∗/


2 public class TicTacToe {
3 public static final int X = 1, O = −1; // players
4 public static final int EMPTY = 0; // empty cell
5 private int board[ ][ ] = new int[3][3]; // game board
6 private int player; // current player
7 /∗∗ Constructor ∗/
8 public TicTacToe( ) { clearBoard( ); }
9 /∗∗ Clears the board ∗/
10 public void clearBoard( ) {
11 for (int i = 0; i < 3; i++)
12 for (int j = 0; j < 3; j++)
13 board[i][j] = EMPTY; // every cell should be empty
14 player = X; // the first player is 'X'
15 }
16 /∗∗ Puts an X or O mark at position i,j. ∗/
17 public void putMark(int i, int j) throws IllegalArgumentException {
18 if ((i < 0) | | (i > 2) | | (j < 0) | | (j > 2))
19 throw new IllegalArgumentException("Invalid board position");
20 if (board[i][j] != EMPTY)
21 throw new IllegalArgumentException("Board position occupied");
22 board[i][j] = player; // place the mark for the current player
23 player = − player; // switch players (uses fact that O = - X)
24 }
25 /∗∗ Checks whether the board configuration is a win for the given player. ∗/
26 public boolean isWin(int mark) {
27 return ((board[0][0] + board[0][1] + board[0][2] == mark∗3) // row 0
28 | | (board[1][0] + board[1][1] + board[1][2] == mark∗3) // row 1
29 | | (board[2][0] + board[2][1] + board[2][2] == mark∗3) // row 2
30 | | (board[0][0] + board[1][0] + board[2][0] == mark∗3) // column 0
31 | | (board[0][1] + board[1][1] + board[2][1] == mark∗3) // column 1
32 | | (board[0][2] + board[1][2] + board[2][2] == mark∗3) // column 2
33 | | (board[0][0] + board[1][1] + board[2][2] == mark∗3) // diagonal
34 | | (board[2][0] + board[1][1] + board[0][2] == mark∗3)); // rev diag
35 }
36 /∗∗ Returns the winning player's code, or 0 to indicate a tie (or unfinished game).∗/
37 public int winner( ) {
38 if (isWin(X))
39 return(X);
40 else if (isWin(O))
41 return(O);
42 else
43 return(0);
44 }
Code Fragment 3.9: A simple, complete Java class for playing Tic-Tac-Toe between
two players. (Continues in Code Fragment 3.10.)

www.it-ebooks.info
3.1. Using Arrays 121

45 /∗∗ Returns a simple character string showing the current board. ∗/


46 public String toString( ) {
47 StringBuilder sb = new StringBuilder( );
48 for (int i=0; i<3; i++) {
49 for (int j=0; j<3; j++) {
50 switch (board[i][j]) {
51 case X: sb.append("X"); break;
52 case O: sb.append("O"); break;
53 case EMPTY: sb.append(" "); break;
54 }
55 if (j < 2) sb.append("|"); // column boundary
56 }
57 if (i < 2) sb.append("\n-----\n"); // row boundary
58 }
59 return sb.toString( );
60 }
61 /∗∗ Test run of a simple game ∗/
62 public static void main(String[ ] args) {
63 TicTacToe game = new TicTacToe( );
64 /∗ X moves: ∗/ /∗ O moves: ∗/
65 game.putMark(1,1); game.putMark(0,2);
66 game.putMark(2,2); game.putMark(0,0);
67 game.putMark(0,1); game.putMark(2,1);
68 game.putMark(1,2); game.putMark(1,0);
69 game.putMark(2,0);
70 System.out.println(game);
71 int winningPlayer = game.winner( );
72 String[ ] outcome = {"O wins", "Tie", "X wins"}; // rely on ordering
73 System.out.println(outcome[1 + winningPlayer]);
74 }
75 }
Code Fragment 3.10: A simple, complete Java class for playing Tic-Tac-Toe be-
tween two players. (Continued from Code Fragment 3.9.)

O|X|O
-----
O|X|X
-----
X|O|X
Tie

Figure 3.9: Sample output of a Tic-Tac-Toe game.

www.it-ebooks.info
122 Chapter 3. Fundamental Data Structures

3.2 Singly Linked Lists


In the previous section, we presented the array data structure and discussed some
of its applications. Arrays are great for storing things in a certain order, but they
have drawbacks. The capacity of the array must be fixed when it is created, and
insertions and deletions at interior positions of an array can be time consuming if
many elements must be shifted.
In this section, we introduce a data structure known as a linked list, which pro-
vides an alternative to an array-based structure. A linked list, in its simplest form,
is a collection of nodes that collectively form a linear sequence. In a singly linked
list, each node stores a reference to an object that is an element of the sequence, as
well as a reference to the next node of the list (see Figure 3.10).

MSP
element next

Figure 3.10: Example of a node instance that forms part of a singly linked list.
The node’s element field refers to an object that is an element of the sequence (the
airport code MSP, in this example), while the next field refers to the subsequent
node of the linked list (or null if there is no further node).

A linked list’s representation relies on the collaboration of many objects (see


Figure 3.11). Minimally, the linked list instance must keep a reference to the first
node of the list, known as the head. Without an explicit reference to the head,
there would be no way to locate that node (or indirectly, any others). The last
node of the list is known as the tail. The tail of a list can be found by traversing the
linked list— starting at the head and moving from one node to another by following
each node’s next reference. We can identify the tail as the node having null as its
next reference. This process is also known as link hopping or pointer hopping.
However, storing an explicit reference to the tail node is a common efficiency to
avoid such a traversal. In similar regard, it is common for a linked list instance to
keep a count of the total number of nodes that comprise the list (also known as the
size of the list), to avoid traversing the list to count the nodes.

LAX MSP ATL BOS

head tail
Figure 3.11: Example of a singly linked list whose elements are strings indicating
airport codes. The list instance maintains a member named head that refers to the
first node of the list, and another member named tail that refers to the last node of
the list. The null value is denoted as Ø.

www.it-ebooks.info
3.2. Singly Linked Lists 123
Inserting an Element at the Head of a Singly Linked List
An important property of a linked list is that it does not have a predetermined fixed
size; it uses space proportional to its current number of elements. When using a
singly linked list, we can easily insert an element at the head of the list, as shown
in Figure 3.12, and described with pseudocode in Code Fragment 3.11. The main
idea is that we create a new node, set its element to the new element, set its next
link to refer to the current head, and set the list’s head to point to the new node.
head

MSP ATL BOS

(a)
newest head

LAX MSP ATL BOS

(b)
newest head

LAX MSP ATL BOS

(c)

Figure 3.12: Insertion of an element at the head of a singly linked list: (a) before the
insertion; (b) after a new node is created and linked to the existing head; (c) after
reassignment of the head reference to the newest node.

Algorithm addFirst(e):
newest = Node(e) {create new node instance storing reference to element e}
newest.next = head {set new node’s next to reference the old head node}
head = newest {set variable head to reference the new node}
size = size + 1 {increment the node count}

Code Fragment 3.11: Inserting a new element at the beginning of a singly linked list.
Note that we set the next pointer of the new node before we reassign variable head
to it. If the list were initially empty (i.e., head is null), then a natural consequence
is that the new node has its next reference set to null.

www.it-ebooks.info
124 Chapter 3. Fundamental Data Structures
Inserting an Element at the Tail of a Singly Linked List
We can also easily insert an element at the tail of the list, provided we keep a
reference to the tail node, as shown in Figure 3.13. In this case, we create a new
node, assign its next reference to null, set the next reference of the tail to point to
this new node, and then update the tail reference itself to this new node. We give
pseudocode for the process in Code Fragment 3.12.
tail

MSP ATL BOS

(a)
tail newest

MSP ATL BOS MIA

(b)
tail newest

MSP ATL BOS MIA

(c)

Figure 3.13: Insertion at the tail of a singly linked list: (a) before the insertion;
(b) after creation of a new node; (c) after reassignment of the tail reference. Note
that we must set the next link of the tail node in (b) before we assign the tail variable
to point to the new node in (c).

Algorithm addLast(e):
newest = Node(e) {create new node instance storing reference to element e}
newest.next = null {set new node’s next to reference the null object}
tail.next = newest {make old tail node point to new node}
tail = newest {set variable tail to reference the new node}
size = size + 1 {increment the node count}

Code Fragment 3.12: Inserting a new node at the end of a singly linked list. Note
that we set the next pointer for the old tail node before we make variable tail point
to the new node. This code would need to be adjusted for inserting onto an empty
list, since there would not be an existing tail node.

www.it-ebooks.info
3.2. Singly Linked Lists 125
Removing an Element from a Singly Linked List
Removing an element from the head of a singly linked list is essentially the reverse
operation of inserting a new element at the head. This operation is illustrated in
Figure 3.14 and described in detail in Code Fragment 3.13.
head

LAX MSP ATL BOS

(a)
head

LAX MSP ATL BOS

(b)
head

MSP ATL BOS

(c)
Figure 3.14: Removal of an element at the head of a singly linked list: (a) before
the removal; (b) after “linking out” the old head; (c) final configuration.

Algorithm removeFirst( ):
if head == null then
the list is empty.
head = head.next {make head point to next node (or null)}
size = size − 1 {decrement the node count}
Code Fragment 3.13: Removing the node at the beginning of a singly linked list.

Unfortunately, we cannot easily delete the last node of a singly linked list. Even
if we maintain a tail reference directly to the last node of the list, we must be able
to access the node before the last node in order to remove the last node. But we
cannot reach the node before the tail by following next links from the tail. The only
way to access this node is to start from the head of the list and search all the way
through the list. But such a sequence of link-hopping operations could take a long
time. If we want to support such an operation efficiently, we will need to make our
list doubly linked (as we do in Section 3.4).

www.it-ebooks.info
126 Chapter 3. Fundamental Data Structures

3.2.1 Implementing a Singly Linked List Class


In this section, we present a complete implementation of a SinglyLinkedList class,
supporting the following methods:
size( ): Returns the number of elements in the list.
isEmpty( ): Returns true if the list is empty, and false otherwise.
first( ): Returns (but does not remove) the first element in the list.
last( ): Returns (but does not remove) the last element in the list.
addFirst(e): Adds a new element to the front of the list.
addLast(e): Adds a new element to the end of the list.
removeFirst( ): Removes and returns the first element of the list.
If first( ), last( ), or removeFirst( ) are called on a list that is empty, we will simply
return a null reference and leave the list unchanged.
Because it does not matter to us what type of elements are stored in the list, we
use Java’s generics framework (see Section 2.5.2) to define our class with a formal
type parameter E that represents the user’s desired element type.
Our implementation also takes advantage of Java’s support for nested classes
(see Section 2.6), as we define a private Node class within the scope of the pub-
lic SinglyLinkedList class. Code Fragment 3.14 presents the Node class definition,
and Code Fragment 3.15 the rest of the SinglyLinkedList class. Having Node as a
nested class provides strong encapsulation, shielding users of our class from the un-
derlying details about nodes and links. This design also allows Java to differentiate
this node type from forms of nodes we may define for use in other structures.

1 public class SinglyLinkedList<E> {


2 //---------------- nested Node class ----------------
3 private static class Node<E> {
4 private E element; // reference to the element stored at this node
5 private Node<E> next; // reference to the subsequent node in the list
6 public Node(E e, Node<E> n) {
7 element = e;
8 next = n;
9 }
10 public E getElement( ) { return element; }
11 public Node<E> getNext( ) { return next; }
12 public void setNext(Node<E> n) { next = n; }
13 } //----------- end of nested Node class -----------
... rest of SinglyLinkedList class will follow ...
Code Fragment 3.14: A nested Node class within the SinglyLinkedList class. (The
remainder of the SinglyLinkedList class will be given in Code Fragment 3.15.)

www.it-ebooks.info
3.2. Singly Linked Lists 127
1 public class SinglyLinkedList<E> {
... (nested Node class goes here)
14 // instance variables of the SinglyLinkedList
15 private Node<E> head = null; // head node of the list (or null if empty)
16 private Node<E> tail = null; // last node of the list (or null if empty)
17 private int size = 0; // number of nodes in the list
18 public SinglyLinkedList( ) { } // constructs an initially empty list
19 // access methods
20 public int size( ) { return size; }
21 public boolean isEmpty( ) { return size == 0; }
22 public E first( ) { // returns (but does not remove) the first element
23 if (isEmpty( )) return null;
24 return head.getElement( );
25 }
26 public E last( ) { // returns (but does not remove) the last element
27 if (isEmpty( )) return null;
28 return tail.getElement( );
29 }
30 // update methods
31 public void addFirst(E e) { // adds element e to the front of the list
32 head = new Node<>(e, head); // create and link a new node
33 if (size == 0)
34 tail = head; // special case: new node becomes tail also
35 size++;
36 }
37 public void addLast(E e) { // adds element e to the end of the list
38 Node<E> newest = new Node<>(e, null); // node will eventually be the tail
39 if (isEmpty( ))
40 head = newest; // special case: previously empty list
41 else
42 tail.setNext(newest); // new node after existing tail
43 tail = newest; // new node becomes the tail
44 size++;
45 }
46 public E removeFirst( ) { // removes and returns the first element
47 if (isEmpty( )) return null; // nothing to remove
48 E answer = head.getElement( );
49 head = head.getNext( ); // will become null if list had only one node
50 size−−;
51 if (size == 0)
52 tail = null; // special case as list is now empty
53 return answer;
54 }
55 }
Code Fragment 3.15: The SinglyLinkedList class definition (when combined with
the nested Node class of Code Fragment 3.14).

www.it-ebooks.info
128 Chapter 3. Fundamental Data Structures

3.3 Circularly Linked Lists


Linked lists are traditionally viewed as storing a sequence of items in a linear or-
der, from first to last. However, there are many applications in which data can
be more naturally viewed as having a cyclic order, with well-defined neighboring
relationships, but no fixed beginning or end.
For example, many multiplayer games are turn-based, with player A taking a
turn, then player B, then player C, and so on, but eventually back to player A again,
and player B again, with the pattern repeating. As another example, city buses and
subways often run on a continuous loop, making stops in a scheduled order, but
with no designated first or last stop per se. We next consider another important
example of a cyclic order in the context of computer operating systems.

3.3.1 Round-Robin Scheduling


One of the most important roles of an operating system is in managing the many
processes that are currently active on a computer, including the scheduling of those
processes on one or more central processing units (CPUs). In order to support
the responsiveness of an arbitrary number of concurrent processes, most operating
systems allow processes to effectively share use of the CPUs, using some form of
an algorithm known as round-robin scheduling. A process is given a short turn
to execute, known as a time slice, but it is interrupted when the slice ends, even if
its job is not yet complete. Each active process is given its own time slice, taking
turns in a cyclic order. New processes can be added to the system, and processes
that complete their work can be removed.
A round-robin scheduler could be implemented with a traditional linked list, by
repeatedly performing the following steps on linked list L (see Figure 3.15):
1. process p = L.removeFirst( )
2. Give a time slice to process p
3. L.addLast(p)
Unfortunately, there are drawbacks to the use of a traditional linked list for this
purpose. It is unnecessarily inefficient to repeatedly throw away a node from one
end of the list, only to create a new node for the same element when reinserting it,
not to mention the various updates that are performed to decrement and increment
the list’s size and to unlink and relink nodes.
In the remainder of this section, we demonstrate how a slight modification to
our singly linked list implementation can be used to provide a more efficient data
structure for representing a cyclic order.

www.it-ebooks.info
3.3. Circularly Linked Lists 129
2. Give current process
a time slice on CPU
CPU

1. Remove the next 3. Add process to end


waiting process waiting processes of waiting pool

Figure 3.15: The three iterative steps for round-robin scheduling.

3.3.2 Designing and Implementing a Circularly Linked List


In this section, we design a structure known as a circularly linked list, which is
essentially a singularly linked list in which the next reference of the tail node is set
to refer back to the head of the list (rather than null), as shown in Figure 3.16.
head tail

LAX MSP ATL BOS

Figure 3.16: Example of a singly linked list with circular structure.

We use this model to design and implement a new CircularlyLinkedList class,


which supports all of the public behaviors of our SinglyLinkedList class and one
additional update method:
rotate( ): Moves the first element to the end of the list.
With this new operation, round-robin scheduling can be efficiently implemented by
repeatedly performing the following steps on a circularly linked list C:
1. Give a time slice to process C.first( )
2. C.rotate( )

Additional Optimization
In implementing a new class, we make one additional optimization—we no longer
explicitly maintain the head reference. So long as we maintain a reference to the
tail, we can locate the head as tail.getNext( ). Maintaining only the tail reference
not only saves a bit on memory usage, it makes the code simpler and more efficient,
as it removes the need to perform additional operations to keep a head reference
current. In fact, our new implementation is arguably superior to our original singly
linked list implementation, even if we are not interested in the new rotate method.

www.it-ebooks.info
130 Chapter 3. Fundamental Data Structures
Operations on a Circularly Linked List
Implementing the new rotate method is quite trivial. We do not move any nodes
or elements, we simply advance the tail reference to point to the node that follows
it (the implicit head of the list). Figure 3.17 illustrates this operation using a more
symmetric visualization of a circularly linked list.
(head) tail
(head)
tail
LAX LAX

BOS MSP BOS MSP

ATL ATL

(a) (b)
Figure 3.17: The rotation operation on a circularly linked list: (a) before the rota-
tion, representing sequence { LAX, MSP, ATL, BOS }; (b) after the rotation, rep-
resenting sequence { MSP, ATL, BOS, LAX }. We display the implicit head refer-
ence, which is identified only as tail.getNext( ) within the implementation.

We can add a new element at the front of the list by creating a new node and
linking it just after the tail of the list, as shown in Figure 3.18. To implement the
addLast method, we can rely on the use of a call to addFirst and then immediately
advance the tail reference so that the newest node becomes the last.
Removing the first node from a circularly linked list can be accomplished by
simply updating the next field of the tail node to bypass the implicit head. A Java
implementation of all methods of the CircularlyLinkedList class is given in Code
Fragment 3.16.
tail newest

LAX STL

BOS
MSP
ATL

Figure 3.18: Effect of a call to addFirst(STL) on the circularly linked list of Fig-
ure 3.17(b). The variable newest has local scope during the execution of the
method. Notice that when the operation is complete, STL is the first element of
the list, as it is stored within the implicit head, tail.getNext( ).

www.it-ebooks.info
3.3. Circularly Linked Lists 131
1 public class CircularlyLinkedList<E> {
... (nested node class identical to that of the SinglyLinkedList class)
14 // instance variables of the CircularlyLinkedList
15 private Node<E> tail = null; // we store tail (but not head)
16 private int size = 0; // number of nodes in the list
17 public CircularlyLinkedList( ) { } // constructs an initially empty list
18 // access methods
19 public int size( ) { return size; }
20 public boolean isEmpty( ) { return size == 0; }
21 public E first( ) { // returns (but does not remove) the first element
22 if (isEmpty( )) return null;
23 return tail.getNext( ).getElement( ); // the head is *after* the tail
24 }
25 public E last( ) { // returns (but does not remove) the last element
26 if (isEmpty( )) return null;
27 return tail.getElement( );
28 }
29 // update methods
30 public void rotate( ) { // rotate the first element to the back of the list
31 if (tail != null) // if empty, do nothing
32 tail = tail.getNext( ); // the old head becomes the new tail
33 }
34 public void addFirst(E e) { // adds element e to the front of the list
35 if (size == 0) {
36 tail = new Node<>(e, null);
37 tail.setNext(tail); // link to itself circularly
38 } else {
39 Node<E> newest = new Node<>(e, tail.getNext( ));
40 tail.setNext(newest);
41 }
42 size++;
43 }
44 public void addLast(E e) { // adds element e to the end of the list
45 addFirst(e); // insert new element at front of list
46 tail = tail.getNext( ); // now new element becomes the tail
47 }
48 public E removeFirst( ) { // removes and returns the first element
49 if (isEmpty( )) return null; // nothing to remove
50 Node<E> head = tail.getNext( );
51 if (head == tail) tail = null; // must be the only node left
52 else tail.setNext(head.getNext( )); // removes ”head” from the list
53 size−−;
54 return head.getElement( );
55 }
56 }
Code Fragment 3.16: Implementation of the CircularlyLinkedList class.

www.it-ebooks.info
132 Chapter 3. Fundamental Data Structures

3.4 Doubly Linked Lists


In a singly linked list, each node maintains a reference to the node that is immedi-
ately after it. We have demonstrated the usefulness of such a representation when
managing a sequence of elements. However, there are limitations that stem from
the asymmetry of a singly linked list. In Section 3.2, we demonstrated that we can
efficiently insert a node at either end of a singly linked list, and can delete a node at
the head of a list, but we are unable to efficiently delete a node at the tail of the list.
More generally, we cannot efficiently delete an arbitrary node from an interior po-
sition of the list if only given a reference to that node, because we cannot determine
the node that immediately precedes the node to be deleted (yet, that node needs to
have its next reference updated).
To provide greater symmetry, we define a linked list in which each node keeps
an explicit reference to the node before it and a reference to the node after it. Such
a structure is known as a doubly linked list. These lists allow a greater variety of
O(1)-time update operations, including insertions and deletions at arbitrary posi-
tions within the list. We continue to use the term “next” for the reference to the
node that follows another, and we introduce the term “prev” for the reference to the
node that precedes it.

Header and Trailer Sentinels


In order to avoid some special cases when operating near the boundaries of a doubly
linked list, it helps to add special nodes at both ends of the list: a header node at the
beginning of the list, and a trailer node at the end of the list. These “dummy” nodes
are known as sentinels (or guards), and they do not store elements of the primary
sequence. A doubly linked list with such sentinels is shown in Figure 3.19.
header next next next next trailer
JFK PVD SFO
prev prev prev prev

Figure 3.19: A doubly linked list representing the sequence { JFK, PVD, SFO },
using sentinels header and trailer to demarcate the ends of the list.

When using sentinel nodes, an empty list is initialized so that the next field
of the header points to the trailer, and the prev field of the trailer points to the
header; the remaining fields of the sentinels are irrelevant (presumably null, in
Java). For a nonempty list, the header’s next will refer to a node containing the first
real element of a sequence, just as the trailer’s prev references the node containing
the last element of a sequence.

www.it-ebooks.info

You might also like