(1863-7310) Kingsley Sage - Concise Guide To Object-Oriented Programming - An Accessible Approach Using Java. (2019)
(1863-7310) Kingsley Sage - Concise Guide To Object-Oriented Programming - An Accessible Approach Using Java. (2019)
(1863-7310) Kingsley Sage - Concise Guide To Object-Oriented Programming - An Accessible Approach Using Java. (2019)
Kingsley Sage
Concise Guide
to Object-Oriented
Programming
An Accessible Approach Using Java
Undergraduate Topics in Computer
Science
Series Editor
Ian Mackie, University of Sussex, Brighton, UK
Advisory Editors
Samson Abramsky, Department of Computer Science, University of Oxford,
Oxford, UK
Chris Hankin, Department of Computing, Imperial College London, London, UK
Dexter C. Kozen, Computer Science Department, Cornell University, Ithaca, NY,
USA
Andrew Pitts, William Gates Building, University of Cambridge, Cambridge, UK
Hanne Riis Nielson, Department of Applied Math and Computer Science,
Technical University of Denmark, Kgs. Lyngby, Denmark
Steven S. Skiena, Department of Computer Science, Stony Brook University, Stony
Brook, NY, USA
Iain Stewart, Department of Computer Science, Science Labs, University of
Durham, Durham, UK
Mike Hinchey, Lero, Tierney Building, University of Limerick, Limerick, Ireland
‘Undergraduate Topics in Computer Science’ (UTiCS) delivers high-quality
instructional content for undergraduates studying in all areas of computing and
information science. From core foundational and theoretical material to final-year
topics and applications, UTiCS books take a fresh, concise, and modern approach
and are ideal for self-study or for a one- or two-semester course. The texts are all
authored by established experts in their fields, reviewed by an international advisory
board, and contain numerous examples and problems, many of which include fully
worked solutions.
The UTiCS concept relies on high-quality, concise books in softback format, and
generally a maximum of 275-300 pages. For undergraduate textbooks that are likely
to be longer, more expository, Springer continues to offer the highly regarded Texts
in Computer Science series, to which we refer potential authors.
Concise Guide
to Object-Oriented
Programming
An Accessible Approach Using Java
123
Kingsley Sage
School of Engineering and Informatics
University of Sussex
Falmer, East Sussex, UK
This Springer imprint is published by the registered company Springer Nature Switzerland AG
The registered company address is: Gewerbestrasse 11, 6330 Cham, Switzerland
Preface
When I was first approached to write this book, it was suggested that its purpose
was to provide an accessible introduction to coding and the world of Object Ori-
ented Programming (OOP). Standard texts on the subject often fall between those
that provide only a very lightweight treatment of the subject (“a little knowledge
can be a frustrating thing”), and those that run to 500 pages or more that are rather
better suited as reference texts or as support on a lengthy period of study in depth.
The challenge for this book is to provide an accessible introduction to the world of
v
vi Preface
coding and OOP in a way that is helpful to the first-time coder and allows them to
develop and to understand their knowledge and skills in a way that is relevant and
practical. The examples developed for this book are intended to show how OOP
skills can be used to create applications and programs that have everyday value,
rather than examples that have been synthesised solely to demonstrate an academic
point.
The reader should be able to use this book to develop a solid appreciation of
OOP and how to code. The programming language used throughout is Java. Java
has been chosen as it can be used across all computing platforms, because it has a
commercial skill that has a clear on-going value derived from its adoption as a core
language for smartphone applications on the Android platform, and as the language
at the heart of the Java EE 8 Jakarta Enterprise scale framework. The book focusses
on the core Java language and does not consider smartphone or EE 8 coding, as
these require skills over and above what this book is about. However, a knowledge
of core Java coding and some of the related issues also discussed in this book would
form an appropriate pre-requisite for the further study of these topics.
Although this book uses Java as its illustrative programming language, many
of the ideas may be translated directly into other OO languages such as C++, C#
and others. Throughout this book, programming in Java is demonstrated using the
BlueJ Integrated Development Environment (IDE). BlueJ is a well-established IDE
for learning BlueJ and is widely used in schools and Universities. Eclipse is the
closest product to an industry standard for the development of Java, but it is often
found too complex for the task of teaching and learning.
explore broader topics surrounding coding. This with some prior knowledge may
opt to skip some of the early chapters. That does not impact the usefulness of this
book in terms of learning to code in Java.
Chapter 1 starts with an overview of what programming and coding is all about. It
includes some useful historical perspective on the development of programming
languages and the core ideas that underpin all programming languages. It intro-
duces the idea of a computing machine and concepts such as a compiler. This
section is helpful to those who have no prior experience of computing as it helps
subsequent understanding of some of the core coding processes and terminology.
The chapter then continues to discuss how the need for OOP arose in the period
from the end of the 1970s to the present day, and a discussion of why it is
considered important to help us solve modern-day programming problems.
Chapter 2 provides a short introduction to programming in Java using BlueJ. It is
intended to provide just enough knowledge and skills to create and execute a
single-class Java program under BlueJ. This is significant as it then facilitates
discussion of the core principles of procedural and structured programming, such as
loops and conditional statements. Those with prior experience of coding using
languages such as C and Python may opt to skip this chapter, as they would
undoubtedly be familiar with much of the content. I chose to organise the book this
way as the basic procedural and structured coding constructions are common to
almost all programming (or at least those that owe their syntactic ancestry to C), and
getting these constructions understood at this stage allows for a more specific focus
later on the principles of OO.
Chapter 3 gets into the details of what OO really is and how it can be applied to
solve modern programming challenges. We start with a discussion of what classes
and objects are, and how the construction and execution of an OO program parallels
the way that human organisations such as a large office operate. Such analogies are
invaluable in appreciating the true benefits of the OO paradigm. In this chapter, we
develop a set of small multi-class Java applications and consider the cornerstone
issues in OO design of class cohesion and coupling.
Chapter 4 considers a range of Java library objects and packages such as the
String and the ArrayList, and introduces the idea of the Application Pro-
gramming Interface (API). This enables the reader to start building more complex
applications involving simple linear collections of objects. These ideas are devel-
oped using a set of simple programs that can be enhanced in many different ways as
an exercise for the reader.
Chapter 5 delves further into the OO paradigm and considers how OO design
forms an essential part of producing a useful solution to a problem. The chapter
introduces the idea of class polymorphism (super and sub-classes) and how this can
be used to create a program with a structure that more closely mirrors an underlying
viii Preface
domain. The chapter also looks further into the idea of selecting classes that are
suited to solving specific problem and so also has elements of software engineering
principles and practice.
Chapter 6 considers what to do when code encounters an error condition.
Software systems are not immune to errors either at the coding or at the run time
phases, and modern software systems need to be built in a robust manner so that
they behave in a predictable manner when something goes wrong. The exception
handling mechanism is introduced, along with steps on laying out a program to
assist in debugging it. This chapter also considers practical measures that are
adopted in defensive coding.
Chapter 7 digs deeper into the work of arrays and collections, notably fixed
length arrays, the HashMap and HashSet, and shows how different collection
types can be used to effectively model different real-world collections of data. This
chapter also includes some background on the underlying ideas for these collection
types, such as the hash table.
Chapter 8 provides an introduction to building a Graphical User Interface
(GUI) using Swing. Although some may consider Swing a relatively old library for
the development of a GUI, the key ideas are relevant across a range of other
libraries such as JavaFX, and Swing forms more of a core element of the Java
landscape. The development of GUIs is a large topic in its own right, so this chapter
can only ever serve as an introduction. In this chapter, we also consider the concept
of a design pattern, specifically the idea of Model View Controller (MVC) archi-
tecture, and how a Java application can be constructed in a well-recognisable design
configuration.
In the final Chap. 9, two complete applications are presented, from conceptual
design to implementation to help cement the ideas presented in the previous
chapters. One is a text-based application with no Graphical User Interface (GUI).
The other is a small GUI-based application to give a sense of how to build a GUI on
top of an underlying application.
All the code examples used in this book and the two example projects described
in Chap. 9 are available as on-line resource accompanying this book.
It is my hope that this book will inspire the reader to learn more about the world
of OO and coding. As such, it represents the start of a learning journey. As with all
endeavours, clarity will improve with time and effort. Few will write an
award-winning book at their first attempt. Few artists will paint their defining
masterpiece at the outset of their career. Programming is no exception and your
skills will improve with effort, time, reflection and experience. But every learning
journey has to start somewhere. For many, the story starts with the codebreakers of
Bletchley Park in the United Kingdom during WWII, but we shall start our story in
early nineteenth-century France …
ix
x Contents
xiii
The Origins of Programming
1
In this first chapter we explore what a programming language is, and something of
the history of their development leading up to the Java language. This will help us
understand some of the most basic terminology used in the process of creating
programs. The history of programming, and computing in general, does not have a
universally agreed timeline and shared sense of significance of contributions.
Nonetheless, computer science has progressed and innovated to bring us a world
that we may scarcely consider without its plurality of systems with software, data
and programs at their core.
Whereas the digital electronic computer is a 20th century concept, the idea of digital
control goes back much further. Digital control simply refers to the idea of a system
controlled by a sequence of instructions that are either 1 or 0, “on” or “off”. One of
earliest notable examples of such a system that used stored digital instructions was
the Jacquard weaving loom. In the early 1800s, Joseph-Marie Jacquard (1752–
1834) developed an automated weaving loom using a series of punched paper cards
to control the head of the loom to raise and lower different threads to permit a wide
range of fabric designs to be mass produced. Any design could be expressed by the
set of punched cards that were fed to the machine.
Sources https://commons.wikimedia.org/wiki/File:Book_Illustration,_Jacquard_Weaving_and_Designing,_
Falcon_Loom_of_1728,_Figure_12,_1895_(CH_68766143).jpg (public domain) https://commons.wikim
edia.org/wiki/File:Jacquard.loom.cards.jpg (public domain)
24 23 22 21 20
16 8 4 2 1
1 0 0 1 1
The machine consisted of a paper tape of infinite length that enabled read and
write operations to be performed. Depending on the symbol observed on the tape,
the tape can be made to move forwards and backwards. Turing is actually
describing the underlying requirements of a modern computer and a programming
language—a feat given that in 1936 the technologies needed to realise such devices
barely existed.
Turing and others would later realise electronic implementations of Turing
machines using electronic valve and later transistor technology, allowing the
realisation of general purpose “electronic digital computers”. Turing is also widely
credited for popularising the term “Artificial Intelligence” as he believed that one
day such digital computers would rival humans for computing and analytical
ability.
The onset of World War 2 brought opportunities for Turing and others in the
form of the Allied effort to decipher Nazi Germany’s secretive Enigma codes,
particularly in respect of minimising shipping losses to U-boats on the North
Atlantic supply route (the “Battle of the Atlantic”). U-boat command used Enigma
machines, a type of modified electronic typewriter, to convert plain text messages to
cipher text that was then broadcast by radio to the U-boats. Recovering the original
4 1 The Origins of Programming
plain text required another Enigma machine with identical settings to the original.
The design was such that there were billions of combinations of settings and it was
statistically unlikely they could be discovered by chance. A group of scientists,
including Turing, worked at the Bletchley Park site in England to build a range of
machines, such as Turing’s Bombe and later the Colossus device, that could sift
through millions of settings in just a few hours to find the correct one.
This was the start of the era of cryptoanalysis. Colossus is regarded by many as
the world’s first semi programmable electronic computer, and a faithful recreation
of the machine can be viewed today at the UK’s National Machine of Computing at
Bletchley Park.
The post-war years were less kind on Turing, with events leading to his suicide
in 1954. But the development of electronic computers continued apace in the UK
and the US, with the development of machines such as the Manchester Mk 1 (UK,
1949) and ENIAC (US, 1945). 1952 heralded the arrival of the Ferranti Mk 1, the
world’s first commercially available general-purpose computer.
By the 1950s, computer hardware was a reality. But as with all technologies, the
question arose of what it should be used for. ENIAC was initially developed to
produce artillery firing tables for the US army—a repetitive and time-consuming
task suited to a machine. The Manchester Mark 1 was used for tasks including
searching for prime numbers and investigating the Riemann hypothesis.
The issue was the relatively low amounts of computing power combined with
the fact that there was only a small group of experts who truly understood how to
program the machines. Initially machines were programmed using binary and very
near binary “assembly languages” supported by mnemonic aids. Creating programs
at such a low level required a great deal of time and intellect.
1.3 The Origin of Programming Languages 5
Compiled languages made the translation one-time and then stored the resulting
machine code for execution many times over. Interpreted languages made the
translation “on the fly” for immediate use. This distinction is still very much in
evidence today, with languages such as C and Java belonging to the compiled
group, and scripting languages such as JavaScript and PHP belonging to the
interpreted group. The interpreted group has become particularly significant in the
world of web computing.
1954 saw the development of FORTRAN by a team lead by John Backus at
IBM. This was a very significant innovation as FORTRAN was the first widely
adopted general purpose programming language and it still exists today, although it
has long since fallen from wide use. Other notable languages include COBOL (for
business related programming tasks) (Grace Hopper, 1959) and LISP (for symbolic
computing) (Russell, Hart and Levin, 1958). Nearly all these early languages are
now a matter of historical note, but 1972 brought a significant milestone with the
arrival of C (Bell Labs, Dennis Ritchie). C was significant as it brought a consistent
syntax, provided a range of high and low level instructions and operations, was
designed to encourage cross platform support, was (and still is) the subject of
international standardisation. C was used to write the UNIX 4 operating system
(still very much in use today). C is also significant in that many contemporary
programming languages (including Java) owe their syntactic history to it. C has also
6 1 The Origins of Programming
seen a reboot in the form of the object oriented C++. Now a wide range of people
could write programs using high-level abstraction rather than needing to understand
the detailed internal operation of the host computer.
The challenge here is not technological, it’s human. By the 1970s, a significant
number of software development projects were failing (i.e. required substantial or
complete write-down of their costs due to failure to deliver a working product) as
they were becoming too complex for teams to develop and manage using the
programming languages and techniques available. This period saw the birth of
software engineering as an academic discipline to try to counter this. The problem
lay in the fact that they kind of data employed by programming languages was
based in mathematical and fundamental terms like characters, integers and pointers.
These are not the atomic elements that were needed to build something like a
graphic computer game, or a word processor. Humans don’t think of most problem
domains in atomic terms. We think of them in terms of entities like “Player”,
“Paragraph” and a “Spell checker” and so on.
So there was a basic mismatch between the programming concepts on offer and
the problem domains that developers wanted to address. Furthermore, a program
written for one computer would not necessarily execute on another. By the 1980s
there was a proliferation of competing brands of computer, with little or no inter-
operability between them.
In 1967, the Simula language (Dahl, Nygaard) was the first Object Oriented
(OO) language. In 1980, Smalltalk-80 (Kay, Ingalls and Goldberg) was released,
drawing heavily on Simula for inspiration. These languages were developed part in
response to the challenges faced by ever expanding code base sizes and part by the
1.4 The Object Oriented Revolution 7
The Java language had its 1990s origins in a language called Oak intended for use
in interactive television set top boxes. Initially developed by James Gosling,
Michael Sheridan and Patrick Naughton, the aim was to produce an OO language to
build applications that could run on any interactive television, regardless of the
underlying hardware that any individual unit had. Although Oak was not suc-
cessful, it developed further into the Java 1.0 released by Sun Microsystems
promising “Write Once, Run Anywhere” (WORA) code. This was a major inno-
vation that arrived at a time where there was a demand for lower cost development
capable of producing applications that could run in a range of machines, and on the
fast-paced range of new web browsers that were emerging.
At the heart of this innovation was the idea of the Java Virtual Machine (JVM).
The JVM was an abstract implementation of a general-purpose microprocessor,
with a corresponding low-level byte code language specification. Although this
abstract microprocessor did not actually exist, it was similar in design to the vast
range of commercial microprocessors available, so the “last leg” translation of the
JVM byte code to actual machine code for a specific microprocessor was a simple,
speedy and low-cost task. Any Java compiler just needed to translate the Java
source code to JVM bytecode, and the code could then execute on any device
equipped with a JVM.
8 1 The Origins of Programming
Software developers soon provided JVMs for all popular platforms. This idea of
a virtual machine or “sand box” has been widely adopted in other software engi-
neering applications and frameworks as it offers flexibility with very little loss of
efficiency. It’s how Macs can pretend to be PCs, and how systems can be built with
components written in different languages.
Java received a particular boost resulting from the emergence of mobile com-
puting platforms. Manufacturers of smartphone and tablet devices faced the same
challenges as the earlier developers of interactive television set top boxes—the need
to run the same code on different underlying devices. So, Java was a natural choice
as the implementation language for the Android operating system (Google, 2007)
and its applications.
Java has continued to evolve and was acquired by the Oracle Corporation fol-
lowing their acquisition of Sun Microsystems in 2010 and continues to be free to
use. It is available as a run time only package (JRE) and as a development toolkit
(JDK). As at 2018, Java is in version SE 11 and there is an Enterprise Edition EE 8
(known as Jakarta EE). A separate version of the JVM (Android Runtime and,
before that, Dalvik) and a branch of the language exist for mobile development.
So, Java is a modern OO compiled language that relies on the virtual JVM for
execution of its byte code. It owes its syntactic ancestry to C and draws on
Smalltalk-80 for inspiration. It is, and has always been, free to use and embodies the
“WORA” principle.
To create Java programs, you will need to download some tools. They can all be
downloaded from reputable sources for free:
• The Java Development Kit (JDK): choose the latest version appropriate for
your development machine. All major platforms are supported. For this book,
we assume you are using a desktop development environment rather than a
mobile platform. The JDK contains the Java compiler and several other tools to
help you develop, debug and document your work. Ensure that your download
the Java Development Kit (JDK) rather than the Run Time Environment
(JRE)—your computer likely has the latter installed already.
• An Integrated Development Environment (IDE): this is a toolset to help you
edit and manage the code that you produce. There are many popular IDEs
available. The professional market leaders are Eclipse and Netbeans which are
rich in features, but overly complex for learning and teaching. This book, and
1.6 Tools of the Trade 9
many others, uses the BlueJ IDE that can be freely downloaded for all major
platforms. BlueJ was developed specifically for teaching and learning and offers
just the right set of features to enable you to develop code easily.
All the code examples used in this book are available freely in the on-line package
that accompanies this book.
With that knowledge, it’s time to start writing some programs.
Procedural Programming Basics
in Java 2
In Chap. 1 we learned some of the key concepts and terminology around pro-
gramming. In this chapter it’s time to jump in and start to create our first programs
in Java using the BlueJ Integrated Development Environment (IDE). The first
programs that we shall consider will be necessarily simple., and are intended to
introduce the reader to the basic workflow necessary to write, compile, execute and
debug a Java program, and to the elements of procedural programming that con-
stitute core programming knowledge applicable not just to Java, but a wide range of
other procedural and Object Oriented (OO) languages. At this stage, we will not
focus on the OO elements of Java—that subject is explored in depth Chap. 3.
Instead we shall master some basic procedural programming elements that make up
an essential component of the broader landscape of coding.
The traditional first program in many textbooks is “Hello World”, so we shall start
there. To create a Java program using BlueJ, first start the BlueJ program and under
the Project menu tab select “New Project”. Give the project a name. The project
name will be used to create a directory on your PC/Mac. A BlueJ project is
effectively a set of files contained within a directory. That set of files will ultimately
consist of source code (.java files), compiled Java byte code (.class files) and
other files that, taken together, constitute your Java application. Once you have
done that you should have a screen that looks like this:
The precise appearance and layout may vary slightly depending on which version
of BlueJ you have installed on your machine. There are some key controls to
appreciate:
• Menu bar: where you will find key operations such as saving and opening
projects.
• Central panel: the large panel on the top right where organisational chunks of
code will ultimately be represented.
• Workbench: the panel at the bottom of the screen where you will be able to
monitor your completed program as it executes.
• The left-hand side key controls panel: where you will find buttons to create a
“New class” and “compile” your programs.
So, we start by creating a class. To do this, press the “New Class” button on the
left-hand panel. Your will be asked for a Class Name. Provide an alphanumeric
name. By convention, Java classes start with a capital (upper case) letter. They
should not start with a number, and there should not be any spaces in the name.
Many experienced programmers use the “camel caps” style of naming where capital
letters are used at the start of key words e.g. “MyFirstClass”, or “My1st-
Class”. The Class Type should be left as “Class” and this is the BlueJ default. The
BlueJ source code editor will open. Once you have done this an orange box will
appear in the central panel with a hatched marking through it.
Note that BlueJ does produce some sample code when a class is created. This
sample code is not particularly useful, so we will delete most of it. Just leave the
skeleton of the class definition as shown below:
/**
* Write a description of class MyFirstClass here.
*
* @author (your name)
* @version (a version number or a date)
*/
public class MyFirstClass
{
}
14 2 Procedural Programming Basics in Java
Note carefully the type of curly brackets (often called “braces”) that are used in
the definition of the class. We will be making a great deal of use of two types of
brackets:
Now we will add some useful source code to make our first working program:
/**
* Write a description of class MyFirstClass here.
*
* @author Kingsley Sage
* @version 1.0
*/
Multi line comments: starting with the symbol pair/* and ending with the
symbol pair */. Any text can appear between those pairs and can run over
many lines. Here we have used a multi-line comment to provide information
about who has written the program.
Single line comments: start with the symbol pair//. Any text after this
symbol pair up to the end of the line are treated as text comments.
The final thing to note is the semi-colon at the end of a line of code. This marks
the end of a complete statement of Java code, a bit like the full stop that we put at
the end of a sentence when writing in English.
If you have written everything correctly, you can now press the Compile button
and the source code will be translated into Java byte code. If you have made an
error, the compiler will produce an error message (a syntax error) and you will need
to fix the problem and try to compile again. When the source code is free from
syntax errors and compiled, you will see the message “Class compiled—no syntax
errors”. Now it’s time to run our first program. To run the program, you will need to
create an instance of MyFirstClass. To do this, right click the solid orange box
on the central panel and select new MyFirstClass. You can then give the
instance of the class a name. Just use the default name for now. Once you have
done that you a red box will appear on the BlueJ workbench.
16 2 Procedural Programming Basics in Java
The red box is a Java object built from the MyFirstClass class definition.
Now that we have created a Java object, we have a functioning program working
in the memory of our machine. Now all we need to do is to tell the object what we
want to do. The object only has one thing that it can do. We need to invoke the
myFirstMethod() chunk of code. To do this, we right click on the object on the
workbench and select the void myFirstMethod(). This will cause the console
window to open and your message will appear. Well done, you have created and
executed your first Java program!
Now that we have looked at the basic workflow for creating and executing a Java
program, we can now delve deeper into the basics of the procedural aspects of
programming. Our first program just displayed some text on the console window.
More useful programs will do rather more, and in particular, will allow us to store
and manipulate data. By data, we mean any type of information including, although
not limited to, numeric data, logical data, text and objects. We can view the purpose
of a program as a means of doing some useful work on data.
2.2 Primitive Data Types 17
Data is stored in the form or variables. We start by considering the most basic
kind of data, called “primitive data”. The term “primitive” here is used to distinguish
between data that is atomic (in the sense that it cannot be broken down into any
smaller useful units) and that does not have the status of an object, and objects. We
consider much more about objects in Chap. 3. Java has 8 primitive types in total, 6
for numeric data, 1 for single character data and 1 for logical or boolean
expressions. Each is characterised by a range of values that a variable of that type can
hold and the number of bytes in memory that it occupies. The 8 primitive types are:
The choice of integer and floating-point types simply reflects the range of values
that each type can accommodate. In Java, all numeric types are signed, meaning
that they can take on positive and negative values (there is no distinction between
signed and unsigned types as there is in languages such as C).
Primitive data is stored in the form of variables. To use a variable we must declare
it first. This ensures that the compiler knows how much memory to set aside to store
each variable. Java is a strongly typed language, meaning that we must always state
what kind of data something is before we can use it. This declaration happens only
once. We can store values in the variables and manipulate those values as our needs
dictate. Note that the primitive data type keywords start with lower case letters to
remind us that they do not have the status of a class. Primitive variables also have
default values (0 for the numeric ones and false for boolean ones).
Here are some example of primitive variables being declared and then given
some values:
int x;
boolean y;
double x1;
float x2;
char myLetter;
x = 3;
y = true;
x1 = 1.5;
x2 = 6.5f;
myLetter = 'x';
18 2 Procedural Programming Basics in Java
int x = 3;
boolean y = false;
You can manipulate variable values using an expression. Here are some
examples of valid expressions using a range of mathematical operators.
x = 5;
x = x + 2; // Add 2 to the value of x
int z = 2;
x = z + 2;
x = x * 6; // Multiply x by 6
x = x / 2; // Integer divide x by 2
y = false;
x2 = x2 / 5.2f;
For an expression, the right-hand side of the equals sign is evaluated and used to
set the variable on the left-hand side. Many programmers do not care for the
x = x + 2 way of writing “add two to 2” as it resembles an impossible equation.
But in practice it does not matter—just use a style that works for you. Note that all
expressions end with the semi-colon. You can also see the quite popular “side style”
of commenting.
Variables must be declared before they are used. Failure to do so will result in
the compiler reporting an error. However, it is important to understand that where
we place the declarations of variables determines their “ownership” under the “rules
of scope”. Variables can be declared within the scope of a class, or within an
individual method, or indeed part of a method. The first two cases are relevant at
this stage.
A variable that is declared within the class definition, but outside the scope of
any method within that class is referred to (interchangeably) as an instance
variable, a class attribute, and a field of a class. However, the term “class
variable” is not appropriate. We shall see in a later chapter that class variable
refers to something different. Such a variable is accessible at any point in the
source code within that class. We say that it has scope of the class.
2.2 Primitive Data Types 19
Now that we know about the primitive data types, we turn our attention to the
fundamental concepts of procedural programming. Java is an Object Oriented lan-
guage, but it also has the procedural programming concepts as its core, as do many
other programming languages. The term “procedural programming” is not entirely
well-defined, and is contentious for some academics. In the broadest terms, it refers
to a style of programming where a problem is broken down in a set of smaller
procedures, also called functions and, in Java’s case, methods. But the term is also
used to include a set of programming code constructions (structured programming)
that deliver the minimal requirements of a general-purpose programming language.
20 2 Procedural Programming Basics in Java
These constructions themselves arise from the pioneering work of Alan Turing and
his abstract Turing machine mentioned in Chap. 1.
Rather than dwell on the detailed mathematical treatments that many purists
attach to programming paradigms, we will instead describe what is required of a
general-purpose programming language in rather more everyday terms. Suffice to
say that it can be demonstrated that a programming language is general purpose (i.e.
can perform any computable calculation) provided it exhibits 3 characteristics:
• Sequence: processes one instruction after another, until all instructions have
been executed.
• Alternation (also called selection): selects one execution path from a set of
alternatives.
• Repetition (also called iteration): repeatedly executes some code whilst some
condition persists.
2.4 Sequence
The notion of sequence from structured programming is simply the idea that
instructions are executed in a given reliable order i.e. from start to finish. It is up to
the programmer to determine what the correct sequence is to achieve the intended
result. This idea contrasts with the concept of declarative programming, where the
user simply states what their requirements are, and the order of these declarative
statements is unimportant.
The only additional aspect of sequence comes from the procedural programming
paradigm, that adds the notion of a “call stack”. This reflects the idea that a larger
program can be broken down int smaller pieces—methods in the case of Java. One
method can then call upon another. This is also the concept behind the program
design philosophy of “task decomposition” where a large task is broken down into a
set of smaller tasks, until each task is sufficiently simple to be understood and
implemented.
When a method calls upon another method, execution of the calling method is
parked whilst the called method is executed. Once the called method has completed
execution, control passes back to the calling method. At any stage of the execution
of the program, there is a stack of calling methods where the order of the stack is
determined by the sequence in which the method calls took place. The following
example will help you understand this point:
2.4 Sequence 21
We shall see later that as well as passing control from one method to another, we
can pass and return values as well.
2.5 Alternation
Here we see that we are required to evaluate a question. This question has a
boolean nature in that the answer can only be either “yes” (true) or “no”
(false). If the answer to question 1 is true, we perform action 1. If the answer to
question 1 is false, we instead evaluate question 2. If the answer to question 2 is
false, we perform action 2. Note that is question 1 was true, we never evaluated
question 2, so the two questions here have answers that are mutually exclusive. We
could have carried on extending the chain of questions as long as we liked. But
once we find a question that evaluates to true, the decision-making process is
complete.
Alternation in Java (as in many other languages) is delivered using if and
switch statements. They are both equally expressive in that anything that is
written using switch can be re-written using if. There are situations where it is
aesthetically more pleasing to use one over another, but this is a choice for the
programmer.
2.5 Alternation 23
if (<test-condition-1>)
{
// Body of statement
}
else if (<test-condition-2>)
{
// Body of statement
}
else
{
// Body of statement
}
Note carefully the use of brackets here. The test conditions are contained in
round brackets, and the body of the statement (the code that is to be executed if the
test condition is true) is contained in braces. Note also that there is no semi-colon at
the end of the test condition code.
We can have as many else … if sections as we like, or none at all. We also
have the option of having a final else section for an action to be performed when
no other test expression in the statement overall evaluated as true. The test
conditions are evaluated as either true or false. Such expressions will make use
of Java operators. An operator is just a name for a symbol that performs a specific
operation on one, two or three operands and returns a result. For example, + the
addition symbol is an operator in the sense that 2 + 3 has two operands and returns
the result 5. For the if statement, we will use operators that return values of true or
false. The commonplace ones in this application are:
Operator Meaning
== Is equal to
!= Is not equal to
> Is greater than
>= Is greater than or equal to
< Is less than
<= Is less than or equal to
&& Logical AND
|| Logical OR
The logical operators are used to combine tests together to make more complex
test conditions that depend on two or more pieces of data. Logical AND only
evaluates as true if all sub-conditions evaluate as true. Logical OR evaluates as true
if any of the sub-conditions evaluates as true.
Here are some example if statements:
int a = 1;
int b = 2;
int c = 3;
boolean x = true;
// Example 1
if (a == 1)
{
System.out.println("Get here if a has the value 1");
}
// Example 2
if (a != 1)
{
System.out.println("a does not have the value 1");
}
// Example 3
if (a > 2)
{
System.out.println("a has a value greater than 2");
}
else
{
System.out.println("Otherwise we get here!");
}
// Example 4
if ((a==1) && (x == true))
{
System.out.println("a is 1 AND x is true");
}
can use logical operators to build up more complex decision making criteria. The
default case functions like a final else option for an if statement and provides
a reliable course of execution when no other valid switch option is found. Here is
an example of a switch statement:
int x = 3;
switch(x)
{
case 1:
System.out.println("Option if x has the value 1");
break;
case 2:
System.out.println("Option if x has the value 2");
break;
case 3:
System.out.println("Option if x has the value 3");
break;
default:
System.out.println("x has some other value");
break;
}
2.6 Repetition
• The <initial-state>: the initial value of some controlling variable that we will be
set one-time prior to the first evaluation of the test condition to some initial
value.
• The <test-condition>: a boolean test that will determine whether the loop
continues to execute.,
• The <action>: a short section of code that is executed each time the body of the
loop has finished executing.
The body of the loop is any amount of valid Java code (including, although not
limited to, sequence, alternation and other examples of repetition).
The for loop is most easily understood using a code example together with a
specific companion flowchart as shown below:
Note here that the loop variable x was actually declared within the scope of the
for loop. It therefore has the scope of just that loop, and does not exist outside
the scope of the loop. This is a common practice in coding. It does not have to be
the case, you could declare the loop variable as a method variable, or use an
instance variable. It simply depends on whether you will need access to the loop
2.6 Repetition 27
variable once the loop has completed execution. In this example, the result of
calling this method would be to print out the values 0 to 9 inclusive on the console
window.
A common error for first time coders is to mistakenly add a semi-colon to the
end of the for loop round brackets like this:
This would not actually be syntactically valid. The semi-colon is the smallest
body of a statement you can have in Java. So here, the body is assumed to be blank
and nothing is executed in respect of it. The braces are then entered, and we try to
access the value of x, but x is now out of scope. It does not matter that there are
excess braces.
As an alternative to the for lop construction, we can use the while loop. The
general form of the while loop is simpler than the for loop:
while (<test-condition>)
{
// Body of loop
}
Here we see that there is just a simple boolean test condition. If the test
condition evaluates as true, we enter the body of the loop. Once the body of the
loop has completed executing, the test condition is evaluated again. The body of the
loop is repeatedly executed until the test condition evaluates as false.
As before, the while loop is most easily understood using a code example
together with a specific companion flowchart as shown below:
while (q > 0)
{
System.out.println(q);
q++;
}
}
28 2 Procedural Programming Basics in Java
Note that in this example, the declaration and initialisation of the loop variable q
does not form a part of the while loop itself. It is also important to realise that it is
the programmer’s responsibility to ensure that something within the body of the
while loop affects the loop variable so that the loop can ultimately exit. In this
case it’s the q++; statement. Failure to do so would result in the loop executing
forever (an “infinite loop”). If this eventuality, the JVM will need to be reset
manually. The BlueJ IDE has a control for this—depending on which version you
have installed it may be a “barbers’ pole” or a circular arrow. In either case, just
right click the control and reset the JVM.
It is easy to see that any code written in the form of a for loop can be re-written
in the form of a while loop. The only real advantage of the while loop is that the
for loop action is limited to just one simple statement of code, typically
incrementing/decrementing the loop variable. With the while loop, you can
incorporate as much action style code into the body of the loop as is required to
solve a problem.
The final form of repetition for now is the rather less used do … while
loop. This loop takes the following general form:
do
{
// Body of loop
}
while (<test-condition>)
Once again, the do .. while loop is most easily understood using a code
example together with a specific companion flowchart as shown below:
do
{
System.out.println(q);
q++;
}
while (q > 0);
}
2.6 Repetition 29
Here we see that the test condition is positioned after the body of the loop. This
means that the boolean test condition is not evaluated until after the body of the
loop has been executed for the first time. This means that the body of the loop is
guaranteed to be executed at least once, even if the test condition should turn out to
be false. This loop is often used in the context of user interfaces and user
interaction, where a user needs to input some value before the value can be
determined as acceptable or not. If the value is not within some acceptable bounds,
the loop runs again to invite the user to try again.
A maths funcƟon of the form y = f(x) A black box model with input(s) and an output
30 2 Procedural Programming Basics in Java
The maths way is helpful is it helps us understand the origins of the syntax for
calling methods. The black box model is helpful as it reminds us of the procedural
programming paradigm, and the fact that the process inside the method is encap-
sulated within that method.
All methods in Java must belong to a class—they cannot exist in isolation. They
can be defined anywhere inside their host class. A method definition takes the
general form:
In summary:
not permitted to exhibit such “side effects” but in Java methods may produce such
side effects. This is very useful in using methods to support the task decomposition
coding principle.
Here is an example method definition that takes two formal parameters and
produces an output:
To use this method, we would use some calling code (within another method)
like this:
int d;
d = addTwoNumbers(3,4);
System.out.println(d);
Note that as this method is void, there is no return keyword. But the method
does exhibit side effects, displaying messages on the console.
We are now able to start to create some more substantive and interesting programs.
To consolidate the principles learned this far, we now consider a simple and
complete game. The game is a version of the “guess the number I am thinking of”.
The rules of the game are:
• The program will generate a random number in the range 1–99 (inclusive).
• The user will have up to 10 attempts to guess the number.
• If the number guessed is too low, a “too low” message is displayed on console.
• If the number guess is too high, a “too high” message is displayed on console.
• The game ends if the user guesses correctly, or has had 10 attempts.
The only code that needs to be added to our existing knowledge base is the
means to generate a random number and the means to allow the user to enter a
number at the console. This functionality is achieved here using the Java library
Random and Scanner classes respectively. Don’t worry if these are the only two
aspects of the code example given here that you are unsure about—as library
classes, their operation will become clearer in later chapters. Here is the complete
code listing for the guessing game:
2.8 Bringing It All Together 33
import java.util.*;
/**
* A simple guessing game.
*
* @author Kingsley Sage
* @version 1.0
*/
int numGuesses;
boolean gameOver = false;
int userGuess;
numGuesses = 0;
gameOver = true;
}
else if (answer == userGuess)
{
gameOver = true;
}
}
}
To play the game, just create an instance of the GuessingGame class in BlueJ
and call the method playGame(). This code has been created to demonstrate the
ideas that have been discussed in this chapter. There are several points worthy of
note:
• The import statement at the top draws in additional Java functionality, in this
case the library (a part of the extended Java code landscape) that includes the
Random and Scanner classes.
• Only the playGame() method is public. That is because it is the only
method that is intended for the user to call directly. The playGame() method
calls upon two other methods, but they are declared private as they are only
intended to be called within the class definition, and have no utility beyond the
class definition.
• The Java Random number generator generates values in the range 0 to an upper
limit (exclusive of the value of the upper limit). We want a value in the range 1
to 99 inclusively, so we add 1 to the number generated.
2.8 Bringing It All Together 35
This game should serve to illustrate many of the core programming concepts in
action. Now that you understand those basics, it is time to expand our thinking to
embrace the Object Oriented design philosophy fully. Out code up to this point has
been based upon a single class. Although that was useful for understanding coding
basics, it has not made any use of the much broader power of OO, and that is where
we turn our attention for the next chapter.
Getting into Object Oriented
Programming 3
One of the easiest ways of getting insight into the OO concept is to consider how
things in our everyday experience are organised. Many of us will work in offices,
play team sports, go shopping and make plans to go on holiday. These tasks seem
normal to us and very much in the domain of our experience. Although you might
not spend much time thinking about them, all these activities are underpinned by
structure. Some of this is social structure, such as the management structures in our
workplace, or the organisation of sports teams or the interactions between a com-
plex set of businesses and people that allow us to book holidays in distant places. In
fact, part of the human experience is that we readily organise into social structures
to achieve tasks collectively. OO design and coding allows us to reflect that
structure when we produce a piece of software. We can understand the principles of
OO design just by reflecting on these social structures.
Consider a modern office that delivers some service. It doesn’t matter what that
service is, but for the sake of this example, let’s imagine that the service is IT telephone
support. Let’s say that the office has several different categories of employees:
• Telephone operator: who takes the calls and routes them to first line support
staff.
• First line supports staff: who deal with the most straightforward problems and
determine whether more complex problems need to be referred to second line
support staff.
• Second line support staff: who have deep technical expertise and produce
advice for more complex cases.
• IT support staff: who keep the internal desktop systems and the telephones
running in an orderly manner.
• Team leaders: who manage staff and ensure that their working conditions are
appropriate.
• HR staff: who ensure that everybody gets paid the correct amount for the work
that they do.
• Cleaners: who ensure that the working environment is kept up to the required
standard.
We could go on, but you get the idea. There are many different types of staff, and
they all have a particular specialism and expertise, and they are all necessary for the
efficient and effective conduct of the business. The organisation seems reasonable in
our experience. But how does the business actually operate? To help us understand
that, we could draw a diagram.
communicate and interact with the HR staff. OK, there may be other links, but the
diagram is capturing the essence of the interactions between the organisational units
of the business. It’s just the way the business is supposed to work. From this simple
model, we can extract some useful general observations about what makes this
business work:
• Each organisational unit has a single specific purpose. That purpose can be
clearly and simply expressed.
• Each organisational unit has a need for information. Some of it is needed solely
for its own use, and some it may need to share with other organisational units.
• Each organisational unit has a need to interact with one or more other units in
order to achieve some collective task.
These same principles underpin the concept of OO design. We just need to apply
particular terms to each of these ideas.
In the previous chapter, we saw the term “class”, but did not give any consideration
to its meaning. Now we can establish a useful definition:
Minimally coupled: the class limits its interactions with other classes to only
those that are really necessary for it to do what it is designed to do.
Encapsulation: the class keeps information necessary to its internal operation
private and does not expose it to other classes, and only makes public the
information necessary for it to interact with other classes in the intended
manner.
Now we are at a point where we can start to build a more formal definition of a class
in Java. In a general sense, any “thing”, “entity” or “class” (these words are often
used interchangeably in many books and articles) can be defined by two key
components:
3.3 The Anatomy of a Class 41
Many formal OO design tools use techniques that analyse free text to derive state
and behaviour by virtue of their presence as nouns (descriptive words) and verbs
(action words). In a general sense everything can be defined this way. For example,
take the following piece of text:
A sports car can be one of a variety of colours, with an engine power between
100 HP and 200 HP. It can be a convertible or a regular model. The car has a
button that starts the engine and a parking brake. When the parking brake is
released and you press the accelerator, it drives in the direction determined by
the transmission setting.
We can break the car entity into state and behaviour as follows:
State Behaviour
Colour (text) Press the start button
Engine power (number of BHP) Press the accelerator
Convertible? (yes/no)
Parking brake (on/off)
You can apply this kind of textual analysis to anything, from the telephone
operator in our office example, to a car, to a football player character in a video
game, a shopping cart in an e-commerce example and so on. It’s a universal concept
of defining what we mean by a “thing”. And that gives us a template that we can use
to define a class in an OO language. So let’s take the car example and create a Java
class that can model the car “entity” for us:
42 3 Getting into Object Oriented Programming
/**
* A simple class to represent a sports car
*
* @author Kingsley Sage
* @version 1.0
*/
So a Java class defines state as a set of variables that represents all the useful data
needed, and the behaviour as a set of functions that represent all the things that it
can do. For readers with previous procedural programming experience outside of
OO, it is worth just making the distinction between a function (a block of code
intended to be used repeatedly) and a method (a member function or “method” that
is a function that belongs to a specific class). So our Java class has data and
methods. In fact, Java is a pure OO language in that all code must belong to a class.
This differentiates it from other OO languages, notably C++ where code can exist
independently of classes or be structured in the form of classes, and any mix of the
two approaches. So C++ can have both functions and methods. All functions in
Java are necessarily methods as they must belong to a class, so we shall always use
the term “method” from now onwards.
Now that we have a class, we can use BlueJ to bring it to life. At this point we
need to make a very important distinction between the terms “class” and “object”.
When we create the Car class in BlueJ, we do so using the integrated text editor.
The class appears as an orange rectangle. When we right click the class and select
the “new” option, we get a red square on the BlueJ workbench. That red square is
an object. So we have an important distinction:
A class is a template for some useful entity defined by its state and behaviour.
It is not a specific example of that entity—it is blueprint for all instances of
that entity. At the code level, there is only ever one definition of any class.
An object is a specific incarnation or instance of a class. It is a concrete
occurrence of that class. There can be many instances of a class in existence
at the same time.
So we have one Car class definition, and using BlueJ, we can make as make
instances of that Car as we like. Just like in the office example, we can have a job
description for a telephone operator and then use that description to hire as many
actual concrete telephone operators as we need for the business to function
properly.
So in BlueJ, we can use the Car class to make two objects car1 and car2. The
objects can have any name we like provided they are unique. Note that by con-
vention, only classes begin with an uppercase letter. Variables, methods (with one
exception we shall note shortly) and objects (with an exception we shall note later)
start with a lower-case letter. Java does not enforce this convention on us, but we
should adopt this convention as it is good programming practice.
44 3 Getting into Object Oriented Programming
Once we have created these two objects, we can inspect them using the BlueJ
inspector. To use the inspector, just right click on the object on the BlueJ work-
bench and select “inspect”:
3.3 The Anatomy of a Class 45
Note that in both cases, when we created (“instantiated”) the two Car objects,
BlueJ opened a dialog box and asked us for the values of three instance variables.
This is down to the role of the constructor method. In the code you will see that
there is a method that shares the precise same name as the class itself (Car in this
case). Also note that the capitalisation is the name as the class name. This method
name convention tells us that this is a constructor method. A constructor a method
that cannot return a value and must be public. It is invoked when an object is
instantiated. In this case the method required three parameters, and this is what we
supplied to BlueJ. You can think of them as “default settings” for those three
parameters. In general, constructors are methods used to setup instances of an
object to some known default or initial condition. The only other point to note is the
use of the term “this” in the definition of the constructor. We shall consider what
“this” means in the next section.
We can also call upon the methods in the Car class. To do this, just right click
the object on the workbench and see the public methods that are on offer. The
console window will open automatically for the display of messages. You should
try that as an exercise and ensure that you understand the logic associated with the
design of the Car class. To get the car to move, you need to first start the engine,
release the parking brake and press the accelerator. But there is a problem, there is
no method to release the parking brake and the default value set by the constructor
is ON.
Exercise: Add a new method to the car class to allow the parking brake to
be released. You will need it to get the car moving! In Sect. 3.5 we will have
a look at a good solution for this exercise.
In this example, the values of the state for car1 and car2 are independent.
Although they share the same class design (they are both instances of the Car
class), they are distinct objects (they are two separate cars). You will often see a
notation like car1: Car meaning car1 is an object of type Car.
At this point, we start to get a sense of what an OO program overall really is.
An OO program worthy of the name consists of a set of classes. These classes are
then used to create objects and these objects then work together towards some
useful collective purpose. You might think at this point that we can’t see much
evidence of classes working together. After all we have provided just the one Car
class definition. But If look closer at the code, we can in fact already see two other
Java classes in use that we are working with. We didn’t define them, they are part of
the broader Java set if library classes. We can identify easily by virtue of the
convention that classes start with an upper-case letter. The two classes are String
and System. So, what do they do?
The Java String class is used to represent text data. It might not initially seem
much of an interesting class because, although it is clearly it has state (the value of
the text), it’s not obvious that it has methods. After all, what can text “do”? In fact,
46 3 Getting into Object Oriented Programming
the Java String class has a rich range of methods associated with it. They include
such methods as toUpperCase() and toLowerCase() that converts the text
to upper and lower case respectively, and methods that allow you to compare one
String with another. As String is a part of Java, we can use the online doc-
umentation from Oracle to get definitive documentation on the String class. Here
is just an extract:
Source https://docs.oracle.com/javase/7/docs/api/java/lang/String.html
In both cases, we do not concern ourselves with how String or System work.
That is down to them. All we care about is that we can use them, and that they offer
certain state and behaviour characteristics to us. That state and behaviour taken
together forms their Application Programming Interface (API) and that is what the
Oracle documentation provides us with.
Our car example in fact has three classes working together. As our examples
grow more complex, the number of our user defined classes will increase, with due
observance of our general OO design principles, and we will make greater and
deeper use of the rich set of Java library classes.
/**
* A simple car test track that can accomodate 3 cars.
*
* @author Kingsley Sage
* @version 1.0
*/
public class TestTrack
{
// Instance variables ...
String name;
Car c1;
Car c2;
Car c3;
boolean isOpen;
Once we have added this new TestTrack class to our BlueJ project, we have
two user defined classes. The BlueJ class panel will now show two orange boxes
with a dotted arrow connecting them.
3.4 Creating Objects at Run Time 49
If we inspect our new object we can see that it contains three objects c1, c2 and c3.
Note that these 3 objects do not appear on the workbench. This is because we (the user)
did not create them. They were created by the object TestTrac1 which is an object of
type TestTrack (TestTrac1:TestTrack). So one object has created three
other objects. For the TestTrac1 object, we see the c1, c2 and c3 objects as arrows
in the inspector. You can click on the arrow to drill down into the car objects to see their
parameters, as can be seen in the diagram for c1. This shows how useful the BlueJ
inspector can be in determining whether the state of an object is what you expect it to be.
This is useful when debugging a program when it is not working as expected.
50 3 Getting into Object Oriented Programming
If you examine the code, it should be apparent why the green car is not moving.
We now have a closer look at the TestTrack class as it has some elements we
have not seen previously. In the TestTrack constructor, we see lines of code that
look like:
This is where the three car objects have been created. The keyword new is an
instruction to the JVM to instantiate a new object of type Car. The three parameters
are then passed to a matching constructor for action. In this case, a matching
constructor would have 3 formal parameters, a String, a value that could be a
double and a boolean. We are in luck since this matches the pattern of formal
parameters for the only constructor method in our Car class. We shall see later that
we are able to have multiple constructors to cover different scenarios. In fact, we
don’t have to have a user defined constructor, it’s just a useful thing to do and is
good programming craft. If there is no constructor available that matches the sig-
nature pattern of parameters (in order and in type), then the Java complier will
report an error.
We can now also state a simple rule concerning the use of objects:
So our three objects c1, c2 and c3 have been initialised. Now we can start to use
them. Each of them is of type Car, and has all the state and behaviour associated
with the Car class. But TestTrack needs to be specific as to which car object it
wishes to refer to. It’s no use simply stating pressStartButton() as a method
call, as we would not know which of the car objects we were referring to. So we must
provide a more fully qualified name. To do that we use the “.” notation. This is
common in Java and other OO languages. In its simplest form, the notation is:
<object reference>.<method/variable name>
So c1.pressStartButton() can be read as “call the pressStartButton()
method for the object c1”. Similarly, c1.parkingBrakeOn = false can be read as
“set the parkingBrakeOn instance variable for object c1 to the value false”. This
notation allows us to be specific about what aspect of state we want to change or method
we wish to call for which object.
Exercise: building on the TestTrack example, improve the class so that the
track can accommodate 4 cars. Add a method allStop() that causes all cars
to come to a stop with their engines off but leaves the track open for business.
We should note here the use of the this keyword we have seen, particularly in
constructors. this is a self-referential operator. It is used to mean “this object”. In
constructors and sometime other methods, we observe that the same variable name
is used as a formal parameter in a method call as is used as an instance variable. For
example, name in the TestTrack class. If we just refer to some data name, do
we mean the formal parameter or the instance variable? The this keyword
resolves this ambiguity In this case, this.name refers to the instance variable,
and just name refers to the variable of that name most immediately in scope (i.e. the
formal parameter in the constructor).
Now our example is getting to the point where we are starting to derive some
benefit from using OO. We have two classes, with multiple objects of one of them,
and a structure that allows objects to work together to perform some useful task.
There is one key element of our OO design principles however that we have not
really addressed - encapsulation. All our instance variables (the state data) are
currently public. This is because we have not specified that it should be anything
else and as such the visibility has defaulted to public. This means that Test-
Track can see and modify the state of the car objects. But this is not a sensible
model in reality. It is not the role of a test track to apply a parking brake on a car.
That’s the car’s responsibility (in a more sophisticated model, it would be the
responsibility of a driver in the car, but we have no class for the driver). We need to
improve this part of our thinking. OO is intended to offer a model that is fair an
accurate representation of our real-world problem and TestTrack is not really
cutting it in this respect. We need to improve our thinking on the encapsulation, and
the visibility of state data and methods.
52 3 Getting into Object Oriented Programming
We have discussed the concept that all data in a class should be private unless
there is a useful reason why it should not be. Many programmers argue that the best
programming craft is to make ALL data in a class private. That way, the only
way to interact with objects of that class is through the class API. That is, by calling
the methods that are available. This is certainly the model that the Java library
classes take. If you look at the on-line documentation for, say, the String class,
you will see many methods to choose from but no instance variables.
First, let’s be clear about how to make data and methods private and
public. To do this you need to add a visibility modifier to the appropriate dec-
laration. If we don’t specify a visibility modifier for a variable or a method, the
default is public. Let’s work with a simple two class example involving books:
/**
* A simple Book class
*
* @author Kingsley Sage
* @version 1.0
*/
public class Book
{
// Instance variables (all made public) ...
public String title;
public String author;
public double value;
// Constructor
public Book (String title, String author, double value)
{
this.title = title;
this.author = author;
this.value = value;
}
}
/**
* A class to represent a small shelf used for books
*
* @author Kingsley Sage
* @version 1.0
*/
public class Shelf
{
public Book b1;
public Book b2;
// Constructor
public Shelf()
{
b1 = new Book("100 Lobster Recipes","Dan Smith",10.50);
b2 = new Book("70s Rock Music","Rick Flash",20.0);
}
So we can create an instance of the Shelf class and call the changeValues()
method if we like and it will work fine. This is because the value variable in the Book
class is public. This means that an object outside of Book can have access to that
value variable and do anything it likes with it, including change its value. The code
is simple, but there are no restrictions on what external objects may subsequently
change the value of the value variable, and that has the potential to undermine the
integrity of the codebase. Really it up the Book class to look after its own information.
So we make a subtle change to the Book class:
/**
* A simple Book class
*
* @author Kingsley Sage
* @version 1.0
*/
// Constructor
public Book (String title, String author, double value)
{
this.title = title;
this.author = author;
this.value = value;
}
}
This time the instance variables have been made private. This means that
they can only be accessed from within the Book class e.g. inside the constructor or
other methods specifically defined within the scope of the Book class. This helps us
achieve the desired encapsulation characteristic but gives us a problem. The
changeValues() method in the Shelf class is no longer able to access the
value attribute in the Book class, as it is private to the Book class. As such,
the complier will complain that the Shelf class has no visibility of the value
attribute in the Book class. So what if another class really does have a purpose in
accessing or changing some of the state data inside the Book objects?
Achieving this is the role of two special sets of methods; accessors and mutators.
In reality, there is nothing special about the definitions of accessor and mutator
methods. There are no special Java keywords to characterise them. They are just a
set of methods written to a set of conventions. But as a concept they have clear
definitions:
54 3 Getting into Object Oriented Programming
So now we add an example accessor and mutator method to the Book class:
Note the naming conventions—they are not enforced by the compiler, they are just
good code craft and other programmers would recognise them immediately. Fol-
lowing these naming conventions saves you having to think of what the names would
be for any accessor or mutator. Now our Shelf object can use the setValue()
mutator method to change the value of the books:
In essence, the Shelf class has to “ask” the Book class to change its own
value. Responsibility for changing the value variable for a book object rests with
the book object and that is where is truly belongs. You may think at first glance this
seems a lot of code for no real practical benefit other than making us feel better in
our OO design principles. But there is a very real reason why this is a good idea.
Now that the responsibility for altering the value variable is built as a method, that
method can use whatever code required to enforce validity constraints on the values
that the variable can take on. For, example, we can prevent another object from
setting a negative value. We just make the mutator a bit more sophisticated:
3.5 Accessor and Mutator Methods 55
Now that we understand the basic concepts of what OO design and coding is all
about, it’s worth reflecting on some general principles for choosing the right
classes. If we choose wisely, coding should follow easily. The examples in this
capture have been necessarily simple. We will develop our understanding further as
we progress through the subsequent chapters. But there are some useful guidelines
that we can develop now that will help us. We have discussed that a good class is
highly cohesive—it focusses on representing one entity and does it well. Where we
have multiple classes, each class should have a distinct, cohesive and clearly
defined purpose. In the cars example, the Car class represented a single car.
56 3 Getting into Object Oriented Programming
In this chapter, we delve into some of the rich pre-built code libraries that make
Java a useful language for a wide range of applications. In truth, the libraries that
exist as at version 8 of the language are so rich and varied that it is unlikely that
many programmers, even experienced professionals, have an encyclopaedic
knowledge of them all. New libraries are developed all the time with many new
ones appearing in open source repositories. Part of the skill of being a good pro-
grammer isn’t knowing what all he libraries are, rather having a feel of what should
exist, and having the ability to use documentation to figure out how to extract the
maximum value from them. As a general principle, whenever we want to solve
some problem, like sorting data, using network connections, creating a GUI,
playing sound and much more, it is always worth spending some time investigating
whether a Java library already exists that offers what you need. As long as you are
sure of the credentials of the library (particularly in respect of open source content)
and you are aware of any licensing conditions that apply in the deployment of a
library (important if you plan to sell your coding skills or create a commercial
application) then you will be fine.
The Java language forms part of a broader Java ecosystem. As well as the language
itself, there are tools, applications and frameworks that enable you to build a vast
range of applications from desktop to mobile, web applets and large-scale dis-
tributed enterprise systems using the EE 8 Jakarta framework. But at the heart of
this ecosystem is the Java language, currently in version 11. The language has
developed a great deal since its first inception in 1995. The language consists of
core functionality and an extended landscape consisting of packages containing
library classes. The class library provides well over 3000 classes for programmers
to use.
The core of the language incorporates all the basic syntactic structures required
to afford sequence, alternation and repetition and a range of frequently used classes
such as String (used to manage pieces of text), the System proxy object (to
facilitate communication with the JVM) and others. No specific coding measures
are needed to utilise the core of the language, other than to correctly adhere to the
syntax of the language.
In common with other languages such as C/C++, the core of the language can be
augmented by functionality drawn from libraries (C programmers will be familiar
with the use of the #include directive to do exactly that). These additional
classes are organised into thematic groups, or packages. A package might contain
just a few additional classes, or several tens of them. For example, there are
packages for managing HTTP requests, or working with SQL databases. Each of
these packages consists of pre-built Java byte code that can be utilised directly,
provided that the programmer understands the Application Programming Interface
(API) that it provides.
To use a library class turns out to be very simple. The byte code resides in a
directory within the JDK/JRE. To access it you simply use the import directive.
This directive tells the JVM that the library byte code will be used in a current
project. You can opt to import just a single class, but it is often easier simply to
import an entire package. To import an entire package you simply replace the name
of the specific class with the wildcard * character. This is no particular disbenefit
from importing the entire package. The necessary import directives are placed at
the top of any class that needs to use them. The directives are outside the class
definition itself:
import java.util.Random;
// imports the Random class from the java.util package
import java.util.*;
// imports all classes from the java.util package.
The import directive is simply syntactic sugar intended to prevent name space
clashes. All Java classes are members of a package somewhere in the Java land-
scape. For example, String is actually a member of the java.lang package.
But that package is imported by default by the Java compiler. The notion of
packages gives rise to the concept of a “fully qualified name”. For example, the
String class is more fully known as java.lang.String, but that’s incon-
venient for such a frequently used class. In fact, all library classes can be used at
any time simply be referring to them by their fully qualified names. But that isn’t
convenient and makes the code we write less clear. So we use the packages and
generally just import what we need.
4.2 Using Library Classes 59
You can also build your code into your own packages, and then re-use that
package at a later stage in another project. This promotes the highly desirable
concept of code re-use, and many professional programmers build their own
packages and libraries over time to speed up projects where pre-built code can be
re-used.
The on-line Oracle documentation always specifies which package a class
belongs to. That information can be found at the top of the page for that class. For
example, if we look at the documentation for the String class:
Source https://docs.oracle.com/javase/9/docs/api/java/lang/String.html
we learn that String is indeed from the java.lang package. We already saw
some examples of the import directive in the guessing game program in Chap. 2.
The String class is probably the most widely used class in the Java language. It is
used to store and manipulate text. In some languages, notably C, such text has to be
stored and manipulated as an array of primitive characters. Whilst that is concep-
tually sound, it’s not convenient, and Java does rather better in this respect. We
firsts saw the String class in Chap. 3, but it’s worth spending more time now
getting to understand some of the finer details.
As a class, an object of type String has fields (attributes) and methods. In fact,
the String class only has one attribute and is not often used. Instead, all the work
on String objects is done using methods. This is a common Java coding phi-
losophy: “If something needs to be done, there should be a public method to do it”.
As we saw in Chap. 3, that extends to setting and retrieving objects values using
accessor and mutator methods.
All string literals (“pieces of text”) in Java are implemented as instances of the
String class. In fact String objects are constant or “immutable”. This means
that once they have been set, their text content (their value) cannot be altered. There
is an alternative to String that allows the text content to be edited—the
60 4 Library Classes and Packages
StringBuffer class. If you need to change the value of a String object, you
simply overwrite the old text content with the new.
Of note to C enthusiasts, a String is implemented at a lower-level as a fixed
length array of type char. As instances of a class, we recall that String objects
need to be declared and initialised. However, as they are so frequently used, some
syntactic shortcuts have been added to the language to enable you to do that quickly
and easily. Here are some examples of how to declare and initialise String objects:
The name of the String object as declared forms what is termed an “object ref-
erence”. It is somewhat similar in concept to the notion of a pointer in the C language, but
it is not as low-level, as Java does not support direct access to the host PC other than via
the System object. In fact this is true for all objects, not just those of type String. An
object reference is used to refer to an object by name, for example, when we wish to pass
an object to a method. An object reference can exist independently of an actual object. It
is also the case that an actual object in memory can have more than one reference to it.
The following code example will help you understand this:
String s1 = "Yoda";
String s2 = "Dent";
String s3;
s3 = s2;
System.out.println(s3); // produces "Dent"
s3 = s1;
System.out.println(s3); // produces "Yoda"
s1 = "Ewok";
System.out.println(s3); // produces "Yoda"
In this example, s1, s2 and s3 are all objects references for an object of type
String. The content of s1 and s2 are initialised, but s3 is just an object reference
with no object initially for it to refer to (it has a default value of null meaning “no
object”). But then s3 is set equal to s2. This does not create a new object for s3, it
just makes s3 a temporary pseudonym for s2. So when we display s3, we actually
4.3 The String Class 61
see the text content for s2. We can change the object that s3 refers to as often as
we want. When we set s3 = s1 and display s3 we get the expected text content
“Yoda”. We then change s1 to “Ewok”. But what happens now is that s1 now
refers to a new object (remember that the String class is immutable, so its value
has been replaced with a new object). So s1 has its new text content, but what
about s3? The text content that s3 refers to still exists, but s1 no longer refers to it,
but s3 does. So s3 is no longer a pseudonym for s1, they now refer to distinct
objects in memory in their own right.
It is important to note that if you want to compare two String objects, you
need to take care to understand whether it’s the objects (as they exist in memory
within the JVM) or the text content of the objects that you want to compare. If we
apply the test s1 == s2, we are asking if object references s1 and s2 refer to the
same object. They may well do, in which case the text content would be the same,
but they may also be two distinct objects in memory that may, or may not, have the
same text content. If we wish to test whether two String objects have the same
text content, there are methods specifically for that (namely compareTo() and
compareToIgnoreCase()—see the examples below).
The String class has a range of methods available to it. They allow you to do a
range of useful things including:
Here are some examples of some of the key String methods in use:
s3 = s3.trim();
int y;
y = s2.compareTo(s3);
System.out.println(y);
int z;
z = s2.compareToIgnoreCase(s3);
System.out.println(z);
}
Note that for the charAt() method, the characters are referred to using a “zero
indexed” numbering system. That is, the first character element in a String is
located at position 0. So a String with 5 characters has 5 characters elements in
positions number 0 to 4 inclusively. This zero indexing strategy is widely used
throughout Java and is commonplace in most other programming languages.
For a full account of the String methods, visit the Oracle on-line documen-
tation. It’s easy to find, using any decent search engine use a search term like “Java
String” and the official class page will likely be the very first result. Each page
provides details of all the publicly available functionality of that class. The docu-
mentation constitutes the Application Programming Interface (API) for that class.
The broad field of software engineering has made considerable advances over the
decades since the first programming languages of the 1950s. When the earliest
languages such as COBOL and FORTRAN first emerged, they were the domain of
a small number of specialists who truly understood how to leverage their features to
solve practical problems. In more modern times, we have a wealth of fully featured
languages that are widely accessible to a global community of developers. Part of
this is down to the widespread availability of computing devices, and part is down
to the fact that we can rely on elements of software that others have already built as
4.4 Application Programming Interfaces (APIs) 63
building blocks for new applications. Very few problems require completely unique
solutions. The concept of software re-use has become a central theme in modern
software engineering. This has led to improvements in reliability, reduced costs and
shorter development times. The open source community leads the way in dis-
tributed code development and incremental improvements (just consider the way
that an open source project such as the Linux operating system is continually
evolving).
A core concept in achieving the goal of effective software re-use is the idea of an
Application Programming Interface (API). The idea is a natural extension of the
concept of a method. We create methods with a characteristic signature of a set of
formal parameters and a return value. We can invoke the method and rely on the
fact that it does the job it was created to do. We do not have to concern ourselves
with how the method works unless we choose to. All any calling code or coder
needs to know is how to use it, and what it does and not how it does it. We say that
we have “abstracted away” the internal implementation details.
This is very much how we should view an OO language such as Java. The
extended Java landscape provides over 3000 classes. We use them by simply
understanding via appropriate documentation as to what they do and how to use
them. We do not concern ourselves with implementation details. In fact the sepa-
ration of the API from its underlying implementation can allow programs written in
one language to use a library written in another. A good example is Java and Scala.
Both of these languages compile to compatible byte code. Code written in Scala can
offer a Java like API and be called by Java code. The calling Java code is agnostic
of the fact that the underlying implementation is in fact in another language. So a
single API can be used across multiple implementations in different languages.
These ideas place a strong emphasis on understanding what something does and
this, in turn, demands a meaningful scheme to document it. The value of an API is
determined by how well it is documented so that we can understand how to use it.
The API should describe what the behaviour of the library should be under all
situations. The API should only make public those features that are intended for use
by the calling code. The underlying implementation may have many elements and
parts to it, but these should be abstracted away and fall under the auspices of the
implementation. All of this provides us with some general software engineering
principles that we should adhere to when we create any significant code:
1. Any single class should be designed to represent one entity, and do that
job well (principle of cohesion).
2. Classes should only make public those attributes and methods that are
intended for use by calling code. All other attributes and methods should
remain private (principle of encapsulation).
3. Consideration should be given to grouping classes together in thematic
packages.
4. Where practical, classes should share common interfaces and interface
patterns (the concept of design patterns).
64 4 Library Classes and Packages
Once we have marked up the code appropriately, the Javadocs tool can be used
to extract the markup data from the code and produce our API documentation.
Whilst the Javadocs tool can be manually invoked, BlueJ offers us a convenient
way in the code editor to switch between a source code view and a documentation
view. If you look at the top right corner of the BlueJ code editor, you will see a drag
down control with options for “Source Code” or “Documentation”. Whenever you
switch to documentation view mode, the Javadocs tool is invoked and parses the
markup data in the class file and produces a HTML compliant file with the API
documentation in it. This documentation is then stored in folder within the project
directory. The HTML documentation can then be distributed as the API docu-
mentation and viewed on any browser.
Let’s look an example of a very simple class designed to perform some math-
ematical operations. The class is not very advanced in its implementation yet:
/**
* A class that performs some simple maths operations.
*
* @author Kingsley Sage
* @version 1.0
*/
public class SimpleMaths
{
/**
* Constructor: to be developed further
*/
public SimpleMaths()
{
}
/**
* Returns the sum of two integers.
* @param x: an integer
* @param y: an integer
* @return the integer sum of x and y
*/
public int addTwoNumbers(int x, int y)
{
return x+y;
}
}
66 4 Library Classes and Packages
We see that the class SimpleMaths currently has one constructor method and
one other method addTwoNumbers(). The multiline comments have been
started with /** and ended with **/. This signifies to Javadocs that these com-
ments form part of the markup. The class as a whole has a description and it is
further annotated with @author and @version, both of which are
self-explanatory. Both the methods have similar basic annotations, but
addTwoNumbers() is further marked up with @param and @return annota-
tions. Each @param marks up one of the formal parameters, and @return marks
up the returned parameter.
If we now switch the BlueJ editor to documentation view, we see:
We see that our markup has been used to produce Oracle style API documen-
tation, complete with hyperlink navigation.
All we now need to do is to ensure that as we incrementally update the codebase,
that we keep our commenting and annotations up to date, and we will always have
the means to produce the API documentation on demand. There are other anno-
tations as well, and we shall see some of them in later chapters.
We should consider what further documentation may be required beyond the
Javadocs standard. Although the purpose of API documentation is to provide other
with sufficient knowledge to use a class, that does not mean that further com-
menting is not appropriate. Other developers may be required to refine the imple-
mentation later. To that end, we should comment to a level that would enable
another developer to make sense of what we have done. Failure to comment and
document code properly can mean that it is cheaper to abandon existing codebases
and start again from scratch when you want to innovate.
Although attributes and methods that are private do not form part of the API
documentation, it is worth documenting them to the same standard as part of a
broader effort to ensure that the software is as reusable and maintainable as pos-
sible, and such practices should be regarded as the norm in professional software
engineering.
4.6 The ArrayList Class 67
We have seen that data can be stored in primitive types and in the form of objects.
But up to now each piece of data has corresponded to one example of data of that
type. An object represents one instance of a class—one example of an entity. One
of the driving reasons for building programs in an OO manner is to allow us to
model better some real-world problem.
One key aspect of a problem that we will want to model is “collections of
things”. For example, if we were building a football video game, we might decide
that a player was an appropriate entity to model and thus a good choice for a class.
But players also work together to make a team—a collection of players. In the same
way, a word processor document is just a collection of paragraphs. A paragraph is a
collection of sentences and a sentence is a collection of words. So the concept of a
collection is commonplace in the real world, and thus it makes sense for Java to
offer us a way of representing one.
Java has many possible means of representing collections. Probably the most
commonly used is the ArrayList class (part of the java.util package). This
class is very flexible and is designed to address several collection related
requirements:
• It can be used to store a collection of any Java class type. We say that the
ArrayList class is a generic one. However, it cannot be used directly to
create collections of primitive types. We discuss a solution for this in the next
section.
• The collection must consist of elements of the same type. So we may have a
collection of String, Bicycle or whatever, but they must all be of the same
type. It is possible to have collections of seemingly mixed types as long as they
share a common super type. We will explore this idea in Chap. 5.
• The collection can be of arbitrary size, subject to any memory limitations of the
JVM and the host machine.
• At any time we can determine how many elements are in the collection
• We can perform a range of collection related operations such as adding new
objects to the collection, removing an object, clearing all objects, and retrieving
and object from the collection.
• The collection is a Java object in its own right. It is an object that incorporates a
collection of other objects.
• There are a range of algorithms designed to work with generic classes to per-
form commonplace operations such as sorting. This is discussed further in
Chap. 7.
• Import the java.util package for any class that will use the ArrayList.
• Declare and initialise the ArrayList.
68 4 Library Classes and Packages
• Use the ArrayList API to determine how to use the methods offered by the
class.
The only significant new syntax we need to master relates to the generic nature
of ArrayList. This generic nature is not confined to the ArrayList. The
general form of the declaration and initialisation of the ArrayList is as follows:
import java.util.*;
Note that the object type appears after the keyword ArrayList in angular
brackets e.g. ArrayList<String> is an ArrayList of type String.
As an example, we build a simple Dictionary class. A dictionary is simply a
collection of words. We can represent a word conveniently as a String, so our
Dictionary will require a collection of String. Our dictionary will then
incorporate some additional functionality, so allow us to add words, and to check
whether words are present (i.e. the start of a design of a simple spell checker).
Here is the start of our Dictionary class:
import java.util.*;
/**
* A simple dictionary to illustrate the ArrayList in use.
*
* @author Kingsley Sage
* @version 1.0
*/
public class Dictionary
{
// Instance variables
private ArrayList<String> words;
/**
* Constructor for Dictionary
*/
public Dictionary()
{
// Initialise the ArrayList
words = new ArrayList<String>();
addInitialWords();
}
Method Description
boolean add(E e) Add an element e of type E to the end of the collection
void clear() Remover all elements from the collection
E get(int index) Retrieve element of type E from position index
boolean isEmpty() Returns true if the collection is empty
E remove(int Removes the element from position index of the collection
index) (returns that element of type E)
int size() Returns the number of elements in the collection
boolean contains Returns true if the list contains the specified element o
(Object o)
/**
* Adds some initial words to the words ArrayList.
* @param none
* @return none
*/
public void addInitialWords()
{
words.add("crocodile");
words.add("antelope");
words.add("gnu");
words.add("zebra");
words.add("giraffe");
}
/**
* Display all words in the ArrayList
* @param none
* @return none
*/
public void displayAllWords()
{
for (int i=0; i < words.size(); i++)
{
// Retrieve the String at position i
String w = words.get(i);
System.out.println(w);
}
}
/**
* Display all words in the ArrayList using for each
* @param none
* @return none
*/
public void displayAllWords2()
{
for (String w:words)
{
System.out.println(w);
}
}
/**
* Add a new word to the ArrayList
* @param word as a String
* @return true if the word added is unique, false otherwise
*/
public boolean addNewWord(String w)
{
if (checkWord(w) == false)
{
words.add(w);
return true;
}
else
{
return false;
}
}
/**
* check to see whether a word is in the dictionary
* @param word as a String
* @return true if the word is present in the dictionary
*/
public boolean checkWord(String w)
{
boolean x;
x = words.contains(w);
if (x == true)
{
// w is already in the dictionary
return false;
}
else
{
return true;
}
}
So we have the start of a useful Dictionary class. With a little more effort, we
could expand it so that we could enter a whole sentence (as a String), break that
sentence down into individual words (an ArrayList of String) and then check
each word. The dictionary is not yet very efficient, as the words are not arranged in
any particular order (other than the order in which they were added to the list), and
this would not scale well. But we will return to this example in Chap. 7 when we
look at other collection types and some of the algorithms available for use with
generic classes such as ArrayList.
72 4 Library Classes and Packages
We have seen that ArrayList can be used to create a collection of any Java
object. But Java also features a set of primitive non-object types such as int. It is
desirable to have the ability to form collections of these on a similar basis to any
other object. But the status of the primitive types is not conducive to the generic
object nature of the ArrayList. Rather than dilute the concept of the Array-
List and have a completely separate collection just for the primitives, Java offers a
neat solution in the form of a set of classes that allow the primitive types to be
packaged in a manner that allows them to function as objects, specifically for the
purpose of interacting with other Java classes that require object status for their
operation. We call these the wrapper classes.
A wrapper class essentially provides an object construction in memory that
embeds the primitive type (that provides the underlying attribute for an object of
that class), and provides appropriate methods to permit that attribute to be initialised
and read i.e. constructor and accessor methods. The wrapper classes also provide a
surprising range of other methods designed to, among other things, convert
String representations of numeric data into numeric data (e.g. the String
“123” into the int 123) and to convert numeric types between different memory
size representations (e.g. int to long, long to double and so on). But most of
the time, we use the wrapper classes to perform their most core job of allowing
primitive types to act as objects. Each of the primitive types has a corresponding
wrapper class including:
If we use the Integer wrapper class an example, we can examine the class
API and we find some key constructors and methods:
Constructor Description
Integer(int value) Creates an object that represents the specified int value
Integer(String s) Creates an object that represents the int value specific by
the String s
Key methods
int intValue() Returns the underlying value as an int
double doubleValue() Returns the underlying value after conversion of the
primitive int to double
4.7 The Wrapper Classes 73
public WrapperTest()
{
x1 = new Integer(10);
x2 = new Integer("123");
}
}
If we want to update the int value, just overwrite the original Integer object,
as the wrapper objects themselves are immutable (the same as the String class),
so here we could just use y2 = new Integer(y3) and that will use the primitive
y3 value above and re-wrapper it in the object x2, displacing the original object
that x2 referred to.
Now we can use any of the suitably wrapped primitive types with other library
classes such as ArrayList. Here is an example of a program that can generate an
ArrayList of random numbers of any size as specified by the user in the constructor:
74 4 Library Classes and Packages
import java.util.*;
/**
* Creates an ArrayList of random numbers in the
* range 1-99 inclusively.
*
* @author Kingsley Sage
* @version 1.0
*/
/**
* Constructor
* @param num number of initial elements in the list
*/
public ListGenerator(int num)
{
Random r = new Random();
myList = new ArrayList<Integer>();
for (int i=0; i<num; i++)
{
int x = r.nextInt(99)+1;
myList.add(new Integer(x));
}
}
/**
* Displays each number in the list on the console
* @param none
* @return none
*/
public void displayList()
{
for (Integer i:myList)
{
System.out.println(i.intValue());
}
}
}
Now that we understand the basic concepts of classes and the extended land-
scape of the Java language, we can start to deepen our understanding of the OO
paradigm and really start to exploit the nature of an entity and our ability to capture
and model it effectively in code. To do that we must turn our attention to one of the
most core features of OO languages—polymorphism.
Modelling the World the Object
Oriented Way 5
We note that the entity of an Item is relatively abstract in the sense that we could
not go into a shop and purchase one. If we said to a member of staff that we “wish
to purchase an item”, we would expect the response “which item?”. We can pur-
chase specific, or concrete entities, but not those that are relatively abstract in their
conceptualisation. But that does not mean that the higher-level entities have no
value. They just serve as base definitions onto which we can build more concrete
entities. We say that that the higher-level entities are “parent”, “base” or “super”
classes, and the lower-level ones built from them are “child” or “sub” classes.
5.1 Hierarchies in the Real World 77
The key idea here is the “is-a” relationship. Looking at our shop example, we see
that HardGood is-a Item and that ElectricalGood is-a HardGood and by
extension is-a Item. That suggests that, in type terms, ElectricalGood has
type ElectricalGood and also has type of HardGood and ultimately Item.
This is the kind of hierarchical relationship that Java and other OO programs are
designed to capture.
Not every real-world hierarchy as we might construe it is suited to this kind of
super and sub class analysis. Consider the human resources example referred to
earlier. The CEO is the head of the organisation, but does not make much sense as a
base class. A senior manager is not also the CEO. A team member is not also a
senior manager. So instead of the classic staffing organogram that you might be
used to seeing, a more appropriate OO relationship might look something like this:
Polymorphism at the class level is the concept that an object can take on
more than one form. If an object of class type X has a parent class of type Y,
then X can take the form of an object of type either X or Y, and can thus
appear in many forms.
As we shall see later in Sect. 5.4, polymorphism is not confined to the class
level. But for now, we shall develop our notions of class level polymorphism in
terms of what it means in Java coding.
Class level polymorphism in Java is implemented using the idea of super and
classes. In OO design terms we need to consider what attributes and methods a set
of classes share. Where we can identify common attributes and methods, we can
78 5 Modelling the World the Object Oriented Way
factorise the design of those classes so that the commonly shared attributes and
methods form part of the super class. We will use an example to help us understand
how this works. Consider a shop that sells antiques items, namely vases, statues and
paintings. For now we shall consider just some appropriate attributes for our initial
OO analysis:
We will make three classes for each of the things that our shop sells. We can see
that the classes have some attributes in common, and some that are unique to each
class. But we can also see that in reality all three classes are examples of “items”—
things that the shop sells. So we can factorise our design with an Item superclass,
and then make our three specific classes a sub class of that super class. This is
beneficial as it reduces un-necessary code duplication (as well as being a better
model of a real-world problem).
The super and sub class relationship is represented by an arrow pointing from the
sub class towards its super class:
We can now create some classes in Java to represent the super class Item and
the three sub classes Vase, Statue and Painting. The keyword extends is
used to indicate in Java that a class is a sub class of another:
5.2 Introducing Super and Sub-classes 79
/**
* Item superclass for the Antique Shop
*
* @author Kingsley Sage
* @version 1.0
*/
public class Item
{
public int value;
public String creator;
}
If we create this as a project in BlueJ, we see the super and sub class relationship
clearly on the central panel:
The Vase class has a total of four attributes. Two of these are unique to it, and
two are “inherited” from its superclass. Similarly, the Painting class has six
attributes, four unique to it, and two inherited from its superclass
So a super, base or parent class contains a basic set of attributes that are intended
to form a part of the implementation of sub classes that are built from it. The sub
classes have their own attributes that make them unique, and they inherit attributes
80 5 Modelling the World the Object Oriented Way
from their superclass. Similarly, a sub class inherits methods from its super class. If
we add a method to the super class Item, we find that instances of Vase, Statue
and Painting will inherit it, and be able to call those inherited methods as if they
had been defined in their own class definitions. In our example, a showValue()
method has been added to the Item super class. A new AntiqueShop class has
been created, and that creates v1, an object of type Vase, and v1 then calls the
new showValue() method:
Note that, for now, all the attributes and methods have been declared as
public. This is not necessarily good OO design practice and we return to this
issue in Sect. 5.4.
The concept of inheritance can continue to be applied at further levels. We could
sub class (extend) our sub classes again e.g. we could have different types of statue,
vase or painting. They would then become super classes for the new sub classes and
the rules of inheritance would apply throughout. In fact, it turns out that this is key
to the very OO way Java is designed. All objects in Java, whether library or
user-defined, ultimately share a common super class, the Java language base class
Object. As such all objects inherit methods from Object. A good example of
such a method is the toString() method. This is a method that determines what
should be displayed on the console when System.out.println() is invoked.
We can customise objects in this respect and we shall see this in Sect. 5.4 when we
discuss over-riding.
5.3 Adding Constructors 81
We now know that inheritance allows sub classes to inherit attributes and methods
from a super class. We now consider how we should build constructor methods so
that instances of our sub classes are created in the most efficient manner.
As the sub class has access to all its attributes and methods, it is reasonable that
each sub class could just provide its own full constructor, and we could forget about
a constructor for the super class, as they are not, in general, intended to be
instantiated directly. However, that is not efficient coding as it would involve code
duplication. One reason why building super and sub classes is a good idea is the
fact that we can reduce code duplication by factorising common attributes and
methods into a single super class.
The solution is to provide the super class with a constructor in respect of its own
attributes (and methods), and then create another constructor for each of the sub
classes to deal with the attributes and methods that are unique to them. But that now
means that there are two constructors to call; one for the super class and one for the
sub class. We need to distinguish between these constructors and this is achieved
using the super keyword. The super keyword refers to the super class of an
object (as compared to the keyword this—the self-referential reference).
We use our antique shop example again to illustrate the point. Here a constructor
has been added to the Item super class and a constructor has been added to the
Vase sub class. Note that the Vase constructor takes a total of four parameters so
that it can setup the object properly, and that it calls upon the Item constructor to
deal with two of the attributes:
If there are successive layers of super and sub classes, the same process can be
extended so that the constructor of the lower sub class calls the constructor of its
parent, that in turn calls its parent’s constructor and so on until the object is fully
constructed. There is only one other rule to be aware of:
Where a sub class is to call a constructor in its parent class using the super()
method call, it must be so as the first line of its own constructor. This is to
ensure that objects are constructed from their most distant super class first.
Now that we understand the basic concept of inheritance, we can look at the rules
that govern the actual process itself, as there are situations where we might wish to
enact a more fine-grained control over what is inherited by a sub class from a super
class. The first point to note is that any class can have at most only one parent class.
Unlike other languages (notably C++) there is no concept of “multiple inheritance”
from more than one direct super class. Classes cam inherit from a chain of super
classes, but in each case only have one direct super class. C++ enthusiasts need not
despair however as the Java concept of inheritance covered in Sect. 5.8 provides an
alternative way of inheriting responsibilities from multiple classes.
In the example in Sect. 5.3, all the attributes and methods were declared as
public. This is not generally good OO practice as we aspire to the principle of
encapsulation. This suggests that attributes are, by design default, private unless
there is a good reason otherwise. This presents a problem. If the attributes of a super
class are private, then we cannot guarantee that they will be inherited by sub
classes, as they are private to the super class. There are situations where it can
happen, based on a more detailed analysis of the rules of inheritance when classes
are distributed in different packages, but we shall not complicate the issue here.
What we need to do is to provide an alternative access modifier protected to
go with the two existing modifiers private and public. The rules that govern
how we can guarantee inheritance are as follows:
5.4 Rules of Inheritance and Over-Riding 83
private: attributes and methods are only accessible from within the class
itself, and are not guaranteed to be available in any class extended from it.
protected: attributes and methods that are private and only available
from within the class itself and any class extended from it.
public: attributes and methods that are accessible from within the class and
from any other class or calling code.
For our antique shop example, we should set the attributes to protected in the
super class Item, and the attributes to private in the sub classes. The con-
structor in the super class remains public and should always be so.
That provides an elegant solution for the encapsulation issue. But what about
situations where we don’t necessarily want to inherit the version of a method from
the super class, but instead replace it with something else. Such a situation requires
an “override”. The idea is that when we make a method call, we look at the
immediate class definition for a method that matches our call. If one is found, we
invoke that method. If no matching method is found, we turn to the super class. If
one is found there, we involve it. If no matching method is found, we continue to
move through the chain of super classes until we either find a method that matches,
or we reach the Java language base class Object and have still not found a match,
and the compiler will report that no matching method can be found.
For our sub classes, we can choose to override a method inherited from the super
class by simply defining a new method with the name and pattern of formal
parameters. By coding convention, and to give other programmers notice that we
are doing so, we annotate the overriding method using the @Override annotation.
For our antique shop example, let’s say that we want the showValue()
method in the Vase class to be different in implementation to the one inherited
from Item. But we do want Statue and Painting to inherit the one from
Item as before. So we leave the class definitions of Statue and Painting
unchanged, and add an overriding method definition in the Vase class. Putting
these new ideas into our example, our code base now looks like this:
84 5 Modelling the World the Object Oriented Way
/**
* AntiqueShop example refined
*
* @author Kingsley Sage
* @version 1.0
*/
@Override
public void showValue()
{
if (value > 0)
{
System.out.println("This vase is worth " +
value + " pounds");
}
else
{
System.out.println("Vase value not yet determined");
}
}
}
For completeness, here is the class diagram as shown on the BlueJ central panel:
We see the super and sub classes relationships as before, but we also see the
dotted arrows between AntiqueShop, Vase and Painting. This is an “asso-
ciation” relationship and simply denotes that AntiqueShop is making use of
Vase and Painting, in the sense that it has declared references to them, and thus
there is a dependency.
We have explored the idea that classes can appear in many forms. But the concept
of polymorphism does not end there. Methods can also appear in many different
forms. A good example here would be a constructor method. The job of a con-
structor method is to initialise an object. Depending on what data is available at the
time the object is created, it may need to be initialised in different ways. More than
one constructor can be created to deal with the different scenarios. They must all
carry the same method name as their class, or they would not be categorised as
constructor methods But they will need to differ in the pattern and/or number of
formal parameters so that the compiler can work out which of the constructors is the
correct one to call.
Using our antique shop example once, let’s equip the Vase class with two
constructors. One when we know all the attribute values (as before) and another
where we only know the height and material:
5.5 Method Polymorphism 87
When we instantiate instances of the Vase object, the compiler will look for a
matching constructor signature. Each signature must be unique or the compiler will
report an error. If the compiler cannot locate a matching signature, it will report an
error. So in our AntiqueShop class, we now have two options to creates Vase
objects:
The first version is for when we have not been given a value for the amount to
reduce the width, and thus use a default value of 1. The second can be used where
we have been given an amount to reduce the width by.
We have seen already the nature of the relationship between super classes and their
sub classes. We now delve a little deeper into Java to understand the precise nature
of object type. On the face of it, an object has the type that was declare for it, but
that is not quite the full story, as it does not take into account the is-a relationship.
An object can have more than one type:
static type: the type that it has when first declared. Static type checking is
enforced by the compiler. This should not be confused with the use of the
static keyword that we consider later.
dynamic type: the type that is has at runtime.
To make things clear consider this following code building on the antique shop
example:
Object v1 has a static type of Vase. But object i1 has a static type of Item and a
dynamic type of Vase. This is possible because v1 is a Vase, but it is also an Item.
This might not seem very useful, but in fact it helps us solve a problem. Our
AntiqueShop could really use a collection of all the artefacts that it has. But
these things could be any mixture of objects of type Vase, Statue and
Painting. We could use 3 separate ArrayList collections. We know that an
ArrayList must be a collection of a single type. But that is not convenient
5.6 Static and Dynamic Type 89
coding and requires a deal of code duplication. In reality the shop really has is a
collection of objects of common type Item. So we could have an ArrayList of
type Item. Objects of type Vase, Status and Painting all have the possi-
bility of a dynamic type of Item, so can be added to a single collection
ArrayList of type Item.
We now further refine the AntiqueShop in exactly this way:
import java.util.*;
/**
* AntiqueShop example with ArrayList
*
* @author Kingsley Sage
* @version 1.0
*/
public class AntiqueShop
{
private ArrayList<Item> allItems;
public AntiqueShop()
{
allItems = new ArrayList<Item>();
}
This is an elegant and practical solution for the AntiqueShop as it now needs
only one collection to do its job. This example does raise a further issue though.
What should we do if we want to create a method to iterate through the
ArrayList and display details about each item in the shop?
One obvious solution would be to provide each class with a dis-
playDetails() method. That can certainly be done, but it not quite satisfactory
in that if the method was common to all the sub-classes, then OO design philosophy
would suggest that the method definition belongs in the Item super class.
90 5 Modelling the World the Object Oriented Way
We have previously discussed that super classes tend to be more abstract in their
nature than the more concrete sub classes. The abstract nature is a reflection of the
fact that they are OO design constructs intended as a means of avoiding code
duplication, and of modelling some real-world problem domain in a more reflective
manner.
In the previous section, we noted that if we wanted to have a dis-
playDetails() method, then by design it should be common to all classes and
thus the obvious place for it is within the super class Item, so that it can be duly
inherited by all the sub classes. But this is not possible in practice as the sub classes
have attributes that are private to them, and are not known or accessible by their
parent super class. So a method in Item cannot access the Vase attributes.
What we can say is that, conceptually at least, displayDetails() would
belong in Item, it’s just that Item is not able to provide the implementation. This
is where we meet the notion of an abstract method.
Note also the addition of the abstract keyword in the class definition itself.
The Item class has become an abstract class.
Our attention now turns to the sub classes. As they extend Item, they inherit the
abstract definition of the displayDetails() method. They are now
required to provide a concrete implementation of that method. The compiler will
not regard the sub class definitions as complete until they provide such a concrete
implementation. So, using the Vase class as an example:
We are obliged to similarly equip the Statue and Painting sub classes:
5.8 Interfaces
// Interface method
public void displayMessage();
}
The use of interface attributes is somewhat limited, but you can access them
directly from the interface if you need to:
System.out.println(MyInterface.defaultMessage);
Now we can make a class that implements the interface. Rather than extending a
parent class as before, we implement the interface:
The idea is that other classes might also implement the same interface. How they
do it is up to them. One class may display the message on the console (as above),
another might place the message on a GUI panel, another may send the message
over a network. But if they all implement the same interface, we are guaranteed that
they do provide an implementation of displayMessage().
You cannot create an instance of the interface on its own—that would make no
sense as it is essentially an abstract entity. But you can require a class to implement
multiple interfaces:
All methods in an interface are public. If you omit the access modifier, the
method will still be public. By allowing classes to implement multiple interfaces,
we can achieve most of the desirable elements of traditional multiple inheritance.
A class may extend a parent class and implement as many interfaces as it needs to
do its job.
A recent innovation from Java 8 is the notion that the interface can provide a
default implementation of interface methods. This can be helpful in the sense that
there may be situations where two classes may utilise the same implementation of
one of the interface methods. If the implementing class does not provide its concrete
implementation of the interface method, the it will take on the default implemen-
tation from the interface:
// Interface methods
public void displayMessage();
Note the use of the default keyword in the interface method. Any imple-
mentation in a class that implements an interface will override a default interface
method.
Now our Message class might look like this:
It is also possible for interfaces to be organised as super and sub interfaces, with
interfaces inheriting from one another. But that is making things too complex for
now.
We have seen that objects are unique instances of some class type. The class
definition specifies the attributes and methods that an object will have. Each
instance of that class is its own object of that class type. Object attributes are
frequently referred to as instance variables. But it is also possible to create variables
that are shared by all instances of a class. These are referred to as “class variables”.
The use of static variables is somewhat limited in practice. It can be used, for
example to keep track of how many instances of a class have been instantiated:
However, it is trickier to determine when an object has gone out of scope, and
thus that numInstances should be decreased. This is because there it is not up to
the user to determine when an object in memory is no longer required and can be
disposed of. This is determined by the JVM which is lazy in its garbage collection.
Rather more useful is the idea that we can use static variables to hold
constants. This provides us with a practical means of storing global values. This can
be conveniently achieved using the concept of a static, or utility class.
96 5 Modelling the World the Object Oriented Way
A static method is one that can be invoked even though no instance of the
class was ever instantiated. This may seem odd, but it is useful in situations where
the content of the method has no dependency on any instance variables or other
non-static methods. For example:
A static class is one that contains only static attributes and/or static
methods.
Probably the most often used static class in Java is the Math class. As it is
not intended to be instantiated by the user, we refer to it by its class name. So in the
example above we access the constant by UtilityClass.VITAL_CONSTANT
and the method as UtilityClass.addTwoNumbers(). By convention,
constants in utility classes are written in upper case e.g. Math.PI.
There is one other notable use of a static method and that is main(). Up to
this point, we have been responsible for creating instances of classes in BlueJ and
invoking an appropriate method to get the work done. But in real world applications
we operate outside of the BlueJ environment. So a Java application needs a means
of knowing how to start itself. But this requires Java to instantiate an object and call
a method. This is where the main() comes in. In a Java project, one and only one
of the classes is nominated as being the host of a main() method. The main()
method is declared as static. When the program is to be executed, the JVM
identifies which of the classes has the main() method and executes it. It can do
this as the method is static. Typically the main() method then creates an
5.9 Class Variables and Static Methods 97
instance of one of the classes (usually the one it resides in) and calls a method to
initiate the application. You can think of main() as being like a flag on a golf
course that tells the player where to begin playing!
public Game()
{
System.out.println("Instance up and running");
}
The signature of the main() method is always public void static main
(String[] args). The formal parameter is an array of type String. Although
little used in a world of Graphical User Interfaces (GUIs), the array of String
represents an optional set of console command line options that can be specified
when launching a Java application in a text only console window.
You can still implement the main() method whilst working in the BlueJ
environment. But rather than instantiating an object and calling one of its methods
as before, you can just right click the class on the central panel and call the main()
method directly.
98 5 Modelling the World the Object Oriented Way
At this point, you should be feeling comfortable with the concept of OO coding,
and able to see the value that it brings in modelling real world problems. But we are
not expert coders yet! Really good programs work even in challenging conditions,
and when dealing with poor user input or incomplete information. In the next
chapter we start to explore how to make our code more resilient in the face of
errors.
Dealing with Errors
6
Anyone involved in teaching coding will have heard the immortal claim by a
student that “my program isn’t working correctly”. Well, the news is that it is – it
just isn’t doing what you intended. Computers and compilers are very useful, but
they are not mind readers and they can only perform the precise task that the code
specifies. Programming languages have a syntax that follows strict and precise rules
so that there is a single and unambiguous execution path. The main source of
problem conditions with modern software tends to be the user. Users make errors in
providing data input, and often fail to read the instructions on how to use the
software. Nonetheless the software is expected to work “correctly” in these
conditions.
It is worth considering a taxonomy of sources of errors as it helps us to consider
what broad approach is right in dealing with any specific problem we may
encounter. Some are relatively easy to fix and some may prove more challenging. It
is also worth considering whether it is, in fact, ever possible to produce a piece of
software that is guaranteed 100% free from error. The answer is a simple “no” even
for small programs. If we build a small program, even one as simple as the classic
“Hello World”, we are using tools and applications that we did not create and have
no guarantees of provenance. How do we know that the compiler that we use does
not have some obscure error? Software systems such as operating systems, com-
pilers, libraries and support applications are the subject of continuous improvement
and updates. There have been many real-world cases where such updates have
caused whole categories of software systems to malfunction or even fail altogether,
sometimes with little thought given as to how to roll back to a safer previous
version.
When a piece of software is developed it is subjected to a testing regime. During
the initial phases of that regime errors will be discovered easily and will be fixed.
But as time passes, errors will become more infrequent and difficult to diagnose. As
such, the cost of fixing them increases. Ultimately there may be errors that occur so
infrequently that despite months of testing, they do not show themselves, but once
the code is deployed, they will eventually do so. This implies that an infinite
amount of time is required to ensure that software is 100% free from error, and that
is neither practical nor economic to do so. So we must accept that software can have
errors and work in a manner intended to mitigate the consequences. That’s a
sobering though for those involved in the design of software systems for avionics,
but it is the reality. A common question that arises in software development is
therefore “how much effort should I put into ensuring that my software is free from
errors?” The answer depends entirely on what the software is being used for, and
what the consequences would be of unexpected failure. It is, at most, inconvenient
if our latest trend social media application stops working. But in avionics control,
the consequences are much more severe and potentially life threatening. Interest-
ingly, there are three stand out industries that put a huge effort into software
reliability; avionics, medical systems and the games industry. The latter might seem
unlikely, but it is worth reflecting that as modern computer games are multi-million
pound investments by development houses, and that no return on that investment is
achieved until you sell copies of the finished produced, most games software
development houses are one poorly received title away from economic ruin.
Errors can occur in all phases of a software application’s lifecycle:
Coding syntax errors: the code produced does not compile as the rules of the
language have not been followed properly. The responsibility lies with the
developer to remedy these errors.
Logic errors: the code compiles, but does not function in the intended
manner. Typically these errors occur as the developer has made a mistake
when producing the code. Testing usually exposes these errors, and they are
relatively easy to fix. We associate this kind of error with the process of
“debugging”.
6.1 The Nature of Errors 101
Design errors: the code works fine but does not address the requirements of
the user. This can be a very costly type of error to address, and usually comes
about because the developer has not understood the needs of the end user
properly. Design errors are the consequence of poor software engineering
practice, and can, in extreme cases, cause failure of the project as a whole.
User errors: the code works, but does not respond consistently or coherently
when faced with erroneous user input. For example, the code askes the user to
“select a menu option from 1 to 5” and the user enters 6. Did the software
provide a useful error message, or instead pursue some unknown default
value? This type of error is addressed through unit and system level testing.
Developers often under-estimate the time it can take to harden a piece of
software against user errors. Defensive coding is a typical means of dealing
with this type of error.
Run time errors: for example failure to open external assets such as files,
network connections that do not respond in a timely manner and faults in run
time support infrastructure such as the JVM running out of memory. We
usually deal with these errors using exception handling.
Environmental errors: errors that arise due to factors outside the immediate
scope of the application such as hardware failure. There is often little we can
do about these issues, other than ensure that our software closes down in an
orderly manner.
Dealing with syntax coding errors at the compile stage is the easiest – for a
compiled language such as Java we can’t make deploy the application until these
errors are dealt with, and solving them just requires knowledge and patience.
However, for interpreted languages such as JavaScript this is not the case. For the
interpreted scripting languages we don’t detect even these basic syntax errors until
we try to run the code.
For all other types or source of error, we need to consider what strategies we can
bring to bear to minimise them happening, and deal with them when they do.
The art of coding defensively reflects the adage that “prevention is better than cure”.
In coding terms, this means working to prevent erroneous data from propagating
through our application. Defensive coding refers to a style of working where:
102 6 Dealing with Errors
• Checks are applied at the earliest opportunity to ensure that data is valid.
• Attribute values can only be set (mutated) in a controlled way.
• Default conditions are set in place for control constructions to ensure that there
is always a directed path of execution.
• Only those attributes and methods that need to be public are declared as such.
We already learned about mutator methods in Chap. 3. One of the real benefits
of using a mutator method is that it allows us to control precisely how a private
instance variable should be set. Consider the following very basic mutator:
private int x;
The setX() mutator provides external access to the x attribute. But here there
is not really much benefit in the mutator method, since we could still use it to set x
to any value. What if it was essential that x only ever had a value in the range 0 to
10 (inclusive):
Good coding will generally see if statements have an else condition, and
switch statements with a default case option.
Turning to repetition structures, we need to ensure that it is always possible for
the controlling test condition to fail so that the loop will eventually terminate,
otherwise we have the potential for an infinite loop. When iterating through a
collection such as an ArrayList, we should use a structure that either uses the
size() method to dynamically determine the list size, or use the alternative form
of iteration.
Poor coding would separate the record of the length from the collection:
import java.util.*;
}
}
The danger here is that x and the length of the ArrayList become unsyn-
chronised. A better solution would be:
104 6 Dealing with Errors
import java.util.*;
These are just some basic examples of a defensive coding style. They are
reflections of just plain good coding style along with the use of appropriate com-
menting, sensible variable names, task decomposition, cohesion and encapsulation.
Even once we have embraced the concept of defensive coding, errors will still
occur. Some of these will be logic errors, where the developer has just plain got the
code wrong and has not implemented what they thought. Such errors require a
strategy to solve them. The simplest approach is the “structured walkthrough” of a
piece of code, working one line at a time seeing whether the expected behaviour
matches the actual behaviour of the code. Many beginner programmers make
extensive use of System.out.println() to do this. That’s OK for a while,
but it is problematic as it can bloat the codebase making it harder to see what it
going on, and also it means that we have to interfere with the codebase to test it.
Ideally, we need a means of looking inside the software as it executes to see what is
happening. We have already seen in Chap. 3 the BlueJ inspector which is useful in
seeing what values attributes have. To use the inspector, we can right click objects
on the workbench and select “inspect”:
6.3 Using the Debugger Tool 105
The inspector is a very useful tool, and is not limited to just examining a single
attribute. We can also “drill down” through objects to examine all aspects of their
inner state. For example, we see that the ArrayList someList has a curved
arrow to show us an object reference to a contained object. We can click on the
arrow to drill down into someList and see its value:
A debugger allows for the testing of a target program. A debugger allows the
code to be executed in blocks or at a line at a time as if the code was
interpreted. A debugger will facilitate the setting of breakpoints at specific
lines of code, allow the user to examine variable values and the state of the
call stack. This permits the results of the code execution to be compared with
the developer’s expectations to determine whether the code is functioning as
intended.
To expose the debugger window in Blue J select “Show debugger” from the
View menu tab:
The call stack is show on the “call sequence” panel. The variable panels will
display the values of all variables in scope at any point in the debugging workflow.
To debug a program you will need to set one or more breakpoints. These are set in
the source code editor window:
6.3 Using the Debugger Tool 107
On the white strip to the left of the code, click the mouse to set and unset points
in the code where you want execution to pause. Note that these breakpoints must be
on lines of code that do something, they cannot be set, for example, on a line with
just a brace on it. You can have as many breakpoints as you need. Then run your
code as normal. When the program reaches a breakpoint, the debugger window will
open:
You should take some time to get used to the debugger as you will find, over time,
that it is one of the most useful tools in figuring out why a program is not behaving
the way you intended.
108 6 Dealing with Errors
It is worth observing that whilst the debugger is a great tool for identifying logic
errors, design errors are a much harder problem to solve. The greatest tool is getting
the design right is you, experience, a pen and paper and taking the time to really
think about what you are trying to achieve.
Once you have created a program that you think is functioning properly, it’s time to
expose it to some methodical testing. Testing can be at the unit or system level.
Unit level testing: individual classes are tested to ensure that they function
according to their cohesive purpose. Sometimes small groups of classes are
tested working together - this is referred to as component level testing.
System level testing: where a program as a whole is checked to see that it
operates as intended.
To create a unit test class in BlueJ, select “New class” and then select the “Unit
Test” option. By convention, unit test classes are named by the class they test
coupled with the word Test. To help us understand unit test classes in practice, we
will use the following BookReviews class:
6.4 Unit Testing 109
import java.util.*;
/**
* A class to store reviews of a Book
*
* @author Kingsley Sage
* @version 1.0
*/
public class BookReviews
{
private ArrayList<String> allReviews;
private double rating;
private String bookTitle;
private String bookAuthor;
/**
* A method to add a review to the list of reviews.
* @param String with the text of the review
* @param int with the rating out of 5
* @return true if the review was accepted
*/
public boolean addReview(String review, int myRating)
{
if ((myRating < 0) || (myRating > 5))
{
System.out.println("Rating must in range 0 to 5");
return false;
}
else if (review.isEmpty()==true)
{
System.out.println("Review text is empty");
return false;
}
else
{
allReviews.add(review);
// Update the average score
if (allReviews.size() == 1)
{
rating = (double) myRating;
}
else
{
double x = rating * (allReviews.size()-1);
x = x + (double) myRating;
rating = x / allReviews.size();
110 6 Dealing with Errors
}
return true;
}
}
/**
* A method to display all reviews and the average rating.
* @param none
* @return none
*/
public void displayReviews()
{
if (allReviews.size() > 0)
{
for (String r:allReviews)
{
System.out.println(r);
}
// Display rating to 2 decimal places
System.out.println("Current rating: " +
String.format("%.2f",rating));
}
else
{
System.out.println("No reviews yet");
}
}
The concept here is that we can enter a review and a rating only if:
This seems easy enough, and the code has compiled. But is the code free from
error, and does it do exactly what was intended? We could create an instance of the
class and call the various methods manually. But instead we create a unit test class
called BookReviewsTest. It will appear in the Java central panel as a green box
(to remind us that it does not form a part of the main codebase). If we open the test
class in the code editor, we find quite a lot of useful code has been generated
automatically:
6.4 Unit Testing 111
/**
* The test class BookReviewsTest.
*
* @author Kingsley Sage
* @version 1.0
*/
public class BookReviewsTest
{
/**
* Default constructor for test class BookReviewsTest
*/
public BookReviewsTest()
{
}
/**
* Sets up the test fixture.
*
* Called before every test case method.
*/
@Before
public void setUp()
{
}
/**
* Tears down the test fixture.
*
* Called after every test case method.
*/
@After
public void tearDown()
{
}
}
The test class is much like any other class in that it can have attributes and other
non-annotated methods. We don’t have to use the constructor, or the @Before and
@After parts, but they can be helpful in larger test arrangements.
112 6 Dealing with Errors
We shall create our first test. Tests are created as methods annotated with
@Test. Out first test will create an instance of the BookReviews class and add
one properly formed review to it:
@Test
public void test1()
{
BookReviews b1 = new BookReviews("fishing", "smith");
assertEquals(
b1.addReview("A splendid read - highly recommended",5),
true);
}
The assertEquals() method is the key. The first argument is out method
call to our object b1. We know that addReview() will return true if all is well.
The second argument is the value we expect addReview() to return – if it does
that it is working fine.
Now that we have a test, we just need to run it. It we now look at BlueJ, we see:
Now we right click on the unit test class where we have options to “Test all” or
invoke “test1()” as we have defined it. It is more pleasing to select “Test all”
and when we do we see:
6.4 Unit Testing 113
We see that the test was conducted and the class has a green clear bill of health
based on the current test set. If assertEquals() found a different result to the
one expected, a red band would be displayed instead. So, is our code perfect?
Let’s try adding a blank review. We can see that the code already should deal
with a blank String when invoking addReview(), but is it foolproof? Let’s
reorganise our test class:
/**
* The test class BookReviewsTest.
*
* @author Kingsley Sage
* @version 1.1
*/
public class BookReviewsTest
{
private BookReviews b1;
/**
* Default constructor for test class BookReviewsTest
*/
public BookReviewsTest()
{
b1 = new BookReviews("fishing", "smith");
}
@Test
public void test1()
{
assertEquals(
b1.addReview("A splendid read - highly recommended",5),
true);
}
@Test
public void blankStringTest()
{
assertEquals(b1.addReview(" ",0), false);
}
Once again, we select “Test all” for the unit test class and we find that
blankStringTest() has failed, as the methods has returned true when we
were expecting false. If we click on the red test result we will get a summary
explanation:
114 6 Dealing with Errors
The problem here is a logic problem. We used the isEmpty() method from
the String class to determine if the user entered a blank review. But in the test
case we use a String that actually consisted of some white space – blank but not
an empty String. What we need to do is to trim the input String before
evaluating whether it is indeed empty. We can do this by just changing one line in
the BookReview class:
// else if (review.isEmpty()==true)
// replace with
else if (review.trim().isEmpty()==true)
The trim() method is invoked prior to isEmpty(). Now we re-run all tests
and find that we have passed.
We can continue to build as many tests as we think are required to demonstrate
that the software is “fit for purpose”. It is up to us as developers to determine that
fitness. Professional software teams often have a dedicated test engineer writing test
scripts. The automated nature of these tests means that we can carry on incre-
mentally developing code and adding to our classes, safe in the knowledge that as
our bank of tests builds up, we always have a means of checking whether some
code we added has caused a problem in our existing codebase. Once we have
finished our classes, we have a means of providing assurance that the class operates
correctly. Some professionals advocate “Test Driven Development” (TDD) where
the tests any class must pass are defined at the outset prior to the body of the class
ever being written. Development then continues until the class can pass all of its
tests.
There is much to the topic of automated testing, and we have just touched on
some of the most important aspects, but you should be left with a desire to
investigate how unit testing can be incorporated into your everyday workflow as a
means of developing a more robust codebase.
6.5 System Testing 115
Once we are satisfied that each individual class is working correctly, the next phase
of testing in software engineering involves testing the application as a whole. The
system testing process is often a part automated process and part a case of building
a documented process.
At the automated level, it is perfectly possible to create Java unit test classes that run
the entire application – there is no particular constraint on what objects a unit test class
can create and the methods that it can call. But the extent to which this is practical
depends on the design of the application itself, particularly in respect of any user
interface aspects of the application. For example, if the application requires user input, it
can be difficult to create a test class that fully simulates the user interaction. The user
would still be required to enter whatever data was necessary to make the application run.
One strategy for dealing with this is to add additional methods to the codebase that
are only there to assist in automated system level testing. Such methods allow for the
“injection” of user input in lieu of any user interface code. Whilst this solves the
problem of automating the tests, it does mean that the code is not being tested quite “as
is”. For example, in the code example below there is a method to allow for some user
input using the Scanner class. To facilitate testing, an alternative method is provided
that can achieve the same effect, but under code control without the Scanner class:
import java.util.*;
/**
* An example of how to adapt code to facilitate
* automated system level testing.
*
* @author Kingsley Sage
* @version 1.0
*/
public class TestingExample
{
private int x;
/**
* Enters a value for x using Scanner
*/
public void userEntersValue()
{
Scanner sc = new Scanner(System.in);
System.out.println("Enter a value for x:");
x = sc.nextInt();
}
/**
* Alternative for automated system testing
* @param int value of x
*/
public void userEntersValueTest(int x)
{
System.out.println("User enters x: " + x);
this.x = x;
}
}
116 6 Dealing with Errors
The software is declared “fit for purpose” when it can pass all of the system level
tests. If any test is failed, appropriate remedial action is taken (generally reworking
some code) and the whole schedule is then carried out again. It is important to
consider performing all of tests again as we cannot guarantee that any remedial
coding has not affected the codebase such that one of the other tests would now fail.
The system level testing documentation is then shipped with the application as
proof of fitness for purpose.
Although we take every effort to eliminate errors in our code, it is unlikely that we
can ever develop a completely error free codebase. So to have a robust application,
we also have to have strategies to deal with issues that only arise when the program
is running. We call these “run time” errors. Run time errors can occur because of
poor coding, or because of problems interacting with user or external data content.
Such errors lead to the “throwing of exceptions”.
6.6 The Basics of Exception Handling 117
A common example of an exception you might see the JVM throw would be a
NullPointerException. Consider this code:
import java.util.*;
The problem here is that we declared myList, but forgot to initialise the object
using the new operator. We then tried to perform a method call on a non-existent
object. The JVM Is unable to comply and has thus raised an “exception” – a
NullPointerException. There is no code to deal with this situation, so the
program comes to a stop.
Java offers quite complex features to allow exceptions to be managed. As well as
built in exceptions such as NullPointerException, we can also define our
own exception building on the Java Exception class. We can then then “try” to
execute a block of code, and raise an exception if an error condition arises. We can
then create code that tries to “catch” these exceptions to see if the problem can be
solved. If the exception remains uncaught, the program will stop. The following
code example shows how we can use the idea of “try and catch”:
118 6 Dealing with Errors
In reality, this is poor coding as we should really have fixed the lack of initialisation
in the first instance. But it does illustrate the broad code approach. The try block is
executed, but a NullPointerExcepption is thrown on first execution. The code
then looks around to see if the exception is caught. The JVM identifies that it is, and
control transfers to the catch block. The method then completes and control passes
back to the code that called the method in the first place.
A method can opt to catch as many different types of exception as required. We
can also add a finally block to try and catch, The finally block provides for
code that will be executed when a return statement is encountered during the
processing of an exception.
We can define our own exceptions as well. All exceptions are sub classes of the
Java Throwable class. Here is an extract of the hierarchy of Throwable classes:
The Error sub class is concerned with low-level JVM issues. There is typically
little we can do to fix these problems. The Exception sub class is then further sub
classes into RunTimeException, and a set of “checked” exceptions such as
IOException.
Checked exceptions are checked at compile time. If some code within a method
can throw a checked exception, then the method must either handle the exception or
it must specify the exception using the throws keyword. This is because some
library classes need to be able to throw certain exceptions. For example a
6.6 The Basics of Exception Handling 119
BufferedReader object is used to open a file for reading. There may be an issue
opening the file, and the BufferedReader object will throw an IOException
in that case. So any method that utilises a BufferedReader has an obligation to
either deal with the IOException itself, or be declared as throwing the exception
so that it may be caught elsewhere. So the compiler demands that a clear under-
taking is given by methods for checked exceptions.
import java.io.*;
String line;
line = r.readLine();
while (line != null)
{
System.out.println(line);
line = r.readLine();
}
r.close();
f.close();
}
catch (IOException e)
{
System.out.println("Problem opening file");
}
}
/**
* Here we rely on something else to catch our exception.
*/
public void openFileAndReadLinesV2() throws IOException
{
FileReader f = new FileReader("C:\\test.txt");
BufferedReader r = new BufferedReader(f);
String line;
line = r.readLine();
while (line != null)
{
System.out.println(line);
line = r.readLine();
}
r.close();
f.close();
}
}
120 6 Dealing with Errors
We can catch either a general exception, or a specific one. We could opt to catch
NullPointerException, or the broader set represented by Exception. We
can also define our own exceptions by sub classing Exception. For example,
let’s say that we want to stop someone from adding blank content to an Array-
List of String:
import java.util.*;
public ExceptionExample3()
{
allNotes = new ArrayList<String>();
}
/**
* To be thrown when blank notes are input
*
* @author Kingsley Sage
* @version 1.0
*/
On first encounter, exception handling seems to be an idea that could have just as
easily been achieved using some well-chosen if statements, and some problem
solving methods. Whilst that is true, the real power of exception handling only
becomes apparent when dealing with larger programs. Checking for errors, par-
ticularly in respect of user input, can be tedious and can obscure more important
underlying code structures. Sometimes an error can occur deal in a chain or call
stack of method calls. Rather than have lots of distributed error checking code, we
can centralise the management of errors and then provide a single exception handler
to deal with them. Consider the following call stack:
In Chap. 4, we met the versatile ArrayList class and saw that it could be used to
represent a collection of any Java type. In this chapter we delve deeper into the
world of arrays and collections, and some of the algorithms that can support us in
using them.
If some grouped data in the problem domain has a fixed size or length, then
we should use a fixed size representation such as an array. Fixed length
arrays may consist of primitive data or objects.
If some grouped data in the problem data has an unknown size or length, or
it can change during the course of the program, then we should use a dynamic
size Collection type such as an ArrayList. Collections must be of object
types. Primitive types must be wrapped to participate in collections.
Fixed length arrays are not classes, and so do not have methods. However, as we
shall see later, there is a static utility class Arrays that provides some useful
algorithms for working with arrays (notably for searching and sorting). Note that we
should not refer to fixed length arrays as Collections, as the latter term is a
framework for dynamic length class based groupings such as ArrayList.
Historically there are some other advantages in using fixed length arrays in that
the amount of memory they occupy can be pre-computed, allowing the run time
environment to reserve and organise memory in an efficient manner. However, this
difference is somewhat moot in Java as organisation of memory within the JVM is
hidden from the user by design, although there can be some running time perfor-
mance issues that are optimised by using fixed length arrays.
A fixed length array has the advantage that we can store arrays of primitive data
without needing to resort to wrappers. All we need to do is to declare and initialise
the array and then use it. For example, let’s create a LotteryTicket class for
our own lottery where each ticket has 6 int numbers in the range 1–99 inclusive:
import java.util.*;
public LotteryTicket()
{
// initialise the numbers array
numbers = new int[6];
Random r = new Random();
for (int x=0; x < numbers.length; x++)
{
// Set each number in the range 1-99
int num = r.nextInt(98)+1;
numbers[x] = num;
}
}
}
7.2 Fixed Length Arrays of Primitive Types 125
To declare an array type, we add the square brackets [] after the type. We
initialise the array using the new operator, but note that this does not mean the
numbers is a Collection—it’s an array. We then use a Random object to fill the
array. Note that the array has a property length. Don’t be tempted to think that
length is a method—it lacks the brackets associated with methods. It just enables
the compiler to look up the length of the array and makes it easier to maintain and
updated code (we need only change one character if we wanted there to be 8
numbers on a ticket).
As an alternative, we can declare arrays and initialise with set values all in one
go:
public SmallDictionary()
{
myWords = new String[10];
myWords[0] = "crocodile";
myWords[1] = "zebra";
myWords[2] = "gnu";
myWords[9] = new String("parrot");
}
We see that we create the array myWords as before. But this time the array
elements are instances of type String. Remember “parrot” is just a syntactic
shorthand for new String(“parrot”). Note here that we only filled four of the
elements. The remaining elements in myWords will remain null until objects are
created and placed in them. System.out.println() will display null when
we try to display the element as text on the screen.
We can create a fixed length array of any Java object type. As before, the array
must be of a single type. If we inspect the array, we will see the characteristic
curved arrow marking inviting us to click through the array inspection to see the
underlying objects in that array.
As well as simple linear one-dimensional fixed length arrays, we can also extend to
multiple dimensions—typically two or three. Two dimensional arrays are useful for
grid style representation (e.g. a TicTacToe board) or image data. Three dimensional
arrays may find applications in CAD and physical world modelling. Higher order
dimension arrays are possible, but their uses would be more narrow, and they have
the potential to be memory intensive.
A two-dimensional array can be regarded as organised into rows and columns,
like a matrix or a spreadsheet. We address a two-dimensional array by row first, the
column. It is also necessary when declaring an array to specify the at least size of
the leading dimension (i.e. row) so that the compiler may perform array memory
access in an appropriate manner. For rectangular arrays, it is common to specify the
128 7 Deeper into Arrays and Collections
size of both the rows and columns. The following example is the start of a simple
TicTacToe board, using a 3 3 array of primitive type char:
public TicTacToeBoard()
{
board = new char[3][3];
for (int row=0; row<board.length; row++)
{
for (int col=0; col<board[row].length; col++)
{
board[row][col] = '-';
}
}
}
Note the extended use of the length property. If we use length on its own
we are referring to the size of the first dimension (the number of rows in this case).
If we want to find the number of columns for a specific row we first select the row
and then apply the length property e.g. board[1].length is the number of
columns associated with row 1. This implies that, as well as rectangular arrays, we
can have jagged arrays where the number of columns for each row is different. That
could use useful for a simple dictionary class, where the words are stored in an
array where each row corresponds to an initial letter of the alphabet. In this example
the number of words in each row can vary:
public BetterDictionary()
{
// Note we do not specify the second dimension size
allWords = new String[26][];
/**
* Display words starting with letter at row
* position c .
* @param int c where a=0, b=1 and so on.
*/
public void displayWordsForLetter(int c)
{
System.out.println("Words for letter " + c);
for (int x=0; x<allWords[c].length;x++)
{
System.out.print(allWords[c][x] + " ");
}
}
}
In fact this two-dimensional array is an “array of arrays” and this explains why
we can have either rectangular or jagged array constructions.
130 7 Deeper into Arrays and Collections
If we use the BlueJ inspector, we see the ragged nature of the array storage:
Sorting is one of the most basic operations that we perform on data in computer
science. Whether we are sorting spreadsheet data in date order, or value, or
organising a dictionary into alphabetic order to facilitate efficient search, sorting is a
key aspect of data management. There is a whole branch of algorithms in computer
science dedicated to sorting data. So it should not surprise you that Java comes with
a variety of sorting algorithms in its libraries.
We don’t concern ourselves with how the sorting process takes place. All we
need is the API and a notion of what constitutes the order we want to search
according to. Where sensible, Java applies a default notion of “natural order”, for
example arrays of int are sorted into ascending order, and String by
alphanumeric order. As we shall see later, sometimes we need to provide a little
help.
For arrays of primate types, we turn to the static utility library Arrays. Recall
that utility libraries contain only static methods and do no need to be instan-
tiated; there are only ever one of them. Revising our LotteryTicket example
from the previous section, we can sort our random numbers as simply as calling
Arrays.sort() The sort is performed “in situ” so no additional data structures
are required:
7.5 Sorting Data 131
We can similarly sort the one-dimensional dictionary. Note that we cannot leave
any element of the array of String as null, or the sorting algorithm will throw a
NullPointerException:
We cannot specify alternative orderings for arrays of primitive types. The cus-
tom ordering of arrays of objects is achieved using a static method from the
Collections class. This in turn masks the use of a Comparator class.
So we can sort fixed length arrays, but what about collections? For something
like an ArrayList of String, it’s very much the same concept, except that an
ArrayList is a Java Collection type, and so we use the Collections
utility methods to sort instead:
132 7 Deeper into Arrays and Collections
import java.util.*;
public DynamicDictionary()
{
allWords = new ArrayList<String>();
allWords.add("crocodile");
allWords.add("zither");
allWords.add("cake");
allWords.add("xylophone");
allWords.add("cat");
allWords.add("flower");
allWords.add("forest");
}
For an ArrayList of String, there is a clear natural order. This is true for
other ArrayList collections, including collections of wrapped types. But what
about situations where:
Once Java can establish how to compare two instances of a class, it can use a
general search algorithm to sort an entire collection. So the compareTo() and
compare() methods just need to compare two instances. They work in slightly
different ways:
Let’s start by building a class to represent antique artefacts that just needs a
single natural ordering. As such it implements the Comparable interface. The
interface requires providing the implementation of the compareTo() method and
then we can use Collections.sort() to do the actual sorting:
134 7 Deeper into Arrays and Collections
import java.util.*;
public AntiqueShop()
{
allAntiques = new ArrayList<Antique>();
allAntiques.add(new Antique("ming vase",5000,1400));
allAntiques.add(new Antique("painting1",10000,1950));
allAntiques.add(new Antique("harrison watch",8000,1750));
allAntiques.add(new Antique("sketchbook",200,1978));
}
import java.util.*;
We can now create as many new comparison classes as we wish. When we sort,
we provide an instance of that class and that directs the Collections.sort()
method.
We do not have to choose exclusively between these two approaches. Out
Antique class has both a natural default order, and we can create additional
Comparator classes and specify the sort order of our choosing. This is a very
flexible means of sorting any ArrayList collections.
we remove an element from an ArrayList, then the elements after the one we
removed move up to ensure that the numbering scheme remains contiguous. This
provides a very understandable linear storage. It only really has one disadvantage
and that relates to search. If we wish to find an element in a linear list, we have to
search the list one element at a time to find it. The ordering can certainly help, but
ultimately as the list grows in length, the time it takes to search for a specific
element will increase. For simpler linear list search, we note that the complexity
class of the search operation is upper bounded by O(n) where n is the number of
elements in the list.
For really large collections, this can pose performance issues. So as an alter-
native, we can consider a different means of organising and searching a collection
based on the concept of a “hash table”.
Hash tables rely on the notion of a hash function. A hash function is a mathe-
matical formula that you apply to some data that produces an integer number. That
integer number suggests where the data element should be stored. For example,
imagine the letters of the English alphabet are numbered 1 (for a) through to 26 (for
z). One hash function would be just to add the letter values together. So the has
function for the word “cat” would be 3 + 1 + 20 = 24. So we store the word in the
24th element of a collection. So if we want to see if “cat” is in our collection,
compute the hash function and look in that space in the hash table. If the space is
empty, the word was not there.
In reality, more than one word is likely to give the same has function value, so
we need to add a few more rules. For another word with a hash function value of
24, we need to move along to position 25. This is called “linear probing”. With a
few more rules we can construct a process whereby we can quickly look up ele-
ments by their hash function value in almost a constant amount of time (the probing
processes do add a bit of variation).
If the collection size is smaller than the largest value that the hash function can
produce, we just take the remainder after division (modulo) when integer dividing
the hash function by the length of the collection. So if “cat” has a hash function of
24, and the hash table has space for 10 elements, then 24 % 10 = 4 (there is a
remainder of 4) and we store “cat” at position 4.
In reality the design of efficient hash function and probing strategies is a com-
plex subject. But we do not have to concern ourselves with, as all Java objects come
with one built in. The Java language base Object has a public hashCode()
method and all objects inherit it from that class. We can see it in action:
138 7 Deeper into Arrays and Collections
s1 = "crocodile";
s2 = "teapot";
System.out.println(s1 + " " + s1.hashCode());
System.out.println(s2 + " " + s2.hashCode());
We can make use of the hash function as a means of affording almost constant time
search, The Java HashMap class provides an effective implementation of this kind
of storage. A HashMap is a Java collection that stores elements in the form of
“key-value pairs”.
HashMap is generic, so any object type can serve as the key and value, and they
do not need to be the same. The HashMap offers a range of methods and some of
the most useful are:
7.7 The HashMap Class 139
Method Description
boolean put(K key, V Adds an entry in the map with key K and value V
value)
void clear() Remover all elements from the collection
E get(Object key) Retrieve element of type E for the specified key
boolean isEmpty() Returns true if the collection is empty
V remove(Object key) Removes the element specified by the key (returns the
value V)
int size() Returns the number of elements in the collection
import java.util.*;
import java.util.*;
public HashMapExample()
{
// The key is of type String.
// The value is of type String.
allRecords = new HashMap<String,String>();
allRecords.put("John Smith","Carpenter");
allRecords.put("Mario","Plumber");
allRecords.put("Sheila Hughes","Teacher");
allRecords.put("Fred Twist","Teacher");
}
}
If we inspect the HashMap, we note that the elements are spread through the
underlying hash table. It is important that the underlying table operates with some
free space, to assist in solving the probing issue (a concept referred to as the
“loading factor”).
140 7 Deeper into Arrays and Collections
If we tried to add a new element to the HashMap with a duplicate key, it would
not be added and the size of the collection would remain unchanged.
Now we can implement a method searchFor() that searches our collection
for a given key value:
This is a simpler approach than iterating our way through a linear collection such
as an ArrayList. The only downside is that our domain may have multiple
mappings from a key to values. In a telephone directory, it’s perfectly possible that
there are two different people with the name “John Smith” and a HashMap cannot
permit duplicate keys. One solution would be to concatenate the name with an
address or other identifying data. But there are other situations where it is useful to
have collections with guaranteed unique values.
7.8 The HashSet Class 141
The HashSet class is somewhat unhelpfully named in that it does not offer any
key/value pairs like HashMap. Although the name might suggest they are closely
related, they are quite different. It is worth summarising some terminology here.
Method Description
boolean add(K e) Adds object e to the set if not already present.
void clear() Remover all elements from the set.
boolean contains(Object o) Returns true if the object o is present in the
collection.
boolean isEmpty() Returns true if the collection is empty
boolean remove(Object o) Removes the element from the set if it is present.
int size() Returns the number of elements in the collection.
import java.util.*;
If we try to add c1 again, we would note that the add() method will return
false and the size would remain three. But if we create a new Card c4, who also
happens to be “an ace of spades” then we can add it successfully to the set as it is a
distinct object. So we must exercise care to ensure that we understand the difference
between two objects that are distinct instances, and two object references that refer
to the same object in memory.
One of the most common tasks for working with any collection is the ability to
iterate through the elements. We have already seen that for an ArrayList this can
be done conveniently using a for loop. Iterating through our collections such as
HashMap and HashSet is a little less straightforward. But for many OO appli-
cations, we would like to have common patterns or means of iterating through any
collection type. Such a pattern is realised in Java using the Iterator interface.
Each collection type has an iterator() method that return an instance of an
Iterator that in turn provides methods to facilitate iterating through the
collection.
The two key methods defined by the Iterator interface are:
Method Description
boolean hasNext() Returns true if there are more elements in the collection
E next() Returns an object E that is the next object iterating through the
collection
import java.util.*;
public IterationExample()
{
myList = new ArrayList<String>();
myMap = new HashMap<String,String>(); // used later
mySet = new HashSet<String>(); // used later
}
// Using an iterator
Iterator it = myList.iterator();
while (it.hasNext())
{
String s = (String) it.next();
System.out.println(s);
}
}
}
In the Iterator example, note the use of the explicit cast in the call to next().
This is because next() is declared as returning an Object, but we know in reality
that object is a String.
Using a for loop for a HashMap would be more complex. Instead we have two
options:
• Get a set of keys and then iterate through each key to recover the corresponding
values.
• Get a set of key/value pairings (called a Map.Entry pairing).
7.9 Iterating Through Collections 145
// Using an iterator
Iterator it = mySet.iterator();
while (it.hasNext())
{
String s = (String) it.next();
System.out.println(s);
}
}
You can use an Iterator with all Java collection types. This provides a
simple and common style pattern for iterating through a collection and helps to
standardise our coding style and make the codebase more consistent and readable.
146 7 Deeper into Arrays and Collections
There are many other Java collection types for you to explore. But ArrayList,
HashMap and HashSet are arguably the most commonplace. The use of the
others would require a more thorough discussion of computer science data struc-
tures and algorithms, and that is beyond the scope of this book. At this stage, we
simply need to consider the broad purpose of lists, maps and sets, and choose them
as the right solution for any real-world problem domain we are modelling.
Adding a Graphical User Interface
8
Up to now, we have built Java programs with solely text-based interfaces relying on
features contained within BlueJ to create key objects and to call methods. Whilst
this has helped us to develop our understanding of the Java language and the
principles of programming, it is not how most applications are used in the real
world. Solely text-based applications are far and few in number, generally confined
to console applications for fine grained operating system activities and batch files.
The world, now rich in processing power, smartphones, tablets and desktop-based
OS technology, has moved to applications controlled by ever more sophisticated
Graphical User Interfaces (GUIs). So no book on Java or programming could really
claim to be rounded unless it looked at the practice and development of a GUI.
It should be noted however that GUI design and implementation is a major
undertaking in its own right. On the one hand, GUI design need to address Human
Computer Interaction (HCI) issues such as usability and accessibility. This is
complex subject rooted in psychology and a major area of study. On the other hand,
GUI design has a range of technology issues such as the ease with which a GUI can
be adapted independently of the underlying application, how it can be customised to
appear differently on multiple platforms, and the libraries and frameworks that are
needed to support it. Different languages offer a wide range of proprietary and open
source code libraries for GUI development, each with their own particular needs
and advantages and there is nowhere near the same degree of standardisation and
consensus as to which approach is best.
One of the key underlying aims of the Java language was to provide a platform
neutral language delivered through a virtual machine. This presents its own chal-
lenges as it implies that any GUI technology for use with Java must also be
platform neutral. But the range of hardware that the virtual machines may be
running on physically may vary enormously, each with their own particular limi-
tations or specific adaptations in graphical and other UI capability. So any GUI
libraries for Java must necessarily be designed in a manner relatively neutral to any
particular technology, and instead focus on a more generalised view of what con-
stitutes a GUI and how it may be delivered.
© Springer Nature Switzerland AG 2019 147
K. Sage, Concise Guide to Object-Oriented Programming,
Undergraduate Topics in Computer Science,
https://doi.org/10.1007/978-3-030-13304-7_8
148 8 Adding a Graphical User Interface
• What general strategies can we adopt to help us build a GUI. HCI issues are
beyond the scope of this book and we will instead focus on this idea in the
context of the software code level design.
• The actual libraries and capabilities within Java that we can use to implement a
GUI.
We focus on the Swing Java libraries for this second theme. Although there are
other libraries, Swing is the most established and forms a part of all Java devel-
opment toolkits. We will look at the basic concepts needed to build a useful Swing
application, and build it as a fully usable standalone application.
Note that Swing and GUI design is a very significant subject, and easily com-
mands a text book in its own right. As such, this book can only ever hope to serve
as a broad introduction to the topic, supported with some small-scale examples to
guide the reader into understanding the principles, and equip them with enough
knowledge to engage with the huge range of materials available elsewhere and
further their practical development skills and experience.
Before we get into the code details of the Swing library, we first need to consider
broader architectural ideas that will shape out ideas of how to develop a GUI, and
the relationship that the GUI will have with its underlying application codebase.
There are many drivers that will shape our thinking. Any significant application is
likely to have a team working on it. That team will likely consist of specialists in
different areas. Some may be algorithm developers, some generalist coders, some
specialists in data management, and some specialists in GUI design and imple-
mentation. They may all be producing code that needs to be integrated to build a
final working application. As with any project undertaking, the success of such a
team-based development depends on the ability of the team to work together
effectively. The requirements of a modern software project may also be driven by
need for flexibility, implementation over a range of highly diverse devices e.g.
tablet, mobile, desktop and on-line, with an emphasis on achieving functionality at
the lowest possible development cost and on short timescales. All these factors
influence the lifecycle processes through which modern software is developed.
One way that the development process itself can help us deliver on all of these
challenges is to adopt designs that promote software re-use (a key ambition of OO
design in any case) and that promote a modular approach to development. Consider
the following example:
Company X develops code in response to a customer requirement for a desktop application
with a GUI. The company delivers the codebase, but then gets a request for a similarly
functional application but with a GUI for use on mobile device.
8.1 The Model View Controller MVC Design Pattern 149
There are two possible ways that the scenario could play out:
• The GUI design is highly integrated into the underlying application codebase, so
changing the GUI would require a substantial rebuild of the entire application
from the ground up.
• The GUI is actually an independent element of the application codebase, with a
strong separation of the code that constitutes the GUI from the underlying code
base. Although the GUI will need to be redeveloped for the new device, the
underlying codebase will remain essentially unchanged and so this rework can
be delivered at a substantially lower cost than the original project.
This second scenario is highly desirable, but it requires a decision upfront at the
application design stage. The notion that the GUI should be, to the extent that is
practicable, separated from the underlying application code, is a very valuable
design idea:
• The GUI code and the application (or business logic) code are likely to be the
responsibilities of separate development teams in larger projects.
• It should be possible to redevelop the GUI element without making substantive
changes to the underling application code base. This permits the same appli-
cation to be presented (“skinned”) in many different ways, possibly dependent
on the run time environment (in the same vein that modern websites deliver
content dependent on the calling device).
• Changes to the GUI can be made without affecting the correctness or validity of
the underling application.
• Re-use of the application code base is possible, and the broad OO design
principles of encapsulation and cohesion are promoted.
Design patterns are a broad concept with applicability beyond the world of
software engineering. For example, modern cars share a common design pattern in
terms of the function and layout of their controls (such as the pedals and instru-
ments), not shared by cars from the early 20th century when manufacturers adopted
150 8 Adding a Graphical User Interface
their own conventions and standards. Software design patterns are intended to help
us construct applications in a way that are likely to be understood widely. There are
many such patterns in common and it is an extensive subject in its own right. But
for the development of a GUI, there is one architectural design pattern that we shall
focus on; the “Model View Controller” (MVC) pattern.
The MVC pattern supports the notion of the “separation of concerns” and “re-
sponsibility driven design”. As a pattern, it separates the architecture of an appli-
cation into three parts:
The complete application then requires these three elements to work together:
The Model maintains all underlying data and provides the core application
functionality. Public data from that underlying application is rendered by the View.
The Controller accepts user input and uses it to change the View (e.g. look at a
particular aspect, page, menu or sub menu), and to send requests to the Model for
operations to be performed.
If we adopt this design pattern, it should be possible to minimise the coupling
between these elements to just those interaction that are required to make the
application work. If we chose to change one of the elements e.g. redesign the
appearance of the GUI, it should be possible to achieve this with minimal impact on
the other architectural elements. Note that, in common with all design patterns, this
8.1 The Model View Controller MVC Design Pattern 151
is an aspiration and not a straitjacket. The extent that we pursue the pattern is down
to us.
In coding terms, many Java applications are built this way. Swing embraces this
design pattern, but many smaller Swing applications will tend to combine aspects of
the View and Controller together. There is nothing particularly wrong with that—it
just depends on what broader goals we have for further incremental software
engineering development. In our examples, we will pursue a design pattern with a
distinct Model on the one part, a hybrid View and Controller on the other part.
Now what we have laid the ground in terms of our software engineering
objectives, it’s time to start looking at the specifics of the Swing library.
Swing is best described as a GUI widget toolkit for the Java language, although it is
often just described as a library. It forms part of the Java Foundation Classes i.e. it
is a part of the regular Java landscape rather than a bolt on and its purpose is to
provide a means of delivering GUIs for Java programs. Swing is in turn built on top
of an earlier generation GUI toolkit the Abstract Window Toolkit (AWT).
The Swing library classes are written in Java so that they are platform inde-
pendent and these classes, along with all other Java classes, are fully documented in
the on-line documentation maintained by Oracle. The Swing classes are intended to
be highly customisable and users can take existing Swing classes and augment their
behaviour by sub classing them and/or by overriding default method implementa-
tions. Although it may seem bewildering at first, Swing is intended as a lightweight
library and embraces the MVC design pattern. The term “lightweight” here gen-
erally refers to the notion that is does not require direct interaction with the host
operating systems resources (as the Swing classes are written in Java), rather than
any reflection of how simple it is to use.
Central to the way that Swing and AWT work together is the notion of com-
ponents and containers:
In general, we need not concern ourselves with the operation of AWT container
services. They are simply a part of the infrastructure on which Swing rests. Instead
we focus on what we want the GUI to be like and how it should operate, and leave
the run time issues to the Java Virtual Machine.
To help you understand this difference, here is a partial class hierarchy for the
Swing library (package javax.swing):
The Object class is, of course, the Java language base object and not part of
Swing. We see on the left-hand side aspects of the GUI that require some inter-
action with the host Operating System and are thus sub classed from Container,
and on the right we see aspects that will interact with those Container derived
classes, rather than with the OS in any direct manner and are thus derived from the
JComponent class.
Before we move on to actually build our very first Swing application, we first need
to be clear on some basic terminology that we will use throughout this chapter.
A GUI is built from a number of thematic components:
8.3 The Taxonomy of a GUI 153
Active controls can have code associated with them to specify what happens
when the user interacts with that control. This code is organised in the form of
“listeners” that provide for the Controller element of the MVC design pattern.
Examples of listeners include responding to button press events and mouse click
events. Listeners are required to respond to events asynchronously. We cannot
predict when these events will occur, but we must be prepared to respond appro-
priately to them. So our application becomes driven by GUI events (an “event
driven” model) rather than following the predictable model laid out by straight
structured and procedural programming.
In general, all useful Swing classes are intended to be sub classed, although we
do not need to do so to create a useful GUI.
We will take one other important step forwards at this stage and free ourselves
from the BlueJ IDE to create a standalone program. This is appropriate here as the
point of a GUI is to free us from the constraints imposed by console operation, and
create an application that stands on its own merits. Here is the code listing:
import java.awt.*;
import javax.swing.*;
From BlueJ, we can just right click the class HelloWorld and select void
main(String[] args) and our application will run. All being well, we should
see:
8.4 A Simple First Swing Application 155
We can now see the taxonomy of this GUI application. We see that we create a
JFrame, then a JPanel. We add the JButton and the JLabel (a place where
text can be displayed) onto the JPanel. We then add the JPanel to the JFrame.
The JFrame needs to be “packed” (a process that assigns each element a place on
the panel) and then finally make the JFrame visible. The JFrame acquires
window controls (it is a sub class of Window) that depend on the OS model
(Windows in this case). Clicking the cross on the top right is specified as causing
the application to exit. The JButton has been created and can be clicked, but the
application has no behaviour to associate with that click event.
In this example, the HelloWorld class has implemented the Runnable
interface. Runnable requires implementation of the run() method. Any class
that implements this interface will have the run() method invoked automatically
when it is instantiated. The application runs on the Event Dispatch Thread using
SwingUtilities.invokeLater(). This is considered to be a good way of
creating a Swing application as Swing is not “thread safe”. A thread is a process on
the host machine that executes processes. A host machine will have multiple threads
with various applications or processes running on them. A modern OS gives the
impression of concurrency by allocating processing time to threads and switching
between them. Java programs can be single thread, or multi-threaded.
Multi-threaded program design is a complex subject in its own right and beyond the
scope of this book. Suffice to say that the job of the Event Dispatching Thread in
Java is to update GUI elements promptly as and when required. We can just leave it
to its own devices if we choose.
As we have defined a main() method, we are no longer dependent on BlueJ to
run our application. To create a fully standalone application, we need to create a
Java Archive (JAR) package. A JAR is an archive (set of files organised in a
directory) that contains everything needed for the JVM to run our application.
The JVM will know how to start the application as it has a main() method. The
job of the main() method is to create an instance of a class in the JVM memory
and then call appropriate methods to get matters started. We create a JAR file in
BlueJ using the “Create JAR file” option under the Project menu. We then identify
which class holds the main() method (there can only be one main() method).
We then press “continue” and specify where the JAR file is to be stored. The
resulting JAR file can then be clicked as any other application. When it is, the JVM
identifies the class than holds the main() method and invokes it. We are now free
of BlueJ.
We can (and many do) chose to ignore thread management issues; there are
alternative ways of organising our first application. We can delegate responsibility
for starting up our application to main() and we can build our own sub class from
JFrame (a common approach that underlines the fact that Swing components are
intended to be sub classed). Here is an alternative version:
156 8 Adding a Graphical User Interface
import java.awt.*;
import javax.swing.*;
this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
// Fetch a reference to the JFrame's contentPane
Container pane = this.getContentPane();
// Create a JPanel
JPanel p = new JPanel();
// Create a button
JButton b = new JButton("You can press me!");
// Create a place where we can put some text
JLabel label1 = new JLabel("Hello World");
// Add the button and the label to the panel
p.add(label1);
p.add(b);
// Add the panel to the frame
this.add(p);
// Pack the frame ready for display
this.pack();
// Make the frame visible
this.setVisible(true);
}
In both cases, we could have dispensed with the JPanel and just added the
GUI elements directly to the JFrame. But the use of the JPanel is included here
for the sake of completeness, and because it represents the more usual way of
organising a GUI.
Our first application has a button that we can press, but nothing happens as yet
when we do. We have dealt with the View aspects of the button, but there is no
corresponding Controller aspect to go with it. We need to add some code to handle
the event of a button press.
8.5 Event Handling 157
Such an event forms just one of the many kinds of events that we might need to
manage. Events correspond to user interactions with components:
Java objects can be notified when events occur. These events may occur asyn-
chronously and are detected by “listeners”. There are three basic types of listeners:
Now that we have a sense of how events are handled, we need a strategy to
determine how the potentially large range of events that our GUI could generate
should be marshalled. There are two broad options:
There are no hard and fast rules as to which is the best way to handle events. To
understand the difference between these two approaches, we build two example
applications with two buttons. First we start with a single centralised event listener:
8.6 Centralised and Distributed Event Management 159
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
{
// Implement the requirements of the ActionListener
// interface - provide the actionPerformed() method
public void actionPerformed(ActionEvent e)
{
// Interrogate e ...
if (e.getSource() == b1)
{
label1.setText("Yes");
}
else if (e.getSource() == b2)
{
label1.setText("No ");
}
}
}
}
The alternative approach would be to provide each button with its own listener:
8.6 Centralised and Distributed Event Management 161
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
new TwoIndependentButtons();
// Schedule the application to run
SwingUtilities.invokeLater(app);
}
}
In this example, each button operates independently of the other and has its own
anonymous inner class to provide a response to its own click event.
Both programs work just fine. In general, most Swing coders will tend to opt for
the distributed model. The centralised model works, but tends not to scale to larger
applications very well, resulting in very large sections of centralised code.
By now, the broad sense of how Swing is designed should be becoming clear. But
thus far we have made some simple GUI applications, but they haven’t had much of
a useful purpose other than to demonstrate core Swing concepts. Now we consider
integrating a GUI with a useful underlying application.
The underlying application is our MVC model. It should not concern itself with
any aspect of the GUI. In turn, the GUI will provide the View and Controller
elements. The View elements include JLabel, the JFrame and the JPanel and
so on. The Controller elements are provided by event handlers. It is worth
observing that Swing does have a tendency in practical implementations to blur the
separation of the View and Controller aspects of the MVC design pattern, as the
code for the event handlers is very much integrated into the GUI code base. It is
certainly possible to completely the separate the two, and the centralised event
management example gives insight as to how this might be achieved. But in
practice the combined View and Controller arrangement works just fine. The
critical separation is between the GUI and the Model.
To help us develop our ides, we shall build a small MVC style application with
the following specification:
• There shall be two buttons, one labelled “increase” and the other “decrease”.
• A number is displayed on the GUI, starting with an initial value of 0.
• Pressing either button shall increase or decrease the displayed number by 1.
• The displayed number has a minimum value of 0 and a maximum value of 10.
• If the user tries to increase or decrease the displayed number outside of these
bounds a dialog box shall appear to advise them of the limit.
Not the most interesting specification, but it will serve as a useful example. First,
we focus on the underlying application logic and create a class NumberApp:
8.7 Applying the MVC Design Pattern 163
/**
* Increases value by 1
* @return true if value is < 10, false otherwise
*/
public boolean incValue()
{
if (value < 10)
{
value++;
return true;
}
else
{
return false;
}
}
/**
* Decreases value by 1
* @return true is value is > 0, false otherwise
*/
public boolean decValue()
{
if (value > 0)
{
value--;
return true;
}
else
{
return false;
}
}
The NumberApp class provides all of the code necessary to make the appli-
cation work, but nothing in respect of a GUI. It does provide a set of public
methods that the GUI will need to work with it. The model part is capable of
executing without an GUI, and that is significant as it also means that it is suited to
automated testing using a JUnit test class:
@Test
public void test()
{
NumberApp n = new NumberApp();
// value should be zero initially ...
assertEquals(n.getValueAsString(),"0");
// try to decrease by 1, should stay at 0 ...
n.decValue();
assertEquals(n.getValueAsString(),"0");
// Increase to 3 ...
n.incValue();
n.incValue();
n.incValue();
assertEquals(n.getValueAsString(),"3");
// reset to 0 ...
n.resetCount();
assertEquals(n.getValueAsString(),"0");
// Increase to 10;
for (int i=0; i<10; i++)
{
n.incValue();
}
assertEquals(n.getValueAsString(),"10");
// Try to inc again, should stay at 10
n.incValue();
assertEquals(n.getValueAsString(),"10");
}
Automated testing of the GUI element is harder to realise. Typically, the GUI
elements would need to be tested either under control of an application scripting
language, or a documentation led process of system testing.
Now we can build a GUI to provide the View and Controller elements. The GUI
will need to:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public NumberAppGUI()
{
// Create an instance of NumberApp
app = new NumberApp();
}
label1.setText(app.getValueAsString());
}
else
{
showWarning(f);
}
}
}
);
p.add(up);
p.add(down);
//Set the contentPane size
pane.setSize(220,100);
p.setPreferredSize(new Dimension(240,120));
// Add the panel to the frame
f.add(p);
// Pack the frame ready for display
f.pack();
// Make the frame visible
f.setVisible(true);
}
• The GUI is responsible for creating the instance of the underlying application. It
does this conveniently using its constructor. The constructor is invoked as soon
as the static main() method creates the instance of the NumberAppGUI
class.
• The code adds an example of a JDialog used to provide a user alert. The
dialog box could have been further equipped with a button to close it.
• The code uses setSize(), setPreferredSize() and setLocation
() methods to exercise a more fine-grained control over where things appear on
the screen.
8.7 Applying the MVC Design Pattern 167
• The GUI communicates with the underlying application via the app object
reference.
We could redesign the GUI at any stage without having to change anything of
the NumberApp class. Thus we have achieved a strong separation of how an
application looks to how it functions. This is the separation of concerns that we
aspire to in good GUI driven application development.
Now that we are familiar with some Swing basics, we can start to investigate the
large range of components that it offers. There are substantive texts devoted to the
components and much available online. In this section we explore a selection of
useful components.
Menus are commonplace in modern GUI design. They form a part of a JFrame
(actually part of the Window super class). and are used to provide over-arching
functionality such as opening files, printing and quitting the application. We can
easily add some menu options to our existing programs.
We shall add two menu options under a thematic heading “Options”. The two
options will be:
We then call makeMenuBar() at some point in the build of our GUI prior to
packing and making the frame visible. Note the method requires the object refer-
ence to the parent JFrame as a parameter. There are some other notable points:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
• The JTextField has an event associated with it when the user presses the
enter key.
• We have once again used the lambda expression to provide the
ActionListener for the JTextField.
• The JTextArea has been placed inside a JScrollPane component that
provides for user configurable scroll bars. Note that it is the JScrollPane
object we ended up adding to the panel, not the JTextArea object.
This should now start giving the confidence to investigate the online docu-
mentation to find out more about these useful classes. Our application looks like
this:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class UsingImages implements Runnable
{
public void run()
{
// First create the JFrame window
JFrame f = new JFrame("Some text examples");
// Determine what happens when the window is closed
f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
// Fetch a reference to the JFrame's contentPane
Container pane = f.getContentPane();
// Create a JPanel
JPanel p = new JPanel();
// Create an image for the button
String path1 = "/resources/image1.png";
java.net.URL imgURL1 =
UsingImages.class.getResource(path1);
ImageIcon icon1 = new ImageIcon(imgURL1);
// Create the button
JButton b1 = new JButton("",icon1);
// Add an ActionListener for completeness
b1.addActionListener((event) -> showMessage(f));
// Add to the panel
p.add(b1);
// Create an image for the JLabel
String path2 = "/resources/image2.png";
java.net.URL imgURL2 =
UsingImages.class.getResource(path2);
ImageIcon icon2 = new ImageIcon(imgURL2);
JLabel k1 = new JLabel("",icon2,JLabel.CENTER);
p.add(k1);
// Add the panel to the frame
f.add(p);
// Pack the frame ready for display
f.pack();
// Make the frame visible
f.setVisible(true);
}
public void showMessage(JFrame f)
{
JOptionPane.showMessageDialog(f,"It's a nice button");
}
public static void main(String[] args)
{
// Create an instance of this class
UsingImages i = new UsingImages();
// Schedule the application to run
SwingUtilities.invokeLater(i);
}
}
172 8 Adding a Graphical User Interface
All being well, we get a button with an image on it, and a still image next to it:
• The images are stored in a folder called resources located in the project root
folder. We used a static method from our own class to recover the URL path
to that folder. This makes our application portable.
• Resizing images on the fly is not trivial. Our button icon is rather large! It is best
to prepare button icons at the size you intend them to be used.
At this point, we notice that although we are doing well in creating many basic
components of our GUI and getting them to do something useful, we have little in
the way of fine grained control over where everything appears on the screen estate.
The spatial organisation of the GUI components needs to be managed by yet
another component—the layout managers.
As the name suggests, the job of a layout manager is to determine how all the GUI
components are set out in a Container (such as a JFrame). The layout manager
determines the minimum, maximum and preferred sizes for the container as it
appears on the screen and then lays out the container’s children i.e. the components
added to the container. If a child component has its own layout manager then it is in
turn laid out by that manager. You are not required to use a layout manager (and our
examples thus far have not), but the default for containers if the FlowLayout.
There is a range of different layout managers that are suited to different basic
GUI designs.
8.9 Layout Managers 173
BoxLayout: components are organised in a single row or column and are not
resized when the container is resized.
GridLayout: components are equally sized and are arranged in rows and
columns.
174 8 Adding a Graphical User Interface
To use a layout manager, you pass an instance of the manager to the constructor
of the container. You can also set after construction using a setLayout()
method. Once a layout manager has been associated with the container, then you
generally (depending on the layout manager) pass additional parameters to the add
() method when adding components to the container to specify the constraints or
other parameters associated with adding the component to the container under the
auspices of the layout manager.
For example, we can create a simple 3 3 grid of JLabels under control of a
GridLayout manager. Note that the grid co-ordinate system has the origin
(x = 0, y = 0) at the top left of the container, with increasing y values going down
the container. Here, the add() method needs no new parameters as the grid is
filled in a pre-determined order:
8.9 Layout Managers 175
import java.awt.*;
import javax.swing.*;
import javax.swing.border.Border;
Resulting in:
import java.awt.*;
import javax.swing.*;
import javax.swing.border.Border;
This time, add() has an additional parameter to specify which zone we wish to
add the component to. The code produces this output:
It takes some experimentation to work out which is the best layout manager for
any specific GUI design. There is no single correct approach, and with time you
will develop you own workflow preferences and strategies. Swing is an extensive
library and it will take time to master it.
It is worth noting that Swing is not the only option for building a GUI in Java.
JavaFX is an alternative platform for building both desktop and rich Internet
applications for a wide range of devices. JavaFX was developed originally by Sun
Microsystems and is now owned by Oracle and it is likely that JavaFX will
eventually replace Swing as the standard approach for Java GUI development, but
the two will likely co-exist for many years to come, and as with all new libraries
and toolkits, it can be difficult to predict what the dominant thinking in GUI design
will be in decades to come.
Example Applications
9
In this final chapter, we look at two examples of Java applications to consolidate the
knowledge we have gained in this book. We consider a practical workflow for
designing and developing an OO program to help us understand some of the
broader software engineering issues associated with good coding.
The two examples are very different and have been chosen to illustrate different
aspects of Java. The first example is the Good Life Foods project. It is an exercise in
getting class design right, but does not consider GUI design. The second example
builds on the Guessing Game example we first saw in Chap. 2 and focusses on
improving the underlying code and incorporating a Swing GUI. Both projects can
be found in the electronic copies of the code that accompanies this book. You
should take the time to look at these code projects and use them to affirm and
develop your understanding of the key concepts discussed throughout this book.
– start with a set of user requirements (in the client’s own terms), leading to
– development of formal requirements, leading to
For this project, we will start with a statement of the user’s requirements. User
requirements are simply a free body of text that describes what our end user or
customer would like to achieve in their own words. It should not contain any
descriptions of how the solution should be coded, or any other significant technical
details—that is down to the development team. Our role will be to consider the user
requirements, and to determine an appropriate set of OO classes that we can use to
produce a working solution. The user requirements for this project are as follows:
“Good Life Foods sells and delivers fruit, vegetables and healthy cakes via
on-line ordering. Customers can order any quantity of products to form an
order. Fruit and vegetables are ordered either by integer quantity (e.g.
3 bananas) or by weight (e.g. 3 kg of potatoes). Heathy cakes are ordered as
individual items and can be produced with customised messages (such as
“Happy Birthday”).
9.2 The Good Life Foods Project 181
We now consider what OO classes are needed to solve this problem. We remind
ourselves of a set of general principles that we should embrace. Consider it your
“coding manifesto”:
• Cohesion: each class should represent one useful entity and do it well.
• Coupling: we should aim to minimise, as far as practicable, interactions
between classes.
• Encapsulation: data and methods should remain private unless they need to
be public. A class API should consist of only those things that are necessary
for the class to do its job.
• Responsibility driven design: It an entity should be responsible for performing
some behaviour, then the corresponding class should implement it.
• Consider factorising sets of class where relevant: so that classes with a
common ancestry or basic purpose share a common super class. This promotes
code re-use and avoids code duplication.
• Defensive coding: don’t provide obvious and easily preventable causes of
failure.
• Extensibility: code in a manner that permits reasonable future enhancements
and development.
• Documentation: provide meaningful comments and use Javadocs documenta-
tion conventions.
• Coding style: use appropriate variable names, and follow basic coding
conventions.
• Test classes: consider adding unit test classes to automate the process of testing
that your classes are operating correctly.
182 9 Example Applications
• use more than one word to describe the same entity (synonyms) e.g. “customer”
and “user” refer to the same entity.
• are incomplete.
• are vague.
• refer to entities at different level of abstraction e.g. “all of the items” and “fruit”,
“vegetables” and so. on
But nonetheless, the set of classes we have are a reflection of the requirements.
We further refine these using our OO design principles. Note the word “item” keeps
on reappearing in the candidate list above—that is a clear clue to the existence of a
super class. We therefore refine our thinking and produce a proposed set of actual
class for development. We also select appropriate collection types:
We can then start to assign attributes and methods into our proposed set of Java
classes. It is often simpler to start with attributes first, since the method signatures
will require prior knowledge of the attribute types. We also note that there is an
obvious OO super class for any item that Good Life Foods sells. Out attribute set
looks like this:
9.2 The Good Life Foods Project 183
Class Attributes
Customer String lastname;
String firstname;
String address;
Order myOrder;
Item String name;
double unitCost
Fruit extends Item int quantity;
Vegetable extends Item double weight;
HealthyCake extends Item String msg;
Order String uniqueReference;
ArrayList < Item > allItems;
double costOfItems;
double deliveryCharge;
GoodLifeFoods HashMap < String,Customer > allOrders;
We double check this list of attributes covers all of the needs of the original user
requirements and it appears to be suitable. This process may require several iter-
ations to get right. There is no single solution, just ones that make sense, and
everything else.
Now we can determine what methods are needed. For clarity, we can ignore
accessor and mutator methods as it is likely that all classes will need them to some
extent. We might need to consider whether multiple constructor methods are
required. As long as we engage in sufficient design to make the path to our
development clear, we will have done enough. We also need to consider whether
any static (factory/utility) methods are required. Here is a list of possible key
public methods:
@Test
/** Create a customer, add some items to their order.
*/
public void placeAnOrder()
{
Customer c = new Customer("Smith","John","1 The Avenue");
c.addItemToOrder(new Fruit("Apples",0.20,5));
c.addItemToOrder(new Vegetable("potatoes",1.0, 2.0));
c.addItemToOrder(new HealthyCake("Chocolate Cake",
10.00, "Happy Birthday"));
}
9.2 The Good Life Foods Project 185
The final BlueJ class diagram (including the test class) looks like this:
We note that:
This is just one possible design. You should examine the full code listing to
make sense of what each of these methods actually does.
There are many ways that the existing Good Life Foods project could be
improved. If you want to develop the project further, here are some suggestions for
improvements:
• There are no validity checks on the supplied parameters for any of the classes.
E.g. we can create an Item with a negative cost, or set the address of a
Customer to a blank String.
• The Item objects are being created “on the fly” within the Customer class. So
a Customer could devise any Fruit, Vegetable or HealthyCake they
imagined to purchase. In reality, GoodLifeFoods sell from a stock list, and
186 9 Example Applications
You will likely think of many other useful ways that this project could be
developed further.
For this project, we revisit the simple game project we first used in Chap. 2, but we
make further enhancements to it, and build a suitable GUI to go with it. As before,
we shall start with a statement of our user requirements:
This game does not really require a multiple class solution. There is only really
one underlying entity and that is the game itself. However, we do want a GUI, and
the MVC design pattern requires us to separate the underlying application logic
from the GUI. By way of an analysis, we come up with a list of the attributes and
methods that the underlying GuessingGame class will require:
9.3 The Guessing Game Project 187
Attribute Description
int answer; The answer generated by the game
int numTurns; The current number of turns the user has made.
int[] allGuesses; An array of int containing all the guesses the user had made
private int The maximum number of turns the user may make
maxTurns;
Method Description
public int getTurns() Accessor for the current number of turns
public int getAnswer() Accessor for the correct answer
public int getMaxTurns() Accessor for maxTurns
public int analyseGuess Analyse the current guess. Return –2 if the guess
(int guess) has been tried before, –1 if the guess is too low, +1 if
the guess is too high, and 0 if the guess is correct
private boolean Returns true if the guess has been tried before,
triedThatAlready(int false otherwise
guess)
public void resetGame() Resets all game attributes to suitable default values
• A JTextField where we can enter our guess. Pressing enter will validate our
guess.
• A JLabel where the current number of guesses is displayed.
• A JLabel where messages for “Too high” and “Too low” can be displayed.
@Test
public void test1()
{
int someGuess;
GuessingGame game = new GuessingGame();
int x = game.getAnswer();
someGuess = x - 1;
// someGuess is too low ...
assertEquals(game.analyseGuess(someGuess),-1);
assertEquals(game.getTurns(),1);
someGuess = x + 1;
// someGuess is too high ...
assertEquals(game.analyseGuess(someGuess),1);
assertEquals(game.getTurns(),2);
// Try that guess again. Should be marked
// as a repeat guess.
assertEquals(game.analyseGuess(someGuess),-2);
// Try the right answer ...
someGuess = x;
assertEquals(game.analyseGuess(someGuess),0);
// Check maxTurns ...
assertEquals(game.getMaxTurns(),10);
}
We start the game either by invoking main() from BlueJ, or we can assemble a
JAR file for standalone operation. If we do, we see the form of the GUI:
9.4 Final Thoughts 189
The two examples shown in this chapter should provide insight into the broad OO
design process and stimulate you into developing them further to develop your
understanding of writing good Java programs.
The Java landscape is large, with ever growing libraries for you to investigate
and use in your own applications. Part of the skill of being a good coder is to have
the intuition that “if a class seems to solve a useful problem often” then there
probably is a class in a library somewhere that does what you need. Once you locate
it, the key to getting the benefit from it is the ability to use the API documentation
properly.
Programming is, in many respects, part science, part common sense, and part
creative. This last point reflects the fact that there are inevitably a large number of
ways that any problem can be solved, each with their own relative merits. The more
you code, the more you think about the way that you code. Discuss coding with
others—even hardened professionals find new ways of solving problems through
dialogue with other. There is always something new to be learned, and a new way
of looking at an old problem.
Above all, learn to think ahead about what you do. The Object Oriented para-
digm is very powerful, and Java delivers on it, but it is worth reflecting in a modern
world dominated by technology and software systems that the most powerful tool in
creating programs and applications is our imagination, a pen and paper, an ana-
lytical mind and a good cup of tea.
Index
D H
Dalvik, 8 Has-a, 56
Debugger, 106 hashCode(), 137
Declaring an object, 50 Hash function, 137
default, 25, 94 HashMap, 138
Defensive coding, 181 HashSet, 141
Design errors, 101 Hash table, 137
Design patterns, 63, 149 hasNext(), 143
Difference Engine, 3 Hello World, 11
Digital control, 1 Hierarchical structures, 75
Documentation, 181 Highly cohesive, 39
Double, 17, 72 Human Computer Interaction (HCI), 147
do .. while loop, 26, 28
Duplicate key, 140 I
Dynamic type, 88 if, 22
ImageIcon, 170
E Immutable, 60
else … if, 23 import, 34
Encapsulation, 40, 63, 181 import directive, 58
ENIAC, 4 Infinite loop, 28
Enigma, 3 Inheritance, 80
Entity, 40 Inherited, 79
Environmental errors, 101 Initialising an object, 50
Index 193
WindowListener, 157 Z
Workbench, 12 Zero indexed, 62
Wrapper class, 72 Zero referenced, 125
Write Once, Run Anywhere (WORA), 7