Data Structures and Algorithms in Java 6th Edition 101 150
Data Structures and Algorithms in Java 6th Edition 101 150
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 }
www.it-ebooks.info
2.4. Exceptions 85
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.
www.it-ebooks.info
2.4. Exceptions 87
Throwable
Error Exception
OutofMemoryError
IllegalArgumentException FileNotFoundException EOFException
IndexOutOfBoundsException
NumberFormatException NullPointerException
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.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.
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.
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.
www.it-ebooks.info
96 Chapter 2. Object-Oriented Design
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
Contents
www.it-ebooks.info
104 Chapter 3. Fundamental Data Structures
www.it-ebooks.info
3.1. Using Arrays 105
A Class for High Scores
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
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
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.
www.it-ebooks.info
3.1. Using Arrays 111
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
www.it-ebooks.info
112 Chapter 3. Fundamental Data Structures
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:
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.
www.it-ebooks.info
3.1. Using Arrays 115
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.
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
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
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
www.it-ebooks.info
3.1. Using Arrays 121
O|X|O
-----
O|X|X
-----
X|O|X
Tie
www.it-ebooks.info
122 Chapter 3. Fundamental Data Structures
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).
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
(a)
newest head
(b)
newest head
(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
(a)
tail newest
(b)
tail newest
(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
(a)
head
(b)
head
(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
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
www.it-ebooks.info
3.3. Circularly Linked Lists 129
2. Give current process
a time slice on CPU
CPU
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
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
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