E-Notes Object Oriented Programming PDF
E-Notes Object Oriented Programming PDF
ON
OBJECT ORIENTED
PROGRAMMMING
B. Tech (CSE/IT)
Semester-Vth
CODE: PCC-CS-503
SUBJECT NAME: OBJECT ORIENTED PROGRAMMING
NO OF CREDITS: 3
REFERENCES
1. Barbara Liskov, Program Development in Java, Addison-Wesley, 2001
Table of Contents
MODULE 1: .................................................................................................................................. 6
ABSTRACT DATA TYPES ................................................................................................................. 6
1.1 Decomposition and Abstraction ............................................................................................... 6
1.2 ABSTRACTION .................................................................................................................... 7
1.2.1 Abstraction by Parameterization...................................................................................................................................8
1.2.2 Abstraction by Specification .........................................................................................................................................9
1.2.3 Kinds of Abstractions..................................................................................................................................................10
1.3 Invariants ............................................................................................................................. 10
1.3.1 Immutability ................................................................................................................................................................10
1.3.2 Immutable Wrappers Around Mutable Data Types..................................................................................................14
1.3.3 How to Establish Invariants ........................................................................................................................................14
1.4 Abstract Data Types (ADTs)-Implementation .......................................................................... 15
1.4.1 The Stack ADT...........................................................................................................................................................16
1.4.2 The Queue ADT.........................................................................................................................................................20
Module-2 ................................................................................................................................... 26
2.1 INTRODUCTION..................................................................................................................... 26
2.1.1 Abstraction...................................................................................................................................................................26
2.1.2 Encapsulation ..............................................................................................................................................................27
2.1.3 Polymorphism .............................................................................................................................................................27
2.1.4 Inheritance ...................................................................................................................................................................27
2.1.5 Association ..................................................................................................................................................................28
2.1.6 Aggregation .................................................................................................................................................................28
2.1.7 Composition ................................................................................................................................................................28
2.2 Characteristics of OOP ........................................................................................................... 29
2.2.1 Abstraction...................................................................................................................................................................29
2.2.2 Interfaces......................................................................................................................................................................30
2.2.3 Encapsulation ..............................................................................................................................................................31
2.2.4 Inheritance ...................................................................................................................................................................32
2.2.5 Polymorphism .............................................................................................................................................................33
2.3 Object-Oriented Programming Basics With Java...................................................................... 35
2.3.1 Encapsulation and data hiding ....................................................................................................................................36
2.3.2 The interaction of objects using polymorphism .........................................................................................................37
2.4 Object-Oriented Programming By Example ............................................................................. 39
2.5 A Start-to-Finish Example ...................................................................................................... 60
2.6 TEMPLATES ..................................................................................................................... 64
2.6.1 Function templates ......................................................................................................................................................64
2.6.2 Class Templates ..........................................................................................................................................................67
2.6.3 Template Specialization ..............................................................................................................................................68
2.6.4 Non-type parameters for templates.............................................................................................................................70
2.6.5 Templates and multiple-file projects...........................................................................................................................71
2.7 Memory Management..................................................................................................... 71
2.8 Object Oriented Memory Management ............................................................................ 74
Module-3 ................................................................................................................................... 75
Design Patterns .......................................................................................................................... 75
3. 1 Introduction to Design Patterns ............................................................................................ 75
3.2 Classification of Design Patterns............................................................................................. 75
3.2.1 Creational Patterns.......................................................................................................................................................75
3.2.2 Structural Patterns........................................................................................................................................................84
3.2.3 Behavioral Patterns......................................................................................................................................................90
MODULE 4: ................................................................................................................................ 99
GENERIC TYPES AND COLLECTIONS............................................................................................ 100
4.1 Introduction........................................................................................................................ 100
4.2 Generics ............................................................................................................................. 101
4.2.1 Generics versus Templates .......................................................................................................................................102
4.3 Generics and Subtyping ....................................................................................................... 103
4.4 Wildcards with extends ....................................................................................................... 104
4.5 Wildcards with super........................................................................................................... 105
4.5.1 Wildcards versus Type Parameters..........................................................................................................................106
4.5.2 Wildcard Capture ................................................................................................................................................108
4.5.3 Restrictions on Wildcards .........................................................................................................................................109
4.6 Generic Methods and Varargs .............................................................................................. 111
4.7 Set Interface ....................................................................................................................... 112
4.7.1 Set Interface Basic Operations ..................................................................................................................................113
4.7.2 Set Interface Bulk Operations ...................................................................................................................................115
4.8 The List Interface ................................................................................................................ 116
4.8.1 Collection Operations................................................................................................................................................117
4.8.2 Positional Access and Search Operations.................................................................................................................117
4.8.3 Iterators......................................................................................................................................................................119
4.8.4. Range-View Operation ............................................................................................................................................120
4.9 Queue Interface .................................................................................................................. 123
4.10 The Deque Interface .......................................................................................................... 125
4.10.1 Insert ........................................................................................................................................................................125
4.10.2 Remove ...................................................................................................................................................................125
4.10.3 Retrieve....................................................................................................................................................................125
4.11 The Map Interface............................................................................................................. 126
4.11.1 Map Interface Basic Operations .............................................................................................................................127
4.11.2 Map Interface Bulk Operations ..............................................................................................................................128
4.11.3 Collection Views.....................................................................................................................................................129
4.12 Object Ordering ................................................................................................................ 130
4.12.1 Writing Your Own Comparable Types.................................................................................................................131
4.13 The SortedSet Interface ..................................................................................................... 133
4.13.1 Set Operations .........................................................................................................................................................134
4.13.2 Standard Constructors.............................................................................................................................................134
4.13.3 Range-view Operations ..........................................................................................................................................134
4.13.4 Endpoint Operations ...............................................................................................................................................135
4.13.5 Comparator Accessor.............................................................................................................................................136
4.14 The SortedMap Interface ................................................................................................... 136
4.14.1 Map Operations.......................................................................................................................................................136
4.14.2 Standard Constructors.............................................................................................................................................137
MODULE 5: ............................................................................................................................ 138
GRAPHICAL PROGRAMMING WITH SCALA AND SWING................................................. 138
5.1 Basics-Creating a Window................................................................................................... 139
5.2 Responding to an event........................................................................................................ 140
5.3 The scala.swing package...................................................................................................... 141
5.3.1 Classes SwingApplication and SimpleSwingApplication ......................................................................................142
5.4 Laying out components ....................................................................................................... 142
5.4.1 A strongly typed, concise container interface...........................................................................................................142
5.4.2 Extending Layout Container.....................................................................................................................................143
5.5 Reacting to events ............................................................................................................... 144
5.5.1 Java Swing Listeners versus scala.swing Reactions ................................................................................................145
5.5.2 Publishers and Reactors............................................................................................................................................146
5.5.3 Actions.......................................................................................................................................................................146
5.6 List Views and Tables ......................................................................................................... 147
5.6.1 Class ListView ..........................................................................................................................................................147
5.6.2 Renderers...................................................................................................................................................................148
5.6.3 Class Table ................................................................................................................................................................150
5.7 CustomPainting .................................................................................................................. 150
5.8 Panels................................................................................................................................. 151
5.9 Concurrency in Java Swing.................................................................................................. 155
5.9.1 Initial Threads............................................................................................................................................................156
5.9.2 Event Dispatch Thread..............................................................................................................................................156
5.9.3 Worker Threads and SwingWorker.........................................................................................................................157
MODULE 6: ............................................................................................................................ 159
THE SOFTWARE DEVELOPMENT PROCESS ....................................................................... 159
6.1 The Software Life Cycle ...................................................................................................... 159
6.2 Requirement Analysis ......................................................................................................... 160
6.3 Requirement Specification ................................................................................................... 162
6.3.1 Data Models ..............................................................................................................................................................162
6.3.1.1 Subsets .................................................................................................................. 162
6.3.1.2 Relations................................................................................................................ 163
6.3.2 Requirements Specification ......................................................................................................................................166
6.4 Designs............................................................................................................................... 168
MODULE 1:
The basic paradigm for tackling any large problem is clear-we must “divide and rule”. Our goal
in decomposing a program is to create modules that interact with one another in simple, well-
defined ways. If we achieve this goal, different people will be able to work on different modules
independently, without needing much communication among themselves, and yet the modules
will work together. In addition, during program modification and maintenance, it will be
possible to modify some of the modules without affecting all of the others.
When we decompose a problem, we factor it into separable subproblems in such a way that
1.2 ABSTRACTION
The process of abstraction can be seen as an application of a many-to-one mapping. It allows
us to forget information and consequently to treat things that are different as if they were the
same. We can simplify our analysis by separating attributes that are relevant from those that
are not. It is crucial to remember, however, that relevance often depends upon context.
For example, A more specifically computer- oriented example that is useful in many programs
is the concept of a “file”. Files abstract from raw storage and provide long-term, online storage
of named entities. Operating Systems differ in their realizations of files; for example, the
structure of the filenames differs from system to system, as does the way in which the files are
stored on secondary storage devices.
In recent years, programmers have become dissatisfied with the level of abstraction generally
achieved in high level language programs. Consider, for example, the program fragments in
Figure 1.1:
//search upwards
found=false;
for(int i=0; i<a.length; i++)
if(a[i]==e)
{
z=i;
found=true;
}
//search downwards
found=false;
for(int i=<a.length; i>=0; i++)
if(a[i]==e)
{
z=i;
found=true;
}
Figure 1.1:
At the level of abstraction defined by the programming language, these fragments are clearly
different: if there is an occurrence of e in a, one fragment finds the index of the first occurrence
and the other, the index of the last. If e does not occur in a, one sets i to a.length and the other
to -1.
A preferable alternative is to design into the language mechanisms that allow programmers to
construct their own abstractions as they need them. One common mechanism is the use of
procedures. By separating procedure definition and invocation, a programming langua ge
makes two important methods of abstraction possible: abstraction by parameterization and
abstraction by specification.
Abstraction by parameterization abstracts from the identity of the data by replacing them
with parameters. It generalizes modules so that they can be used in more situations.
Abstraction by specification abstracts from the implementation details (how the module is
implemented) to the behaviour users can depend on (what the module does). It isolates
modules from one another’s implementations; we require only that a module’s
implementation supports the behaviour being relied on.
1.2.1 Abstraction by Parameterization
Abstraction by parameterization, through the introduction of parameters, allows us to represent
a potentially infinite set of different computations with a single program text that is an
abstraction of all of them. For example,
X*x + y*y
describes a computation that adds the square of the value stored in the variable x to the square
of the value stored in the variable y.
On the other hand, the lambda expression
λ x,y : int(x*x+y*y)
describes the set of computations that square the value stored in some integer variable, which
we shall temporarily refer to as x, and add it to the square of the value stored in another integer
variable, which we shall temporarily call y. In such a lambda expression, we refer to x and y
as the formal parameters and x*x+y*y as the body of the expression. We invoke a computatio n
by binding the formal parameters to arguments and then evaluating the body.
For example,
λ x,y : int(x*x+y*y)(w,z) is identical in meaning to w*w+z*z
Programmers often use abstraction by parameterization without even noticing that they are
doing so. For example, suppose we ned a procedure that sorts an array of integers a. At some
time in the future, we shall probably have to sort some other array, perhaps even somewhere
else in the same program. It is highly unlikely, however, that every array we need to sort will
be named a; we therefore invoke abstraction by parameterization to generalize the procedure
and thus make it more useful.
Abstraction by parameterization is an important means of achieving generality in programs. It
is an extremely powerful mechanism. Not only does it allow us to describe a large (even
infinite) number of computations relatively simply, but it is easily and efficiently realizab le in
programming languages.
1.2.2 Abstraction by Specification
Abstraction by specification allows us to abstract from the computation described by the body
of a procedure to the end that procedure was designed to accomplish. We do this by associating
with each procedure a specification of its intended effect and then considering the meaning of
a procedure call to be based on this specification rather than on the procedure’s body.
We are making use of abstraction by specification whenever we associate with a procedure a
comment that is sufficiently informative to allow others to use that procedure without looking
at its body. A good way to write such comments is to use pairs of assertions.
The requires assertion (or precondition) of a procedure specifies something that is assumed to
be true on entry to the procedure.
The effects assertion (or postcondition) of a procedure specifies something that is assumed to
be true at the completion of any invocation of the procedure for which the precondition was
satisfied.
Consider, for example, the sqrt procedure in Figure 1.2. Because a specification is provided,
we can ignore the body of the procedure and take the meaning of the procedure call y=sqrt(x)
to be “If x is greater than zero when the procedure is invoked then after the execution of the
procedure, y is an approximation to the square root of x.” Notice that the requires and efforts
assertions permit us to say nothing about the value of y if x is not greater than zero. This is
important, since a user might otherwise quite reasonably assume that sqrt(0) returned a
meaningful answer.
In using a specification to reason about the meaning of a procedure call, we follow two distinct
rules:
1. After the execution of the procedure, we can assume that the post condition holds
provided the precondition held when the call was made.
2. We can assume only those properties that can be inferred from the post condition.
The two rules mirror the two benefits of abstraction by specification.
The first asserts that users of the procedure need not bother looking at the body of the procedure
in order to use it. They are thus spared the effort of first understanding the details of the
computations described by the body and then abstracting from these details to discover that the
procedure really does compute an approximation to the square root of its argument.
The second rule makes it clear that we are indeed abstracting from the procedure body, that is,
omitting some irrelevant information.
1.2.3 Kinds of Abstractions
Abstraction by parameterization and by specification are powerful methods for program
construction. They enable us to define three different kinds of abstractions : procedural
abstraction, data abstraction, and iteration abstraction. In addition, each procedural, data, and
iteration abstraction will incorporate both methods within it.
Procedural abstraction is a powerful tool. It allows us to extend the virtual machine defined by
a programming language by adding a new operation. This kind of extension is most useful
when we are dealing with problems that are conveniently decomposable into independent
functional units.
Data abstraction consists of a set of objects and a set of operations characterizing the behaviour
of the objects. It is often more fruitful to think of adding new kinds of data objects to the virtua l
machine. The behaviour of the data objects is expressed most naturally in terms of a set of
operations that are meaningful for those objects. This set includes operations to create objects,
to obtain information from them, and possibly to modify them.
Iteration abstraction is used to avoid having to say more than is relevant about the flow of
control in a loop. A typical iteration abstraction might allow us to iterate over all the elements
of any set without constraining the order in which the elements are to be processed.
Type Hierarchy allows us to abstract from individual data types to families of related types.
All members of the family have operations in common; these common operations are defined
in the supertype, the type that is the ancestor of all the others, which are its subtypes. Type
families abstract from the details that distinguish members of a family from one another to their
commonalities. They allow programmers to ignore the difference most of the time.
1.3 Invariants
The property of a good abstract data type is that it preserves its own invariants. An invariant
is a property of a program that is always true, for every possible runtime state of the program.
Immutability is one crucial invariant: once created, an immutable object should always
represent the same value, for its entire lifetime. Saying that the ADT preserves its own
invariants means that the ADT is responsible for ensuring that its own invariants hold. It
doesn’t depend on good behavior from its clients.
When an ADT preserves its own invariants, reasoning about the code becomes much easier. If
you can count on the fact that Strings never change, you can rule out that possibility when
you’re debugging code that uses Strings – or when you’re trying to establish an invariant for
another ADT that uses Strings. Contrast that with a string type that guarantees that it will be
immutable only if its clients promise not to change it. Then you’d have to check all the places
in the code where the string might be used.
1.3.1 Immutability
Here’s a specific example:
/**
* This immutable data type represents a tweet from Twitter.
*/
public class Tweet {
/**
* Make a Tweet.
* @param author Twitter user who wrote the tweet.
* @param text text of the tweet
* @param timestamp date/time when the tweet was sent
*/
public Tweet(String author, String text, Date timestamp) {
this.author = author;
this.text = text;
this.timestamp = timestamp;
}
}
How do we guarantee that these Tweet objects are immutable – that, once a tweet is created,
its author, message, and date can never be changed?
The first threat to immutability comes from the fact that clients can — in fact must — directly
access its fields. So nothing’s stopping us from writing code like this:
Tweet t = new Tweet("justinbieber",
"Thanks to all those beliebers out there inspiring me everyday",
new Date());
t.author = "rbmllr";
This is a trivial example of representation exposure, meaning that code outside the class can
modify the representation directly. Rep exposure like this threatens not only invariants, but also
representation independence. We can’t change the implementation of Tweet without affecting
all the clients who are directly accessing those fields.
Fortunately, Java gives us language mechanisms to deal with this kind of rep exposure:
public class Tweet {
}
The private and public keywords indicate which fields and methods are accessible only
within the class and which can be accessed from outside the class. The final keyword also
helps by guaranteeing that the fields of this immutable type won’t be reassigned after the object
is constructed.
But that’s not the end of the story: the rep is still exposed! Consider this perfectly reasonable
client code that uses Tweet:
/** @return a tweet that retweets t, one hour later*/
public static Tweet retweetLater(Tweet t) {
Date d = t.getTimestamp();
d.setHours(d.getHours()+1);
return new Tweet("rbmllr", t.getText(), d);
}
retweetLater takes a tweet and should return another tweet with the
same message (called a
retweet) but sent an hour later. The retweetLater method might be part of a system that
automatically echoes funny things that Twitter celebrities say.
What’s the problem here? The getTimestamp call returns a reference to the same date object
referenced by tweet t. t.timestamp and d are aliases to the same mutable object. So when that
date object is mutated by d.setHours(), this affects the date in t as well, as shown in the
snapshot diagram.
Tweet’s immutability invariant has been broken. The problem is that Tweet leaked out a
reference to a mutable object that its immutability depended on. We exposed the rep, in such a
way that Tweet can no longer guarantee that its objects are immutable. Perfectly reasonable
client code created a subtle bug.
We can patch this kind of rep exposure by using defensive copying: making a copy of a mutable
object to avoid leaking out references to the rep. Here’s the code:
Mutable types often have a copy constructor that allows you to make a new instance that
duplicates the value of an existing instance. In this case, Date’s copy constructor uses the
timestamp value, measured in seconds since January 1, 1970. As another example,
StringBuilder’s copy constructor takes a String. Another way to copy a mutable object is
clone(), which is supported by some types but not all. There are unfortunate problems with
the way clone() works in Java. For more, see Josh Bloch, Effective Java, item 10.
So we’ve done some defensive copying in the return value of getTimestamp. But we’re not
done yet! There’s still rep exposure. Consider this (again perfectly reasonable) client code:
Again, the immutability of Tweet has been violated. We can fix this problem too by using
judicious defensive copying, this time in the constructor:
public Tweet(String author, String text, Date timestamp) {
this.author = author;
this.text = text;
this.timestamp = new Date(timestamp.getTime());
}
In general, you should carefully inspect the argument types and return types of all your ADT
operations. If any of the types are mutable, make sure your implementation doesn’t return direct
references to its representation. Doing that creates rep exposure.
You may object that this seems wasteful. Why make all these copies of dates? Why can’t we
just solve this problem by a carefully written specification, like this?
/**
* Make a Tweet.
* @param author Twitter user who wrote the tweet.
* @param text text of the tweet
* @param timestamp date/time when the tweet was sent. Caller must never
* mutate this Date object again!
*/
public Tweet(String author, String text, Date timestamp) {
This approach is sometimes taken when there isn’t any other reasonable alternative – for
example, when the mutable object is too large to copy efficiently. But the cost in your ability
to reason about the program, and your ability to avoid bugs, is enormous. In the absence of
compelling arguments to the contrary, it’s almost always worth it for an abstract data type to
guarantee its own invariants, and preventing rep exposure is essential to that.
The downside here is that you get immutability at runtime, but not at compile time. Java won’t
warn you at compile time if you try to sort() this unmodifiable list. You’ll just get an exception
at runtime. But that’s still better than nothing, so using unmodifiable lists, maps, and sets can
be a very good way to reduce the risk of bugs.
make the invariant true in the initial state of the object; and
ensure that all changes to the object keep the invariant true.
creators and producers must establish the invariant for new object instances; and
mutators and observers must preserve the invariant.
The risk of rep exposure makes the situation more complicated. If the rep is exposed, then the
object might be changed anywhere in the program, not just in the ADT’s operations, and we
can’t guarantee that the invariant still holds after those arbitrary changes. So the full rule for
proving invariants is:
then the invariant is true of all instances of the abstract data type.
try - guarded fragment of code that might thrown an exception when executed.
catch - block of code to which control jumps on catching an exception. The block contains
code to analyze the exception and apply an appropriate solution.
If an exception is never caught in a method, it will propagate upwards along the sequence
of method calls until the user sees it.
java.util has a built- in stack data structure class - we will define our own interface and
not use the built-in class
/**
* Removes and returns the top element from the stack.
* @return element removed
* @throws StackEmptyException if the array storing the stack is empty
*/
public E pop() {
if (isEmpty()) throw new StackEmptyException ("Stack is empty”);
E answer = data[t];
data[t] = null; // dereference to help garbage collection
t--;
return answer;
}
/**
* Returns the number of elements in the stack.
* @return number of elements in the stack
*/
public int size() { return list.size(); }
/**
* Tests whether the stack is empty.
* @return true if the stack is empty, false otherwise
*/
public boolean isEmpty() { return list.isEmpty(); }
/**
* Inserts an element at the top of the stack.
* @param element the element to be inserted
*/
public void push(E element) { list.addFirst(element); }
/**
* Returns, but does not remove, the element at the top of the stack.
* @return top element in the stack
* @throws StackEmptyException if the list storing the stack is empty
*/
public E top() {
if (isEmpty()) throw new StackEmptyException ("Stack is empty”);
return list.first();
}
/**
* Removes and returns the top element from the stack.
* @return element removed
* @throws StackEmptyException if the list storing the stack is empty
*/
public E pop() {
if (isEmpty()) throw new StackEmptyException ("Stack is empty”);
return list.removeFirst();
}
Applications of Stacks
Direct applications
Page-visited history in a Web browser
Undo sequence in a text editor
Chain of method calls in the Java Virtual Machine
Indirect applications
Auxiliary data structure for algorithms
Component of other data structures
Array-based Queue
Use an array of size N in a circular fashion
Two variables keep track of the front and size
f index of the front element
sz number of stored elements
When the queue has fewer than N elements, array location r = (f + sz) mod N is the first
empty slot past the rear of the queue
Queue Operations
We use the modulo operator (remainder of division)
// constructors
public ArrayQueue() {this(CAPACITY);} // constructs queue with default capacity
// methods
/**
* Returns the number of elements in the queue.
* @return number of elements in the queue
*/
public int size() { return sz; }
public boolean isEmpty() { return (sz == 0); } // Tests whether the queue is empty.
/**
* Inserts an element at the rear of the queue.!
* This method runs in O(1) time.!
* @param e new element to be inserted!
* @throws QueueFullException if the array storing the elements is full!
*/
public void enqueue(E e) throws QueueFullException {
if (sz == data.length) throw new QueueFullException ("Queue is full");
int avail = (f + sz) % data.length; // use modular arithmetic
data[avail] = e;
sz++;
}
/**
* Returns, but does not remove, the first element of the queue.!
* @return the first element of the queue!
* @throws QueueEmptyException if the array storing the elements is empty!
*/
public E first() throws QueueEmptyException {
if (isEmpty()) throws new QueueEmptyException() ;
return data[f];
}
/**
* Removes and returns the first element of the queue.
* @return element removed
* @throws QueueEmptyException if the queue is empty)
*/
public E dequeue() throws QueueEmptyException {
if (isEmpty()) throw new QueueEmptyException ();
E answer = data[f];
data[f] = null; // dereference to help garbage collection
f = (f + 1) % data.length;
sz--;
return answer;
}
Applications of Queues
Direct applications
Waiting lists, bureaucracy
Access to shared resources (e.g., printer)
Multiprogramming
Indirect applications
Auxiliary data structure for algorithms
Component of other data structures
Deque supports insertion and deletion from the front and back of the queue
The deque ADT supports the following fundamental methods
InsertFirst(Q:ADT, e:element):ADT – inserts e at the beginning of the deque
InsertLast(Q:ADT, e:element):ADT – inserts e at the end of the deque
RemoveFirst(Q:ADT):ADT – removes the first element
RemoveLast(Q:ADT):ADT – removes the last element
First(Q:ADT): element – returns the first element
Last(Q:ADT):element – returns the last element
Deque implementations
Module-2
2.1 INTRODUCTION
Java is a class-based object-oriented programming (OOP) language that is built around the
concept of objects. OOP concepts (OOP) intend to improve code readability and reusability by
defining how to structure a Java program efficiently. The main principles of object-oriented
programming are:
1. Abstraction
2. Encapsulation
3. Inheritance
4. Polymorphism
5. Association
6. Aggregation
7. Composition
Java comes with specific code structures for each OOP principle. For example, the extends
keyword for inheritance or getter and setter methods for encapsulation.
OOP concepts allow us to create specific interactions between Java objects. They make it
possible to reuse code without creating security risks or making a Java program less readable.
2.1.1 Abstraction
Abstraction aims to hide complexity from the users and show them only the relevant
information. For example, if you want to drive a car, you don’t need to know about its interna l
workings. The same is true of Java classes. You can hide internal implementation details by
using abstract classes or interfaces. On the abstract level, you only need to define the method
signatures (name and parameter list) and let each class implement them in their own way.
Abstraction in Java:
Encapsulation in Java:
2.1.3 Polymorphism
Polymorphism refers to the ability to perform a certain action in different ways. In Java,
polymorphism can take two forms: method overloading and method overriding. Method
overloading happens when various methods with the same name are present in a class. When
they are called they are differentiated by the number, order, and types of their parameters.
Method overriding occurs when the child class overrides a method of its parent.
Polymorphism in Java:
2.1.4 Inheritance
Inheritance makes it possible to create a child class that inherits the fields and methods of the
parent class. The child class can override the values and methods of the parent class, however
it’s not necessary. It can also add new data and functionality to its parent. Parent classes are
also called super classes or base classes, while child classes are known as subclasses or derived
classes as well. Java uses the extends keyword to implement the principle of inheritance in
code.
Inheritance in Java:
A class (child class) can extend another class (parent class) by inheriting its features.
Implements the DRY (Don’t Repeat Yourself) programming principle.
Improves code reusability.
Multilevel inheritance is allowed in Java (a child class can have its own child class as
well).
Multiple inheritances are not allowed in Java (a class can’t extend more than one
class).
2.1.5 Association
Besides the four main principles of OOP, Java also works with three further concepts
(association, aggregation, composition) you can make use of when designing your programs.
Aggregation is a special form of association, while composition is a special form of
aggregation.
Association simply means the act of establishing a relationship between two unrelated classes.
For example, when you declare two fields of different types (e.g. Car and Bicycle) within the
same class and make them interact with each other, you have performed association.
Association in Java:
2.1.6 Aggregation
Aggregation is a narrower kind of association. It occurs when there’s a one-way (HAS-A)
relationship between the two classes you associate through their objects. For example, every
Passenger has a Car but a Car doesn’t necessarily have a Passenger. When you declare the
Passenger class, you can create a field of the Car type that shows which car the passenger
belongs to. Then, when you instantiate a new Passenger object, you can access the data stored
in the related Car as well.
Aggregation in Java:
One-directional association.
Represents a HAS-A relationship between two classes.
Only one class is dependent on the other.
2.1.7 Composition
Compositionis a stricter form of aggregation. It occurs when the two classes you associate are
mutually dependent on each other and can’t exist without each other. For example, take a Car
and an Engine class. A Car cannot run without an Engine, while an Engine also can’t functio n
without being built into a Car. This kind of relationship between objects is also called a PART-
OF relationship.
Composition in Java:
Abstract classes
An abstract class is a superclass (parent class) that cannot be instantiated. You need to
instantiate one of its child classes if you want to create a new object. Abstract classes can have
both abstract and concrete methods. Abstract methods contain only the method signature, while
concrete methods declare a method body as well. Abstract classes are defined with the
abstract keyword.
In the example below, you can see an abstract class called Animal with two abstract and one
concrete method.
// concrete method
void label() {
System.out.println("Animal's data:");
}
}
Extend the Animal abstract class with two child classes: Bird and Fish. Both of them set up their
own functionality for the move() and eat() abstract methods.
class Bird extends Animal {
void move() {
System.out.println("Moves by flying.");
}
void eat() {
System.out.println("Eats birdfood.");
}
}
Now, test it with the TestBird and TestFish classes. Both call the one concrete ( label()) and the
two abstract ( move() and eat()) methods.
class TestBird {
public static void main(String[] args) {
Animal myBird = new Bird();
myBird.label();
myBird.move();
myBird.eat();
}
}
class TestFish {
public static void main(String[] args) {
Animal myFish = new Fish();
myFish.label();
myFish.move();
myFish.eat();
}
}
In the console, the concrete method has been called from the Animal abstract class, while the two
abstract methods have been called from Bird() and Fish(), respectively.
[Console output of TestBird]
Animal's data:
Moves by flying.
Eats birdfood.
2.2.2 Interfaces
An interface is a 100% abstract class. It can have only static, final, and public fields and
abstract methods. It’s frequently referred to as a blueprint of a class as well. Java interfaces
allow us to implement multiple inheritance in our code, as a class can implement any number
of interfaces. Classes can access an interface using the implements keyword.
In the example, define two interfaces, Animal and Bird. Animal has two abstract methods,
while Bird has two static fields and an abstract method.
interface Animal {
public void eat();
public void sound();
}
interface Bird {
int numberOfLegs = 2;
String outerCovering = "feather";
In the TestEagle test class, instantiate a new Eagle object (called myEagle) and print out all
the fields and methods to the console.
As static fields don’t belong to a specific object but to a whole class, you need to access them
from the Bird interface instead of the myEagle object.
class TestEagle {
public static void main(String[] args) {
Eagle myEagle = new Eagle();
myEagle.eat();
myEagle.sound();
myEagle.fly();
The Java console returns all the information you wanted to access:
[Console output of TestEagle]
Eats reptiles and amphibians.
Has a high-pitched whistling sound.
Flies up to 10,000 feet.
Number of legs: 2
Outer covering: feather
2.2.3 Encapsulation
With encapsulation, you can protect the fields of a class. To do so, declare the fields as private
and providing access to them with getter and setter methods.
The Animal class below is fully encapsulated. It has three private fields and each of them has
its own set of getter and setter methods.
class Animal {
private String name;
private double averageWeight;
private int numberOfLegs;
// Getter methods
public String getName() {
return name;
}
public double getAverageWeight() {
return averageWeight;
}
public int getNumberOfLegs() {
return numberOfLegs;
}
// Setter methods
public void setName(String name) {
this.name = name;
}
public void setAverageWeight(double averageWeight) {
this.averageWeight = averageWeight;
}
public void setNumberOfLegs(int numberOfLegs) {
this.numberOfLegs = numberOfLegs;
}
}
The TestAnimal class first sets a value for each field with the setter methods, then prints out the
values using the getter methods.
public class TestAnimal {
public static void main(String[] args) {
Animal myAnimal = new Animal();
myAnimal.setName("Eagle");
myAnimal.setAverageWeight(1.5);
myAnimal.setNumberOfLegs(2);
As you can see below, the Java console returns properly all the values you set with the setter methods:
[Console output of TestAnimal]
Name: Eagle
Average weight: 1.5kg
Number of legs: 2
2.2.4 Inheritance
Inheritance allows us to extend a class with child classes that inherit the fields and methods of
the parent class. It’s an excellent way to achieve code reusability. In Java, we need to use the
extends keyword to create a child class.
In the example, the Eagle class extends the Bird parent class. It inherits all of its fields and
methods, plus defines two extra fields that belong only to Eagle.
class Bird {
public String reproduction = "egg";
public String outerCovering = "feather";
The TestEagle class instantiates a new Eagle object and prints out all the information (both the
inherited fields and methods and the two extra fields defined in the Eagle class).
class TestEagle {
public static void main(String[] args) {
Eagle myEagle = new Eagle();
myEagle.flyUp();
myEagle.flyDown();
}
}
2.2.5 Polymorphism
Polymorphism makes it possible to use the same entity in different forms. In Java, this means
that you can declare several methods with the same name until they are different in certain
characteristics. Java provides us with two ways to implement polymorphism: method
overloading and method overriding.
Static polymorphism
Method overloading means that you can have several methods with the same name within a
class. However, the number, names, or types of their parameters need to be different.
For example, the Bird() class below has three fly() methods. The first one doesn’t have
any parameters, the second one has one parameter (height), and the third one has two
parameters (name and height).
class Bird {
public void fly() {
System.out.println("The bird is flying.");
}
public void fly(int height) {
System.out.println("The bird is flying " + height + " feet
high.");
}
public void fly(String name, int height) {
System.out.println("The " + name + " is flying " + height +
" feet high.");
}
}
The test class instantiates a new Bird object and calls the fly() method three times. Firstly, without
parameters, secondly, with one integer parameter for height, and thirdly, with two parameters for
name and height.
class TestBird {
public static void main(String[] args) {
Bird myBird = new Bird();
myBird.fly();
myBird.fly(10000);
myBird.fly("eagle", 10000);
}
}
In the console, we can see that Java could have differentiated the three polymorphic fly() methods:
[Console output of TestBird]
The bird is flying.
The bird is flying 10000 feet high.
The eagle is flying 10000 feet high.
Dynamic polymorphism
By using the method overriding feature of Java, you can override the methods of a parent
class from its child class.
The Bird class extends the Animal class in the example below. Both have an eat() method.
By default, Bird inherits its parent’s eat() method. However, as it also defines its own
eat() method, Java will override the original method and call eat() from the child class.
class Animal {
public void eat() {
System.out.println("This animal eats insects.");
}
}
The TestBird class first instantiates a new Animal object and calls its eat() method. Then,
it also creates a Bird object and calls the polymorphic eat() method again.
class TestBird {
public static void main(String[] args) {
Animal myAnimal = new Animal();
myAnimal.eat();
The console returns the values of the relevant methods properly. Therefore Java could have
differentiated the two eat() methods indeed.
Computer scientists have struggled for decades to design new languages and techniques for
writing software. Unfortunately, experience has shown that writing large systems is virtua lly
impossible. Small programs seem to be no problem, but scaling to large systems with large
programming teams can result in $100M projects that never work and are thrown out. The only
solution seems to lie in writing small software units that communicate via well-defined
interfaces and protocols like computer chips. The units must be small enough that one
developer can understand them entirely and, perhaps most importantly, the units must be
protected from interference by other units so that programmers can code the units in isolation.
The object-oriented paradigm fits these guidelines as designers represent complete concepts
or real world entities as objects with approved interfaces for use by other objects. Like the
outer membrane of a biological cell, the interface hides the internal implementation of the
object, thus, isolating the code from interference by other objects. For many tasks, object
oriented programming has proven to be a very successful paradigm. Interestingly, the first
object-oriented language (called Simula, which had even more features than C++) was
designed in the 1960's, but object-oriented programming has only come into fashion in the
1990's.
Points to Consider:
Object-oriented programming takes advantage of our perception of world.
An object is an encapsulated completely-specified data aggregate containing attributes
and behaviour.
Data hiding protects the implementation from interference by other objects and defines
approved interface.
An object-oriented program is a growing and shrinking collection of objects that
interact via messages.
You can send the same message to similar objects--the target decides how to impleme nt
or respond to a message at run-time.
Objects with same characteristics are called instances of a class.
Classes are organized into a tree or hierarchy.
Two objects are similar if they have the same ancestor somewhere in the class
hierarchy.
You can define new objects as they differ from existing objects
Benefits of object-oriented programming include:
reduced cognitive load (have less to think about and more natural paradigm)
isolation of programmers (better team programming)
less propagation of errors
more adaptable/flexible programs
faster development due to reuse of code
You are used to observing the world around you through the eyes of a huntergatherer: mainly
animals acting upon other animals and objects. There must have been tremendous selection
pressure for brains that were adept at reasoning about entities, their attributes, their behavior,
and the relationships among them. Is that object edible, ready to eat me, or going to mate with
me? When writing software, one can easily argue that you are at your best when designing and
implementing software in a manner that parallels the way your brain perceives the real world.
Closely associated with encapsulation is the idea of data hiding. Most animals have hidden
attributes or functionality that are inaccessible or are only indirectly accessible by other
animals. The inner construction and mechanism of the human body is not usually available to
you when conversing with other humans. You can only interact with human beings through the
approved voice-recognition interface. Bookkeeping routines such as those controlled by the
autonomic nervous system like breathing may not be invoked by other humans. Without
bypassing the approved interface, you cannot directly measure attributes such as internal body
temperature and so on.
One can conclude that we perceive objects in the world as encapsulated (selfcontained) entities
with approved interfaces that hide some implementation behavior and attributes. From a design
perspective, this is great because it limits what you have to think about at once and makes it
much easier for multiple programmers to collaborate on a program. You can think about and
design each object independently as well as force other programmers to interact with your
objects only in a prescribed manner; that is, using only the approved interface. You do not have
to worry about other programmers playing around with the inner workings of your object and
at the same time you can isolate other programmers from your internal changes.
Encapsulation and data hiding are allowed to varying degrees in non-object oriented langua ges
and programmers have used these principles for decades. For example, in C, you can group
related variables and functions in a single file, making some invisible to functions in other files
by labeling them as static. Conversely, object-oriented languages support these design
principles. In Java, for example, you will use an actual language construct called a class
definition to group variables and functions. You can use access modifiers like private and
public to indicate which class members are visible to functions in other objects.
If we view the world as a collection of objects that send and receive messages, what
programming principle can we derive? At first you may suspect that a signal or message is just
a function call. Rather than manipulate the internals of an object, you might call a function that
corresponded to the signal you wanted to send.
Unfortunately, function calls are poor analogs to real world messaging for two main reasons.
First, function calls do things backwards. You pass objects to functions whereas you send
messages to an object. You have to pass objects to functions for them to operate on because
they are not associated with a particular object. Second, function calls are unique in that the
function's name uniquely identifies what code to run whereas messages are more generic. The
receiver of a message determines how to implement it. For example, you can tell a man and a
woman both to shave and yet they respond to the exact same message by doing radically
different things.
The truly powerful idea behind message sending lies in its flexibility--you do not even need to
know what sex the human is to tell them to shave. All you need to know is that the receiver of
the message is human. The notion that similar, but different, objects can respond to the same
message is technically called polymorphism (literally "multiple- forms"). Polymorphism is
often called latebinding because the receiver object binds the message to an appropriate
implementation function (method in Java terminology) at run-time when the message is sent
rather than at compile-time as functions are.
Polymorphism's flexibility is derived from not having to change the code that sends a message
when you define new objects. Imagine a manager that signals employees when to go home at
night. A so-called micro-manager must know all sorts of details about each employee (such as
where they live) to get them home at night. A manager that delegates responsibility well will
simply tell each employee to go home and let them take care of the details. Surely, adding a
new kind of employee such as a "summer intern" should not force changes in the manager
code. Alas, a micro-manager would in fact have to change to handle the details of the new
employee type, which is exactly what happens in the functio n-centric, non-object-oriented
programming model.
Another similarity measure corresponds to the relationship between the objects themselves
rather than just their interfaces. If two objects are the same kind of object then it makes sense
that they also share a partial interface. For example, males and females are kinds of humans
and, hence, share a common interface (things that all humans can do like shave, sleep, sit and
so on). Java uses object relationships to support polymorphism.
Defining by difference
The second important feature of an object-oriented design model inspired by similar ity
detection is "defining by difference." If I want to define a new object in my system, an efficie nt
way to do so is to describe how it differs from an existing object. Since man and woman objects
are very similar, the human object presumably contains most of the behavior. The man object,
for example, only has to describe what distinguishes a man from the abstract notion of a human,
such as buying lots of electronic toys.
Background
Over the past 40 years, four main computation paradigms have developed:
• Imperative (procedural); C, Pascal, Fortran
• Functional; Lisp, ML
• Logic (declarative); Prolog
• Object-oriented; Simula67, SmallTalk, CLOS, Objective-C, Eiffel, C++, Java
There are also application-specific languages like SQL, SETL (set language), AWK/PERL,
XML (structured data description), ANTLR (grammars), PostScript, and so on. The idea is
that you should use the "right tool for the right job."
Object-oriented programming is not a "silver bullet" that should be used in every situation, but
many modern programming problems are readily mapped to object oriented languages.
"Learning to drive"
We all know how to add two numbers, for example "3+4=7". In a programming language such
as C, you might have the values in variables like this:
int a, b, c;
a=3;
b=4;
c=a+b; // c is 7
Now imagine that you plan on doing some graphics work and your basic unit will be a two-
dimensional point not a single number. Languages do not have built in definitions of point, so
you have to represent points as objects (similar to structs in C and records in Pascal). The plus
operator "+" also is not defined for point so you must use a method call to add points together.
In the following code fragment, type Point holds x and y coordinates and knows how to add
another point to itself yielding another Point. Just like in C or other procedural languages, you
have to define the type of your variables. Here, the variables are objects of type Point. To create
a new point, use the new operator and specify the coordinates in parentheses like a functio n
call.
Point a, b, c;
a = new Point(2,4);
b = new Point(3,5);
To add two points together, use the plus() method:
c = a.plus(b); // c is 5,9
In object-oriented terminology, you would interpret this literally as, "send the message plus to
point a with an argument of b." In other words, tell a to add b to itself, yielding a new point.
The field access operator, "dot", is used for methods just like you use it in C or Pascal to access
struct or record fields.
moveLeft(robot,3); /* C style */
The "dot" field access operator is consistent with field access. For example, in most procedural
languages like C, you say p.x to get field x from struct or record p. In object-oriented langua ges,
you group methods as well as data into objects--the field access operator is consistently used
for both. The Point type is called a class because it represents a template for a class of objects.
In the example above, a, b, and c are all point objects and, hence, they are instances of class
Point. You can think of a class as blueprints for a house whereas instances are actual houses
made from those blueprints. Instances exist only at run-time and an object-oriented program is
just a bunch of object instances sending messages to each other. Also, when you hear someone
talking about the methods of an object, they are, strictly speaking, referring to the methods
defined in the object's class definition.
Imagine that you wanted to duplicate the simple point addition example above, but in a
procedural programming language. You almost certainly would make a data aggregate such as
the following C struct:
struct Point {
int x, y;
};
Then, to define two points, you would do:
struct Point a = {2,4};
struct Point b = {3,5};
struct Point c;
Adding two points together would mean that you need an add_points function that returned the
point sum like this:
c = plus_points(a,b);
A Comment on C syntax
Variable definitions in C are like Java and of the form:
type name;
or
type name = init-value;
For example,
int x = 0;
defines an integer called x and initializes it to 0.
C data aggregates are called structs and have the form:
struct Name {
data-field(s);
};
Defining a variable of struct type allocates memory space to hold something that big;
specifically, sizeof(Name), in C terminology. You can initialize these struct variables using a
curly-brace-enclosed list of values. For example,
struct Point a = {1,2};
makes memory space to hold a point of size sizeof(Point) (normally two 32- bit words) and
initializes its x and y values to 1 and 2, respectively.
To access the fields within a struct, use the "dot" operator. For example, if you printed out the
value of a.x after you defined variable a as above, you would see the value 1.
Object-oriented programming offers a better solution. If humans naturally see the elements in
the real world as self-contained objects, why not program the same way? If you move the
functions that manipulate a data aggregate physically into the definition of that aggregate, you
have encapsulated all functionality and state into single program entity called a class. Anybody
that wants to examine everything to do with a point, for example, just has to look in a single
place. Here is a starting version of class Point in Java:
class Point {
int x,y;
Point plus(Point p) {
return new Point(x+p.x,y+p.y);
}
}
It contains both the state and behavior (data and functionality) of Point. The syntax is very
similar to the C version, except for the method hanging around inside. Notice that the name of
function plus_points becomes method plus; since plus is inside Point, the "_points" is
redundant. The difference between a function and a method will be explored in more detail in
the section on polymorphism. For now, it is sufficient to say that C functions or Pascal
procedures become methods inside class definitions.
How are objects initialized? In other words, how do the arguments of "new Point(1,2)" get
stored in the x and y fields? In an object-oriented language, you can define a constructor method
that sets the class data members according to the constructor arguments (arriving from the new
expression). The constructor takes the form of another method with the same name as the class
definition and no return type. For example, here is an augmented version of Point containing a
constructor.
class Point {
int x,y;
Point(int i, int j) {
x=i;
y=j;
}
Point plus(Point p) {
return new Point(x+p.x,y+p.y);
}
}
The constructor is called by the Java virtual machine once memory space has been allocated
for a Point instance during the new operation.
What would happen if you defined the constructor with arguments named the same thing as
the instance variables:
Point(int x, int y) {
x=x;
y=y;
}
Ooops. Reference to variables x and y are now ambiguous because you could mean the
parameter or the instance variable. Java resolves x to the closest definition, in this case the
parameter. You must use a scope override to make this constructor work properly:
Point(int x, int y) {
this.x=x; // set this object's x to parameter x
this.y=y;
}
This class definition is now adequate to initialize and add points as in the following fragment.
Point a=new Point(2,4);
Point b=new Point(3,5);
Point c=a.plus(b); // c is 5,9
Look at the method invocation:
c=a.plus(b);
What is this really doing and what is the difference between it and
c=d.plus(b);
for some other point, d? You are supposed to think of a.plus(b)as telling a to add b to itself and
return the result. The difference between the two method invocations is the target object, but
how does method plus know about this target; that is, how does plus know which object to add
b to--there is only one argument to the method? Remember that computers cannot "tell" objects
to do things, computers only know (in hardware) how to call subroutines and pass arguments.
It turns out that the two plus invocations are actually implemented as
follows by the computer:
c=plus(a,b);
c=plus(d,b);
where the method targets a and d are moved to arguments, thus, converting them from message
sends to function calls. Because you may have many objects with a method called plus such as
Point3D, the compiler will actually convert your method named plus to a unique function name,
perhaps Point_plus. The method could be converted to a function something like:
The "this" argument will be the target object referenced in the method invocation expression
as the translation above implies.
So, for the moment, you can imagine that the compiler converts the class definition, variable
definitions, and method invocations into something very much like what you would do in C or
other procedural languages. The main difference at this point is that the class definitio n
encapsulates the data of and the functions for a point into a single unit whereas C leaves the
data and functions as disconnected program elements.
Encapsulation promotes team programming efforts because each programmer can work on an
object without worrying that someone else will interfere with the functionality of that object.
A related concept enhances the sense of isolation. Data hiding allows you to specify which
parts of your object are visible to the outside world. The set of visible methods provides the
"user interface" for your class, letting everyone know how you want them to interact with that
object. Sometimes helper methods and usually your data members are hidden from others. For
example, you can modify class Point to specify visibility with a few Java keywords:
class Point {
protected int x,y;
public Point(int i, int j) {
x=i;
y=j;
}
public Point plus(Point p) {
return new Point(x+p.x,y+p.y);
}
}
The data is protected from manipulation, but the constructor and plus() method must be
publicly visible for other objects to construct and add points.
for some point p. Java has a convention that objects are automatically converted to strings via
the toString() method, therefore, the above statement is interpreted as:
System.out.println(p.toString());
Naturally, the functionality for converting a Point to a string will be in class Point:
class Point {
Point(int i, int j) {
x=i;
y=j;
}
Point plus(Point p) {
return new Point(x+p.x,y+p.y);
}
String toString() {
return "("+x+","+y+")";
}
}
The first Rectangle construction statement uses a constructor that takes the four coordinates
for the upper left and lower right coordinates:
If your design calls for a method to return the width and height of a Rectangle, you could define
method, say getDimensions(), as follows.
class Rectangle {
protected Point ul;
protected Point lr;
/** Return width and height */
public Dimension getDimensions() {
return new Dimension(lr.x- ul.x, lr.y-ul.y);
}
}
Because Java does not allow multiple return values, you must encapsulate the width and height
into an object, called Dimension as returned by getDimensions() in the example:
class Dimension {
public int width, height;
public void Dimension(int w, int h) {
width=w;
height=h;
}
}
The complete Rectangle class looks like:
class Rectangle {
protected Point ul;
protected Point lr;
/** Return width and height */
public Dimension getDimensions() {
return new Dimension(lr.x- ul.x, lr.y-ul.y);
}
public Rectangle(int ulx, int uly, int lrx, int lry) {
ul = new Point(ulx,uly);
lr = new Point(lrx,lry);
}
public Rectangle(Point ul, Point lr) {
this.ul = ul;
this.lr = lr;
}
}
You might notice that we have two constructors, which naturally must have the same name--
that of the surrounding class! As long as the arguments different in type, order, and/or number,
you can reuse constructor and regular method names. When you reuse a method name, you are
overloading a method. At compile time, the compiler matches up the arguments to decide
which method to execute just as if the constructors were named differently.
Inheritance
A Rectangle object contains two Point objects. A Rectangle is not a kind of Point nor is a Point
a kind of Rectangle; their relationship is one of containment. There is another kind of object
relationship called inheritance in which one object is a specialization of another. You say that
a class extends or derives from another class and may add data or behavior. The original class
is called the superclass and the class derived from it is called the subclass.
The first important concept behind inheritance is that the subclass can behave just like any
instance of the superclass. For example, if you do not add any data members nor extend the
behavior of Rectangle, you have simply defined another name for a Rectangle:
The definition of class AnotherNameForRectangle is empty, so how can you ask for its
dimensions? The subclass inherits all data and behavior of Rectangle, therefore, object ar can
masquerade as a plain Rectangle. Just as you are born with nothing, but inherit money and
characteristics from your parents,
AnotherNameForRectangle inherits data and behavior from its parent: Rectangle. One way to
look at inheritance of data and behavior is to consider it a language construct for a "live" cut-
n-paste. As you change the definition of a class, all subclasses automatically change as well.
The second important concept behind inheritance is that you may define new objects as they
differ from existing objects, which promotes code reuse. For example, your design may call
for a FilledRectangle object. Clearly, a filled rectangle is a kind of rectangle, which allows yo u
to define the new class as it differs from Rectangle. You may extend Rectangle by adding data:
class FilledRectangle extends Rectangle {
protected String fillColor = "black"; // default color
}
Unfortunately, FilledRectangle does not, by default, know how to initialize itself as a kind of
Rectangle. You must specify a constructor, but how does a subclass initialize the contributio ns
from its superclass? A subclass constructor can invoke a superclass constructor:
class FilledRectangle extends Rectangle {
protected String fillColor = "black"; // default color
public FilledRectangle(Point ul, Point lr, String c) {
super(ul,lr); // calls constructor: Rectangle(ul,lr)
fillColor = c; // initialize instance vars of this
}
}
When you construct a FilledRectangle, space is allocated for an object the size of a Rectangle
plus a String contributed by subclass FilledRectangle. Then Java initializes the Rectangle
portion of the object followed by the FilledRectangle portion. The new operation for
FilledRectangle takes three arguments, two of which are used to initialize the Rectangle
component:
Point p = new Point(10,10);
Point q = new Point(20,40);
FilledRectangle fr = new FilledRectangle(p,q,"red");
You may add behavior to subclasses as well. For example, to allow other objects to set and
get the fill color, add a so-called "getter/setter" pair:
class FilledRectangle extends Rectangle {
protected String fillColor = "black"; // default color
public FilledRectangle(Point ul, Point lr, String c) {
super(ul,lr); // calls constructor: Rectangle(ul,lr)
fillColor = c; // initialize instance vars of this
}
Summary
• An object-oriented program is a collection of objects
• Objects with same properties and behavior are instances of the same class
• Objects have two components: state and behaviour (variables and methods)
• An object may contain other objects
• Instance variables in every object, class variables are like global variables shared by all
instances of a class.
Many of the objects in a running program will have the same characteristics and conform to
the same rules of behavior. Such objects are considered to be instances of the same class. For
example, there may be many instances of the class Car in existence at any point in a running
traffic simulation. An object-oriented program is a collection of class definitions, each one
wrapping up all the data and functionality associated with a single concept or entity specified
in the program design.
Consider trying to outline the definition of a car for a computer or person unfamiliar with a car.
You would note that a car has certain properties like color and number of doors:
• color
• numberOfDoors
A car has certain behavior; it can start, stop, turn left, turn right, and so on:
• start
• stop
• turnLeft
• turnRight
By adding a bit of punctuation, you can turn this outline into a Java class definition:
class Car {
// properties
String color;
int numberOfDoors;
This class definition is analogous to the blueprint for a car versus the car instances themselves.
The data fields are called instance variables and the functions embedded within a class are
called methods to distinguish them from functions or procedures in nonobject- oriented
languages. Both the data fields and methods are called members of the class. A class definitio n
explicitly relates data and the methods that operate on it.
class Car {
// class variables
static int count = 0;
// instance variables; properties
String color;
int numberOfDoors;
...
Car( ) {
count = count + 1;
...
}
}
Encapsulation
One of the best metaphors for the software object is the cell, the fundamental biologic a l
building block. Loosely speaking, the surface of a cell is a membrane that physically wraps the
contents (encapsulation), restricts chemical exchange with the outside environment (data
hiding), and has receptors (message interface) that receive chemical messages from other cells.
Cells interact by sending chemical messages not by directly accessing the internals of other
cells.
This metaphor extends nicely to designing programs. Imagine designing a program that tracks
your company's vehicle pool. The first thing you might do is write down all of the types of
vehicles in your company and then start to describe their characteristics and behavior. You
might have non-vehicle objects, such as the vehicle pool itself, that do not correspond directly
to physical objects. If you consider each object in your design a cell, then you will think of it
as an independent entity with an internal implementation and external interface for
interacting with other objects.
Good programmers have always tried to keep the data and the functions that manipulate that
data together, for example in the same file. Object-oriented languages formalize this strategy
by forcing you to encapsulate state and behaviour within classes.
class Car {
// properties
String color;
int numberOfDoors;
// contained objects (Engine,Body,Wheel defined elsewhere)
Engine theEngine;
Body theBody;
Wheel[] theWheels;
// behavior
public void start() {...}
public void stop() {...}
public void turnLeft() {...}
public void turnRight(){...}
}
The code within the methods and any of the member variables could be rewritten without
affecting the interface and, hence, without forcing changes in other objects.
So, encapsulation and data hiding work together as a barrier that separates the contractual
interface of an object from its implementation. This barrier is important for two reasons:
1. The interface/implementation separation protects your object from other objects. Also, when
looking for bugs, you can usually limit your search to the misbehaving object.
2. The separation also protects other objects from themselves. Changes in implementation of
one object will not require changes in the other objects that use it.
From a design perspective, this is great because it limits what you have to think about at once
and makes it much easier for multiple programmers to collaborate on a program because
objects may be implemented independently.
Sending Messages
Summary
Objects communicate via messages
A target object receives a message and implements it with a method
You may overload method names
Binding occurs at run-time
Objects in a running program collaborate by sending messages back and forth to elicit
responses, changes of state, or more message sends. An object's behavior is characterized by
how it reacts to received messages. Message sends are of the form:
targetObject.message(arguments);
At run-time, the targetObject receives the message, looks up the message name in its method
list, and executes the method with the matching signature; the signature includes the name of
the method plus the number and type of the arguments. Sending start to a Car object such as:
aCar.start(); means that aCar would look up start in its method table, find method start(), and
execute it. A method name may be reused, or overloaded, if the arguments differ in type or
number. For example, you could add another start() method to class Car as long as the signature
was different:
class Car {
...
public void start() {...}
public void start(float acceleration) {...}
...
}
Sending message start to aCar would now choose between the start() and start(float) methods
of Car depending on the argument list of the message send:
aCar.start(); // call empty-arg start method
aCar.start(9.08); // call acceleration start method
Method overloading makes an object choose between methods with different signatures within
a class and, while very convenient, is not a core principle of object-oriented programming.
Overloading should be distinguished from polymorphism, which is described in the next
section, where a message forces a choice between methods with the same signature, but in
different classes.
Polymorphism
Summary
Polymorphism is the ability for a single object reference to refer to more than a single concrete
type.
The benefit of polymorphism is that the introduction of a new type does not force widespread
code changes.
The more specific your program statements have to be, the less they can deal with change and
the shorter their useful lifespan. The primary adaptability of objectoriented programming
derives from the message sends between objects. Consider that, when you call a function in C
or other procedural language, you are specifying exactly the code you want to execute. While
easy to follow, such exactness renders your program extremely brittle. Sending a message to
an object, on the other hand, relies on the receiver object to react to the message by executing
an appropriate method. You can swap out the receiver object and replace it with any other
object as long as that object answers the same message. No change is required to the message
send code. For example, if someone hands you an object and you know it is one of 5 objects
that can respond to message start, there are 5 possible start() methods that could be executed in
response to the start message. This nonspecificity and flexibility is called polymorphism
because the object may take one of many forms.
If there are 5 different classes that can answer start, there are 5 possible methods that could be
executed by this one statement if v is an instance of one of those 5 classes:
v.start();
At run-time the exact type of object, such as Car, is known and the target object binds the start
message to its start() method.
The equivalent code written in C would use a function call instead of a message send and would
have to specify exactly which function to call. If there is only one data type that you can start,
a single function call suffices:
startCar(v);
Adding a new data type, say, Truck, would mean adding an if-then-else statement to deal with
the newly-introduced type; you would have to do something like this:
// C fragment
if ( v.type == CAR_TYPE ) { // assume vehicle stores type
startCar(v);
}
else if ( v.type == TRUCK_TYPE ) {
startTruck(v);
}
Some object-oriented languages, such as SmallTalk, allow you to send any message you want
to any other object, which can lead to annoying run-time errors like "object does not impleme nt
that message." Further, while both Rectangle and GunFighter might understand message draw,
the same message is surely interpreted in two different ways. It would be nice if the compiler
informed you at compile-time that you were sending the same message to very differe nt
objects.
Java provides this service via strong typing. For each object you refer to, you have to specify
the type of object, kind of object, or behavior of the object. You cannot perform assignme nts
between dissimilar objects, which prevents you from sending a message to a radically differe nt
object inadvertently.
GunFighter gf = ...;
Rectangle r = gf; // INVALID!!! compile error!
r.draw();
So how does Java know which objects are similar and which are unrelated? Java uses the notion
of inheritance to express similarity between classes.
Inheritance
Summary
Inheritance specifies the commonality or similarity relationship between classes.
It is a relationship of identify
A subclass inherits state and behavior from the superclass
Any reference to an object can be given a reference to a subclass instead
The inheritance relationships together form a class hierarchy, which helps to organize
your program
Classes whose primary job is to group similar classes is often called an abstract class
Inheritance lets you define by difference: augment, override, or refine members
inherited from superclass
Polymorphism uses the inheritance relationship as its definition of similarity; Java
restricts a message send to similar objects as its definition of similarity; Java restricts
a message send to similar objects.
Object-oriented languages support a simple, but potent idea called inheritance that lets you
express the similarity between different class types. A class (the subclass) that inherits from
another acquires all of the properties and behavior of the parent class (superclass). For this
reason any two classes that inherit from the same superclass share a common set of properties
and behavior, namely that of the superclass, and you can treat the two subclasses similarly.
class Employee {
...
}
class Manager extends Employee {
...
}
class Developer extends Employee {
...
}
Class hierarchies
Many procedural languages are just "bags" of variables, data types, and functions as there is
no language construct to group them or show the relationships betweenthem. Some langua ges
have modules or packages that can group things but, still, the relationship between functio ns
and data and between data types remains unspecified. Often, the proximity of one function to
another is due to the timing of when you typed in the code.
The class mechanism of object-oriented languages lets you encapsulate data and behavior
associated with each conceptual entity and the class inheritance relationship lets you specify
the relationship between the classes. The combined result of all inheritance relationships is a
tree, or class hierarchy. In Java, this class hierarchy has a single root, class Object, from which
all classes directly or indirectly inherit. You can interpret this to mean that all objects are related
in that they all share the characteristics of a basic Object.
When designing an object-oriented program, you decide what objects you will need in the
system and then you look for similarities between objects that you can exploit. For example,
given objects of types Manager, Developer, Marketeer, VP, CEO, and Guru, you would
probably define the following inheritance relationships:
When looking for similarities, you will often create abstract classes that exist purely to group
related classes and to contain the factored data and methods; abstract classes are organizatio na l
classes that have no run-time instances.
Creating class Employee helps to organize your classes as it shows the relationship now
between all classes: they are all employees. Further, Employee would probably contain
common data and behavior such as method getName(), automatically extending that
functionality to all employees, that is all subclasses of Employee.
For small sets of classes, a decent class hierarchy is usually obvious, but this is not true for
large programs. Different designers will analyze the same situation differently yielding
different classes and class organizations. Following this, different programmers will add
different "helper" classes to actually implement the design. The classes and their relations hips
depends on the perspective and experience of the designers and developers.
Defining by difference
Another way to exploit the class inheritance relationship is to define new classes as they differ
from existing classes. For example, to define SalesPerson, you could extend Employee and to
define Analyst you could extend Developer (assuming all analysts know how to develop
software).
Organizing your program into a hierarchy, then, means that you have less work to do to
augment a program. Inexperienced developers often take this to mean that you extend a class
if you need to reuse or borrow some or all of its code. Unfortunately, this practice results in
poorly organized and specified programs.
Use the class hierarchy to show the appropriate relationships between the classes and code
reuse occurs naturally as a side effect. A good rule of thumb is that if the phrase "x is a kind of
y" sounds correct in English, x is probably a appropriate subclass of y. For example, reconsider
the Point and Rectangle classes from above. A Rectangle has two points so you might be
tempted to have Rectangle extend Point so you could reuse all the Point code, but a Rectangle
contains two Point objects it is not a kind of Point.
When you extend a class you have the opportunity to add data or method members to specialize
or augment the characteristics of the super class. For example, you might define Employee as:
class Employee {
String name;
int id;
public Employee(String name, int id) {// constructor
this.name = name;
this.id = id;
}
public void setName(String name) { // "setter"
this.name = name;
}
public String getName() { // "getter"
return name;
}
}
Then, you can then augment Employee with, say, an office number (plus the usual
"getter/setter") methods to define Manager:
A Developer on the other hand, would still respond with the employee's name unless it too
overrode getName():
Developer d = ...;
String dname = d.getName(); // dname is developer's name
One final thing you can do with inheritance is refine the behavior of the superclass. If you
wanted a Manager to respond to getName with the person's name prefixed with "Boss", the
following definition would suffice:
A tricky situation can arise when a subclass delegates functionality to a superclass that in turns
calls a method that is overridden in the subclass. For example, assume Manager does not
override getName(), delegating it to superclass Employee. But, Employee.getName() simply
returns the result of calling toString(). What value does name get in the following code?
Manager m = new Manager("Jimbo", 213002);
String name = m.getName();
Nor can you assign instances of classes that are at the same level in the hierarchy:
m = d; // NO: a Manager is not a kind of Developer
d = m; // NO: a Developer is not a kind of Manager
The type of the object on the left-hand-side must be a superclass of the type on the right-ha nd-
side. This prevents you from sending subsequent messages to an object that might not respond
to that message. For example, you cannot create a generic employee and then treat it like a
manager:
Employee e = ...;
Manager boss = e; // compile-time type error!
boss.setOfficeNumber(210); // run-time error!
Sure. The beauty of inheritance is that you can treat any subclasses of Employee just like an
Employee. Each time through the loop, the message getName is sent to the ith object in the
employees array. The employees[i].getName() expression does not uniquely specify what code
to run. It only specifies that the getName() method of one of Employee subclasses will be
executed. The exact method cannot be determined until run-time when the concrete object type
of each employees[i] is known. Therein lies the flexibility of polymorphism. Ten years from
now, if someone adds another kind of employee, this loop would still work without
modification.
You can see polymorphism at work everyday in the real world. Programming instructors use
polymorphism to teach, for example. Each student that walks into the classroom is treated
strictly as a developer and, since the instructor presumably does not know the students, that is
all the instructor can assume. Any other attributes or specialties of the person are irrelevant and
hidden such as their nationality, skill level, political affiliation, etc... As long as they are
developers, the instructor can teach them. The instructor teaches by sending messages that
developers understand to the students such as "open a new project called DBQuery." In Java,
the analogous situation might be represented as a teach(Developer) method within an Instructor
class:
class Instructor {
void teach(Developer d) {
d.openNewProject("DBQuery");
...
}
...
}
A company might have 5 different kinds of developers, but any kind can be passed to method
teach(Developer) if the developers are defined as subclasses of Developer. If a nondeveloper
happened to walk into the classroom, they would not understand what was being said.
Similarly, if a nondeveloper was somehow passed to teach(Developer) a run-time error would
occur indicating that the object does not know how to respond to the openNewProject message.
The instructor could be introduced to a random person in the hall, so another method, greet(),
would make sense:
class Instructor {
void teach(Developer d) {
d.openNewProject("DBQuery");
...
}
void greet(Employee e) {
String name = e.getName();
...
}
...
}
Method greet(Employee) is much more widely applicable than teach(Developer) and would
work for any kind of employee including developers:
Instructor instr = ...;
Developer jim = ...;
Marketeer pat = ...;
instr.greet(jim);
instr.greet(pat);
instr.teach(jim); // only jim can be taught programming
On the other hand, the instructor can assume much less about the incoming objects. The
instructor is limited to asking the Employee objects for their name.
Class inheritance implies identity and identity implies a behavior, but similar behavior does
not necessarily imply the same identity. For example, broccoli and corn share the vegetable
identity and, hence, both behave like vegetables:
class Vegetable {
public void die() {...}
void growRoots() {...}
...
}
class Broccoli extends Vegetable {...}
class Corn extends Vegetable {...}
Cows and chickens share an animal identity and behave like animals:
class Animal {
public void die() {...}
void eat() {...}
...
}
class Cow extends Animal {...}
class Chicken extends Animal {...}
While all of these flora and fauna objects may share a common message such as die, being
mortal does not indicate vegetable or animal. If all you care about is an object's mortality, you
need a type system that lets you specify behavior rather than just identity: Java provides a
construct called an interface that looks exactly like a class, but without any method
implementations or data variables. Here is an interface that defines the mortality behavior:
// all Mortal objects implement method die().
interface Mortal {
public void die();
}
If a class of objects can answer die, you say that the class implements Mortal. You can then
rewrite Animal and Vegetable to indicate their mortality:
class Animal implements Mortal {
public void die() {...}
void eat() {...}
...
}
class Vegetable implements Mortal {
public void die() {...}
void growRoots() {...}
...
}
The power of interfaces comes when you reference objects by their behavior as in the following
method that accepts a Mortal object:
void kill(Mortal o) {
o.die(); // could be an Animal or Vegetable
}
Coding
If you are going to walk through the complete coding of this problem, you may find the
following coding strategy helpful when implementing the design:
1. Define all the classes for the actors without inheritance
2. Add the inheritance relationships
3. Add the properties
4. Implement print() where appropriate
5. Implement the constructors and add() methods used for construction
6. Make a TestHarness class with a main() that constructs a book and does tells it to print itself
out
7. Walk through the execution of the program with a debugger to see how the objects get
constructed and how you can look at the contents of the overall book containment "tree".
8. Walk through the execution of the program with a debugger, tracing methods and watching
how it prints out and recurses the containment relationships.
Code Listing
class Book extends TextContainer {
private String TOC;
private String index;
private String title;
private String author;
public Book(String title, String author) {
this.title = title;
this.author= author;
}
public void print() {
// do title page
System.out.println();
System.out.println();
System.out.println();
System.out.println(title);
System.out.println();
System.out.println("BY");
System.out.println();
System.out.println(author);
System.out.println();
System.out.println(TOC);
super.print();
System.out.println();
System.out.println(index);
}
public void setIndex(String i) {
index = i;
}
public void setTOC(String t) {
TOC = t;
}
}
class Chapter extends TextContainer {
private String title;
public Chapter(String t) {
title = t;
}
public void print() {
System.out.println();
System.out.println(title);
System.out.println();
super.print();
}
}
class Section extends TextContainer {
private String title;
public Section(String title) {
this.title = title;
}
public void print() {
System.out.println();
System.out.println(title);
super.print();
}
}
class Paragraph extends TextEntity {
private String text;
public Paragraph(String t) {
text = t;
}
public void print() {
System.out.println(text);
}
}
abstract class TextEntity {
public abstract void print();
}
abstract class TextContainer extends TextEntity {
private static final int MAX_CONTAINED = 20;
private int n=0;
private TextEntity[] elements;
public void add(TextEntity e) {
if ( elements==null ) {
elements = new TextEntity[MAX_CONTAINED];
}
elements[n] = e;
n = n + 1;
}
public void print() {
for (int i=0; i<n; i=i+1) {
elements[i].print();
}
}
}
class TestHarness {
public static void main(String[] args) {
Book b = new Book("JDK User's Guide", "MageLang");
b.setTOC("Preface\n"+
"1. Overview of JDK\n"+
"2. Getting Started\n");
b.setIndex("the index");
Paragraph preface = new Paragraph("The Preface");
Chapter overview = new Chapter("1. Overview of JDK");
Chapter started = new Chapter("2. Getting Started");
b.add(preface);
b.add(overview);
b.add(started);
// chapter 1
Paragraph hello =
new Paragraph("Intro paragraph of chapter 1");
Section intro =
new Section("Introduction");
intro.add(
new Paragraph("1st para of intro section of chapter 1")
);
intro.add(
new Paragraph("2nd para of intro section of chapter 1")
);
Section features = new Section("Features");
features.add(
new Paragraph("1st para of features section of chapter 1")
);
Section help = new Section("Help");
help.add(
new Paragraph("1st para of help section of chapter 1")
);
help.add(
new Paragraph("2nd para of help section of chapter 1")
);
overview.add(hello);
overview.add(intro);
overview.add(features);
overview.add(help);
// chapter 2
Section install = new Section("Installation");
install.add(
new Paragraph("1st para of install section of chapter 2")
);
// 2 subsections of section 1
Section unzip = new Section("Unzipping");
unzip.add(
new Paragraph(
"1st para of unzip subsection of install section chapter 2")
);
Section classpath = new Section("Setting CLASSPATH");
classpath.add(
new Paragraph(
"1st para of classpath subsection of install section chapter 2")
);
Section path = new Section("Setting PATH");
path.add(
new Paragraph(
"1st para of setting PATH subsection of install chapter 2")
);
path.add(
new Paragraph(
"2nd para of setting PATH subsection of install chapter 2")
);
install.add(unzip);
install.add(classpath);
install.add(path);
started.add(install);
// NOW: PRINT THE BOOK!
b.print();
}
}
2.6 TEMPLATES
2.6.1 Function templates
Function templates are special functions that can operate with generic types. This allows us to
create a function template whose functionality can be adapted to more than one type or class
without repeating the entire code for each type.
In C++ this can be achieved using template parameters. A template parameter is a special kind
of parameter that can be used to pass a type as argument: just like regular function parameters
can be used to pass values to a function, template parameters allow to pass also types to a
function. These function templates can use these parameters as if they were any other regular
type.
The format for declaring function templates with type parameters is:
template <class identifier> function_declaration;
template <typename identifier> function_declaration;
The only difference between both prototypes is the use of either the keyword class or the
keyword typename. Its use is indistinct, since both expressions have exactly the same meaning
and behave exactly the same way.
For example, to create a template function that returns the greater one of two objects we could
use:
template <class myType> myType GetMax (myType a, myType b)
{return (a>b?a:b); }
Here we have created a template function with myTypeas its template parameter. This template
parameter represents a type that has not yet been specified, but that can be used in the template
function as if it were a regular type. As you can see, the function template GetMax returns the
greater of two parameters of this still- undefined type.
To use this function template we use the following format for the function call:
function_name <type> (parameters);
For example, to call GetMax to compare two integer values of type int we can write:
int x,y;
GetMax <int> (x,y);
When the compiler encounters this call to a template function, it uses the template to
automatically generate a function replacing each appearance of myType by the type passed as
the actual template parameter (int in this case) and then calls it. This process is automatica lly
performed by the compiler and is invisible to the programmer.
oUTPUT: 6 10
In this case, we have used T as the template parameter name instead of myType because it is
shorter and in fact is a very common template parameter name. But you can use any identifier
you like. In the example above we used the function template GetMax() twice. The first time
with arguments of type int and the second one with arguments of type long. The compiler has
instantiated and then called each time the appropriate version of the function. As you can see,
the type T is used within the GetMax()template function even to declare new objects of that
type:
T result;
Therefore, result will be an object of the same type as the parameters a and b when the functio n
template is instantiated with a specific type.
In this specific case where the generic type T is used as a parameter for GetMax the compiler
can find out automatically which data type has to instantiate without having to explicitly
specify it within angle brackets (like we have done before specifying <int> and <long>). So
we could have written instead:
int i,j;
GetMax (i,j);
Since both i and j are of type int, and the compiler can automatically find out that the template
parameter can only be int. This implicit method produces exactly the same result:
// function template II
#include <iostream>
usingnamespace std;
template <class T>
T GetMax (T a, T b)
{ return (a>b?a:b);
}
int main ()
{ int i=5, j=6, k;
long l=10, m=5, n;
k=GetMax(i,j);
n=GetMax(l,m);
cout << k << endl;
cout << n << endl;
return 0;
}
6
10
Notice how in this case, we called our function template GetMax() without explicitly
specifying the type between angle-brackets <>. The compiler automatically determines what
type is needed on each call.
Because our template function includes only one template parameter (class T) and the functio n
template itself accepts two parameters, both of this T type, we cannot call our function template
with two objects of different types as arguments:
int i;
long l;
k = GetMax (i,l);
This would not be correct, since our GetMax function template expects two arguments of the
same type, and in this call to it we use objects of two different types.
We can also define function templates that accept more than one type parameter, simply by
specifying more template parameters between the angle brackets. For example:
template <class T, class U>
T GetMin (T a, U b)
{
return (a<b?a:b);
}
In this case, our function template GetMin() accepts two parameters of different types and
returns an object of the same type as the first parameter (T) that is passed. For example, after
that declaration we could call GetMin() with:
int i,j;
long l;
i = GetMin<int, long> (j,l);
or simply:
i = GetMin (j,l);
even though j and l have different types, since the compiler can determine the apropriate
instantiation anyway.
The class that we have just defined serves to store two elements of any valid type. For example,
if we wanted to declare an object of this class to store two integer values of type int with the
values 115 and 36 we would write:
this same class would also be used to create an object to store any other type:
The only member function in the previous class template has been defined inline within the
class declaration itself. In case that we define a function member outside the declaration of the
class template, we must always precede that definition with the template <...> prefix:
Notice the syntax of the definition of member function getmax:
Confused by so many T's? There are three T's in this declaration: The first one is the template
parameter. The second T refers to the type returned by the function. And the third T (the one
between angle brackets) is also a requirement: It specifies that this function's template
parameter is also the class template parameter.
First of all, notice that we precede the class template name with an emptytemplate<> parameter
list. This is to explicitly declare it as a template specialization. But more important than this
prefix, is the <char>specialization parameter after the class template name. This specializa tio n
parameter itself identifies the type for which we are going to declare a template class
specialization (char). Notice the differences between the generic class template and the
specialization:
The first line is the generic template, and the second one is the specialization. When we declare
specializations for a template class, we must also define all its members, even those exactly
equal to the generic template class, because there is no "inheritance" of members from the
generic template to the specialization.
2.6.4 Non-type parameters for templates
Besides the template arguments that are preceded by the class or typename keywords, which
represent types, templates can also have regular typed parameters, similar to those found in
functions. As an example, have a look at this class template that is used to contain sequences
of elements:
It is also possible to set default values or types for class template parameters. For example, if
the previous class template definition had been:
Abbreviations for AR
Some definitions
Declaration vs. Definition
By declaring an entity we inform the compiler about the structure and the behavior of the
entity itself and bind unique name to it.
By defining an entity we instruct the compiler to generate code to performmemory allocatio n
for that entity
The scope of a variable
The scope of a variable is a portion of the source code in which that variable is visible.
Extent of a Variable
The extent (or lifetime) describes when in a program's execution a variable has a value. A
variable can exist (extent) but be not visible (scope). i.e., global variables hidden by
homonymous local variable in a scope.
Blocks
A block is a portion of code enclosed between two special symbols, which mark the beginning
and the end of the block.
Stack-based variables have their extent determined by their scope, so the former is constraine d
by the structure of the code at compile-time.
Sometimes there is a need for the variables with unconstrained extent in order to cope with
problems where lifetime of a variable can only be known at run-time. In this case heap-based
variables, whose extent is strictly under control of the programmer, are used.
The programmer can create such a variable any time and ask the system to dispose it when it
is no longer needed.
In majority of programming languages, the scope of an entity allocated on the heap is the union
of the scopes of all the variables referring to it. Usually, the extent of dynamic data starts when
they are created and lasts until they are destroyed or until the program terminates.
Design Patterns
Design patterns provide a standard terminology and are specific to particular scenario. For
example, a singleton design pattern signifies use of single object so all developers familiar with
single design pattern will make use of single object and they can tell each other that program
is following a singleton pattern.
Shape.java
public interface Shape {
void draw();
}
Step 2: Create concrete classes implementing the same interface.
Rectangle.java
public class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("Inside Rectangle::draw() method.");
}
}
Square.java
@Override
public void draw() {
System.out.println("Inside Square::draw() method.");
}
}
Circle.java
public class Circle implements Shape {
@Override
public void draw() {
System.out.println("Inside Circle::draw() method.");
}
}
Step 3: Create a Factory to generate object of concrete class based on given
information.
ShapeFactory.java
} else if(shapeType.equalsIgnoreCase("RECTANGLE")){
return new Rectangle();
} else if(shapeType.equalsIgnoreCase("SQUARE")){
return new Square();
}
return null;
}
}
Step 4: Use the Factory to get object of concrete class by passing information such as
type.
FactoryPatternDemo.java
Implementation
We are going to create a Shape and Color interfaces and concrete classes implementing
these interfaces. We create an abstract factory class AbstractFactory as next step.
Factory classes ShapeFactory and ColorFactory are defined where each factory extends
AbstractFactory. A factory creator/generator class FactoryProducer is created.
We are going to create a Shape and Color interfaces and concrete classes implementing
these interfaces. We create an abstract factory class AbstractFactory as next step.
Factory classes ShapeFactory and ColorFactory are defined where each factory extends
AbstractFactory. A factory creator/generator class FactoryProducer is created.
Shape.java
RoundedRectangle.java
AbstractFactory.java
ShapeFactory.java
FactoryProducer.java
AbstractFactoryPatte rnDemo.java
}
}
Step 7: Verify the output.
3. Singletone Pattern: Singleton pattern is one of the simplest design patterns in Java.
This type of design pattern comes under creational pattern as this pattern provides one
of the best ways to create an object. This pattern involves a single class which is
responsible to create an object while making sure that only single object gets created.
This class provides a way to access its only object which can be accessed directly
without need to instantiate the object of the class.
Implementation
We're going to create a SingleObject class. SingleObject class have its constructor as
private and have a static instance of itself. SingleObject class provides a static method
to get its static instance to outside world. SingletonPatternDemo, our demo class will
use SingleObject class to get a SingleObject object.
Figure 3.3: Singleton Pattern UML Diagram
SingleObject.java
public class SingleObject {
public class SingleObject {
//illegal construct
//Compile Time Error: The constructor SingleObject() is not visible
//SingleObject object = new SingleObject();
Hello World!
Bridge is used when we need to decouple an abstraction from its implementation so that the
two can vary independently. This type of design pattern comes under structural pattern as this
pattern decouples implementation class and abstract class by providing a bridge structure
between them.
This pattern involves an interface which acts as a bridge which makes the functionality of
concrete classes independent from interface implementer classes. Both types of classes can be
altered structurally without affecting each other.
We are demonstrating use of Bridge pattern via following example in which a circle can be
drawn in different colors using same abstract class method but different bridge impleme nte r
classes.
Implementation
circle.
Shape.java
Circle.java
Step 5: Use the Shape and DrawAPI classes to draw different colored circles.
BridgePatternDemo.java
redCircle.draw();
greenCircle.draw();
}
}
2. Flyweight Pattern
Flyweight pattern is primarily used to reduce the number of objects created and to decrease
memory footprint and increase performance. This type of design pattern comes under
structural pattern as this pattern provides ways to decrease object count thus improving the
object structure of application.
Flyweight pattern tries to reuse already existing similar kind objects by storing them and
creates new object when no matching object is found. We will demonstrate this pattern by
drawing 20 circles of different locations but we will create only 5 objects. Only 5 colors
are available so color property is used to check already existing Circle objects.
Implementation
We are going to create a Shape interface and concrete class Circleimplementing
the Shape interface. A factory class ShapeFactory is defined as a next step.
ShapeFactory has a HashMap of Circle having key as color of the Circle object. Whenever a
request comes to create a circle of particular color to ShapeFactory, it checks the circle object
in its HashMap, if object of Circlefound, that object is returned otherwise a new object is
created, stored in hashmap for future use, and returned to client.
FlyWeightPatternDemo, our demo class, will use ShapeFactory to get a Shapeobject. It will
pass information (red / green / blue/ black / white) to ShapeFactory to get the circle of desired
color it needs.
Shape.java
public interface Shape {
void draw();
}
Circle.java
@Override
public void draw() {
System.out.println("Circle: Draw() [Color : " + color + ", x : " + x + ", y :" + y + ",
radius :" + radius);
}
}
Step 3: Create a factory to generate object of concrete class based on given informatio n.
ShapeFactory.java
import java.util.HashMap;
if(circle == null) {
circle = new Circle(color);
circleMap.put(color, circle);
System.out.println("Creating circle of color : " + color);
}
return circle;
}
}
Step 4: Use the factory to get object of concrete class by passing an information such
as color.
FlyweightPatternDemo.java
Iterator pattern is very commonly used design pattern in Java and .Net programming
environment. This pattern is used to get a way to access the elements of a collection object in
sequential manner without any need to know its underlying representation. Iterator pattern falls
under behavioural pattern category.
Implementation
We're going to create an Iterator interface which narrates navigation method and a Container
interface which returns the iterator. Concrete classes implementing the Container interface will
be responsible to implement Iterator interface and use it IteratorPatternDemo, our demo class
will use NamesRepository, a concrete class implementation to print a Names stored as a
collection in NamesRepository.
Figure 3.5: Iterator Pattern UML Diagram
Step 1: Create interfaces.
Iterator.java
Step 2: Create concrete class implementing the Container interface. This class has inner
class NameIterator implementing the Iterator interface.
NameRepository.java
@Override
public Iterator getIterator() {
return new NameIterator();
}
int index;
@Override
public boolean hasNext() {
@Override
public Object next() {
if(this.hasNext()){
return names[index++];
}
return null;
}
}
}
IteratorPatternDemo.java
Name : Robert
Name : John
Name : Julie
Name : Lora
2. Observer pattern
Observer pattern is used when there is one-to-many relationship between objects such as if one
object is modified, its dependent objects are to be notified automatically. Observer pattern falls
under behavioural pattern category.
Implementation
Observer pattern uses three actor classes. Subject, Observer and Client. Subject is an object
having methods to attach and detach observers to a client object. We have created an abstract
class Observer and a concrete class Subject that is extending class Observer.
ObserverPatternDemo, our demo class, will use Subject and concrete class object to show
observer pattern in action.
import java.util.ArrayList;
import java.util.List;
Observer.java
BinaryObserver.java
@Override
public void update() {
System.out.println( "Binary String: " + Integer.toBinaryString( subject.getState() ) );
}
}
OctalObserver.java
@Override
public void update() {
System.out.println( "Octal String: " + Integer.toOctalString( subject.getState() ) );
}
}
HexaObserver.java
@Override
public void update() {
System.out.println( "Hex String: " + Integer.toHexString( subject.getState ()
).toUpperCase() );
}
}
ObserverPatternDemo.java
new HexaObserver(subject);
new OctalObserver(subject);
new BinaryObserver(subject);
View - View represents the visualization of the data that model contains.
Controller - Controller acts on both model and view. It controls the data flow into
model object and updates the view whenever data changes. It keeps view and model
separate.
Implementation
We are going to create a Student object acting as a model.StudentView will be a view class
which can print student details on console and StudentControlleris the controller class
responsible to store data in Student object and update view StudentView accordingly.
MVCPatternDemo, our demo class, will use StudentController to demonstrate use of MVC
pattern.
Student.java
StudentView.java
StudentController.java
Step 4: Use the StudentController methods to demonstrate MVC design pattern usage.
MVCPatternDemo.java
controller.updateView();
controller.updateView();
}
Student:
Name: Robert
Roll No: 10
Student:
Name: John
Roll No: 10
MODULE 4:
4.1 Introduction
Generics and collections work well with a number of other new features introduced in the latest
versions of Java, including boxing and unboxing, a new form of loop, and functions that accept
a variable number of arguments. We begin with an example that illustrates all of these. As we
shall see, combining them is synergistic: the whole is greater than the sum of its parts.
Taking that as our motto, let’s do something simple with sums: put three numbers into a list
and add them together. Here is how to do it in Java with generics:
List<Integer> ints = Arrays.asList(1,2,3);
int s = 0;
for (int n : ints) { s += n; }
assert s == 6;
You can probably read this code without much explanation, but let’s touch on the key features.
The interface List and the class Arrays are part of the Collections Framework (both are found
in the package java.util). The type List is now generic; you write List<E> to indicate a list with
elements of type E. Here we write List<Integer> to indi-cate that the elements of the list belong
to the class Integer, the wrapper class that corresponds to the primitive type int. Boxing and
unboxing operations, used to convert from the primitive type to the wrapper class, are
automatically inserted. The static method asList takes any number of arguments, places them
into an array, and returns a new list backed by the array. The new loop form, foreach, is used
to bind a variable successively to each element of the list, and the loop body adds these into
the sum. The assertion statement (introduced in Java 1.4), is used to check that the sum is
correct; when assertions are enabled; it throws an error if the condition does not evaluate to
true.
Reading this code is not quite so easy. Without generics, there is no way to indicate in the type
declaration what kind of elements you intend to store in the list, so instead of writing
List<Integer>, you write List. Now it is the coder rather than the compiler who is responsible
for remembering the type of the list elements, so you must write the cast to (Integer) when
extracting elements from the list. Without boxing and unboxing, you must explicitly allocate
each object belonging to the wrapper class Integer and use the intValue method to extract the
corresponding primitive int. Without functions that accept a variable number of arguments, you
must explicitly allocate an array to pass to the asList method. Without the new form of loop,
you must explicitly declare
an iterator and advance it through the list. By the way, here is how to do the same thing with
an array in Java before generics:
int s = 0;
for (int i = 0; i < ints.length; i++) { s += ints[i]; }
assert s == 6;
This is slightly longer than the corresponding code that uses generics and collections, is
arguably a bit less readable, and is certainly less flexible. Collections let you easily grow or
shrink the size of the collection, or switch to a different representation when appropriate, such
as a linked list or hash table or ordered tree. The introduction of generics, boxing and unboxing,
foreach loops, and varargs in Java marks the first time that using collections is just as simple,
perhaps even simpler, than using arrays
4.2 Generics
An interface or class may be declared to take one or more type parameters, which are written
in angle brackets and should be supplied when you declare a variable belonging to the interface
or class or when you create a new instance of a class.
We saw one example in the previous section. Here is another:
List<String> words = new ArrayList<String>(); words.add("Hello ");
words.add("world!");
String s = words.get(0)+words.get(1); assert s.equals("Hello world!");
In the Collections Framework, class ArrayList<E> implements interface List<E>. This trivia l
code fragment declares the variable words to contain a list of strings, creates an instance of an
ArrayList, adds two strings to the list, and gets them out again.
In Java before generics, the same code would be written as follows:
List words = new ArrayList(); words.add("Hello ");
words.add("world!");
String s = ((String)words.get(0))+((String)words.get(1)) assert s.equals("Hello world!");
Without generics, the type parameters are omitted, but you must explicitly cast when- ever an
element is extracted from the list.
In fact, the bytecode compiled from the two sources above will be identical. We say that
generics are implemented by erasure because the types List<Integer>, List<String>, and
List<List<String>> are all represented at run-time by the same type, List. We also use erasure
to describe the process that converts the first program to the second. The term erasure is a slight
misnomer, since the process erases type parameters but adds casts.
Generics implicitly perform the same cast that is explicitly performed without generics. If such
casts could fail, it might be hard to debug code written with generics. This is why it is reassuring
that generics come with the following guarantee:
Cast-iron guarantee: the implicit casts added by the compilation of generics never fail.
There is also some fine print on this guarantee: it applies only when no unchecked warnings
have been issued by the compiler. Later, we will discuss at some length what causes unchecked
warnings to be issued and how to minimize their effect.
Implementing generics by erasure has a number of important effects. It keeps things simple, in
that generics do not add anything fundamentally new. It keeps things small,in that there is
exactly one implementation of List, not one version for each type. And it eases evolution, since
the same library can be accessed in both non generic and generic forms.
Another consequence of implementing generics by erasure is that array types differ in key ways
from parameterized types. Executing
new String[size] allocates an array, and stores in that array an indication that its components
are of type String. In contrast, executing: new ArrayList<String>() allocates a list, but does not
store in the list any indication of the type of its elements.
4.2.1 Generics versus Templates
Generics in Java resemble templates in C++. There are just two important things to bear in
mind about the relationship between Java generics and C++ templates: syntax and semantics.
The syntax is deliberately similar and the semantics are deliberately different.
Syntactically, angle brackets were chosen because they are familiar to C++ users, and because
square brackets would be hard to parse. However, there is one difference in syntax. In C++,
nested parameters require extra spaces, so you see things like this:List< List<String> In Java,
no spaces are required, and it’s fine to write this:List<List<String>>
You may use extra spaces if you prefer, but they’re not required. (In C++, a problem arises
because >> without the space denotes the right-shift operator. Java fixes the problem by a trick
in the grammar.)
Semantically, Java generics are defined by erasure, whereas C++ templates are defined by
expansion. In C++ templates, each instance of a template at a new type is compiled separately.
If you use a list of integers, a list of strings, and a list of lists of string, there will be three
versions of the code. If you use lists of a hundred different types, there will be a hundred
versions of the code—a problem known as code bloat. In Java, no matter how many types of
lists you use, there is always one version of the code, so bloat does not occur.
Expansion may lead to more efficient implementation than erasure, since it offers more
opportunities for optimization, particularly for primitive types such as int. For code that is
manipulating large amounts of data—for instance, large arrays in scientific computing—this
difference may be significant. However, in practice, for most purposes the difference in
efficiency is not important, whereas the problems caused by code bloat can be crucial. In C++,
you also may instantiate a template with a constant value rather than a type, making it possible
to use templates as a sort of “macroprocessor on steroids” that can perform arbitrarily complex
computations at compile time. Java generics are deliber- ately restricted to types, to keep them
simple and easy to understand.
4.3 Generics and Subtyping
Subtyping is a key feature of object-oriented languages such as Java. In Java, one type is a
subtype of another if they are related by an extends or implements clause. Here are some
examples:
Integer is a subtype of Number
Double is a subtype of Number
ArrayList<E> is a subtype of List<E>
List<E> is a subtype of Collection<E>
Collection<E> is a subtype of Iterable<E>
Subtyping is transitive, meaning that if one type is a subtype of a second, and the second is a
subtype of a third, then the first is a subtype of the third. So, from the last two lines in the
preceding list, it follows that List<E> is a subtype of Iterable<E>. If one type is a subtype of
another, we also say that the second is a supertype of the first. Every reference type is a subtype
of Object, and Object is a supertype of every reference type. We also say, trivially, that every
type is a subtype of itself.
The Substitution Principle tells us that wherever a value of one type is expected, one may
provide a value of any subtype of that type:
Substitution Principle: a variable of a given type may be assigned a value of any subtype of
that type, and a method with a parameter of a given type may be invoked with an argument of
any subtype of that type.
Consider the interface Collection<E>. One of its methods is add, which takes a param- eter of
type E:
interface Collection<E> { public boolean add(E elt);
...
}
According to the Substitution Principle, if we have a collection of numbers, we may add an
integer or a double to it, because Integer and Double are subtypes of Number.
List<Number> nums = new ArrayList<Number>(); nums.add(2);
nums.add(3.14);
assert nums.toString().equals("[2, 3.14]");
Here, subtyping is used in two ways for each method call. The first call is permitted because
nums has type List<Number>, which is a subtype of Collection<Number>, and 2 has type
Integer (thanks to boxing), which is a subtype of Number. The second call is simila r ly
permitted. In both calls, the E in List<E> is taken to be Number.
It may seem reasonable to expect that since Integer is a subtype of Number, it follows that
List<Integer> is a subtype of List<Number>. But this is not the case, because the
Substitution Principle would rapidly get us into trouble. It is not always safe to assign a value
of type List<Integer> to a variable of type List<Number>. Consider the following code
fragment:
List<Integer> ints = new ArrayList<Integer>(); ints.add(1);
ints.add(2);
List<Number> nums = ints; // compile-time error
nums.add(3.14);
assert ints.toString().equals("[1, 2, 3.14]");
This code assigns variable ints to point at a list of integers, and then assigns nums to point at
the same list of integers; hence the call in the fifth line adds a double to this
list, as shown in the last line. This must not be allowed! The problem is prevented by
observing that here the Substitution Principle does not apply: the assignment on the fourth line
is not allowed because List<Integer> is not a subtype of List<Number>, and the compiler
reports that the fourth line is in error.
What about the reverse? Can we take List<Number> to be a subtype of List<Integer>? No, that
doesn’t work either, as shown by the following code:
List<Number> nums = new ArrayList<Number>(); nums.add(2.78);
nums.add(3.14);
List<Integer> ints = nums; // compile-time error
assert ints.toString().equals("[2.78, 3.14]");
The problem is prevented by observing that here the Substitution Principle does not apply: the
assignment on the fourth line is not allowed because List<Number> is not a subtype of
List<Integer>, and the compiler reports that the fourth line is in error.
So List<Integer> is not a subtype of List<Number>, nor is List<Number> a subtype of
List<Integer>; all we have is the trivial case, where List<Integer> is a subtype of itself, and we
also have that List<Integer> is a subtype of Collection<Integer>.
Arrays behave quite differently; with them, Integer is a subtype of Number. We will compare
the treatment of lists and arrays later . Sometimes we would like lists to behave more like
arrays, in that we want to accept not only a list with elements of a given type, but also a list
with elements of any subtype of a given type. For this purpose, we use wildcards.
The first call is permitted because nums has type List<Number>, which is a subtype of
Collection<Number>, and ints has type List<Integer>, which is a subtype of Collec tion<?
extends Number>. The second call is similarly permitted. In both calls, E is taken to be
Number. If the method signature for addAll had been written without the wildcard, then the
calls to add lists of integers and doubles to a list of numbers would not have been permitted;
you would only have been able to add a list that was explicitly declared to be a list of numbers.
We can also use wildcards when declaring variables. Here is a variant of the example at the
end of the preceding section, changed by adding a wildcard to the second line:
Before, the fourth line caused a compile-time error (because List<Integer> is not a subtype of
List<Number>), but the fifth line was fine (because a double is a number, so you can add a
double to a List<Number>). Now, the fourth line is fine (because List<Integer> is a subtype of
List<? extends Number>), but the fifth line causes a com- pile-time error (because you cannot
add a double to a List<? extends Number>, since it might be a list of some other subtype of
number). As before, the last line shows why one of the preceding lines is illegal!
In general, if a structure contains elements with a type of the form ? extends E, we can get
elements out of the structure, but we cannot put elements into the structure. To put elements
into the structure we need another kind of wildcard, as explained in the next section.
public static <T> void copy(List<? super T> dst, List<? extends T> src)
{
for (int i = 0; i < src.size(); i++) {
dst.set(i, src.get(i));
}
}
The quizzical phrase ? super T means that the destination list may have elements of any type
that is a supertype of T, just as the source list may have elements of any type that is a subtype
of T.
Here is a sample call.
As with any generic method, the type parameter may be inferred or may be given ex- plicitly.
In this case, there are four possible choices, all of which type-check and all of which have the
same effect:
Collections.copy(objs, ints);
Collections.<Object>copy(objs, ints);
Collections.<Number>copy(objs, ints);
Collections.<Integer>copy(objs, ints);
The first call leaves the type parameter implicit; it is taken to be Integer, since that is the most
specific choice that works. In the third line, the type parameter T is taken to be Number. The
call is permitted because objs has type List<Object>, which is a subtype of List<? super
Number> (since Object is a supertype of Number, as required by the wildcard) and ints has
type List<Integer>, which is a subtype of List<? extends Num ber> (since Integer is a subtype
of Number, as required by the extends wildcard).
We could also declare the method with several possible signatures.
The first of these is too restrictive, as it only permits calls when the destination and source have
exactly the same type. The remaining three are equivalent for calls that use implicit type
parameters, but differ for explicit type parameters. For the example calls above, the second
signature works only when the type parameter is Object, the third signature works only when
the type parameter is Integer, and the last signature works (as we have seen) for all three type
parameters—i.e., Object, Number, and Integer. Al- ways use wildcards where you can in a
signature, since this permits the widest range of calls.
The contains method checks whether a collection contains a given object, and its gen-
eralization, containsAll, checks whether a collection contains every element of another
collection. This section presents two alternate approaches to giving generic signatures
for these methods. The first approach uses wildcards and is the one used in the Java Collectio ns
Framework. The second approach uses type parameters and is often a more appropriate
alternative.
Wildcards Here are the types that the methods have in Java with generics:
interface Collection<E> {
...
public boolean contains(Object o);
public boolean containsAll(Collection<?> c);
...
}
The first method does not use generics at all! The second method is our first sight of an
important abbreviation. The type Collection<?> stands for:
Collection<? extends Object>
Extending Object is one of the most common uses of wildcards, so it makes sense to provide a
short form for writing it.
These methods let us test for membership and containment:
The given list of objects contains both the string "one" and the given list of integers, but the
given list of integers does not contain the string "one", nor does it contain the given list of
objects.
The tests ints.contains(obj) and ints.containsAll(objs) might seem silly. Of course, a list of
integers won’t contain an arbitrary object, such as the string "one". But it is permitted because
sometimes such tests might succeed:
Object obj = 1;
List<Object> objs = Arrays.<Object>asList(1, 3);
List<Integer> ints = Arrays.asList(1, 2, 3, 4);
assert ints.contains(obj);
assert ints.containsAll(objs);
In this case, the object may be contained in the list of integers because it happens to be an
integer, and the list of objects may be contained within the list of integers because every object
in the list happens to be an integer. Type Parameters You might reasonably choose an
alternative design for collections a design in which you can only test containment for subtypes
of the element type:
interface MyCollection<E> { // alternative design
...
public boolean contains(E o);
public boolean containsAll(Collection<? extends E> c);
...
}
Say we have a class MyList that implements MyCollection. Now the tests are legal only one
way around:
Object obj = "one";
MyList<Object> objs = MyList.<Object>asList("one", 2, 3.14, 4);
MyList<Integer> ints = MyList.asList(2, 4);
assert objs.contains(obj); assert objs.containsAll(ints)
assert !ints.contains(obj); // compile-time error
assert !ints.containsAll(objs); // compile-time error
The last two tests are illegal, because the type declarations require that we can only test whether
a list contains an element of a subtype of that list. So we can check whether a list of objects
contains a list of integers, but not the other way around.
Which of the two styles is better is a matter of taste. The first permits more tests, and the second
catches more errors at compile time (while also ruling out some sensible tests). The designers
of the Java libraries chose the first, more liberal, alternative, be- cause someone using the
Collections Framework before generics might well have written a test such as ints
containsAll(objs), and that person would want that test to remain valid after generics were
added to Java. However, when designing a new generic library, such as MyCollection, when
backward compatibility is less important, the de- sign that catches more errors at compile time
might make more sense.
Arguably, the library designers made the wrong choice. Only rarely will a test such as
ints.containsAll(objs) be required, and such a test can still be permitted by declaring ints to
have type List<Object> rather than type List<Integer>. It might have been better to catch more
errors in the common case rather than to permit more-precise typing in an uncommon case.
The same design choice applies to other methods that contain Object or Collection<? > in their
signature, such as remove, removeAll, and retainAll.
The wildcard signature is slightly shorter and clearer, and is the one used in the library. If you
use the second signature, it is easy to implement the method:
Another reason to know about wildcard capture is that it can show up in error messages, even
if you don’t use the above technique. In general, each occurrence of a wildcard is taken to stand
for some unknown type. If the compiler prints an error message containing this type, it is
referred to as capture of ?. For instance, with Sun’s current compiler, the incorrect version of
reverse generates the following error message:
Capture.java:6: set(int,capture of ?) in java.util.List<capture of ?> cannot be applied to
(int,java.lang.Object)
list.set(i, tmp.get(list.size()- i-1));
^
Hence, if you see the quizzical phrase capture of ? in an error message, it will come from a
wildcard type. Even if there are two distinct wildcards, the compiler will print the type
associated with each as capture of ?. Bounded wildcards generate names that are even more
long-winded, such as capture of ? extends Number.
This is usually not a hardship. The Get and Put Principle tells us that if a structure contains a
wildcard, we should only get values out of it (if it is an extends wildcard) or only put values
into it (if it is a super wildcard). For a structure to be useful, we must do both. Therefore, we
usually create a structure at a precise type, even if we use wildcard types to put values into or
get values from the structure, as in the following example:
Here wildcards appear in the second and third lines, but not in the first line that creates the
list.Only top-level parameters in instance creation are prohibited from containing wild- cards.
Nested wildcards are permitted. Hence, the following is legal:
Even though the list of lists is created at a wildcard type, each individual list within it has a
specific type: the first is a list of integers and the second is a list of strings. The wildcard type
prohibits us from extracting elements from the inner lists as any type other than Object, but
since that is the type used by toString, this code is well typed.
One way to remember the restriction is that the relationship between wildcards and ordinary
types is similar to the relationship between interfaces and classes—wildcards and interfaces
are more general, ordinary types and classes are more specific, and in- stance creation requires
the more specific information. Consider the following three statements:
The first is legal; the second is illegal because an instance creation expression requires a class,
not an interface; and the third is illegal because an instance creation expression requires an
ordinary type, not a wildcard.
You might wonder why this restriction is necessary. The Java designers had in mind that every
wildcard type is shorthand for some ordinary type, so they believed that ultimately every object
should be created with an ordinary type. It is not clear whether this restriction is necessary, but
it is unlikely to be a problem. Generic Method Calls If a generic method call includes explic it
type parameters, those type parameters must not be wildcards. For example, say we have the
following generic method:
class Lists {
public static <T> List<T> factory() { return new ArrayList<T>(); }
}
You may choose for the type parameters to be inferred, or you may pass an explicit type
parameter. Both of the following are legal:
The motivation for this restriction is similar to the previous one. Again, it is not clear whether
it is necessary, but it is unlikely to be a problem.
class Lists {
public static <T> List<T> toList(T[] arr) { List<T> list = new ArrayList<T>();
for (T elt : arr) list.add(elt); return list;
}
}
The static method toList accepts an array of type T[] and returns a list of type List<T>, and
does so for any type T. This is indicated by writing <T> at the beginning of the method
signature, which declares T as a new type variable. A method which declares a type variable
in this way is called a generic method. The scope of the type variable T is local to the method
itself; it may appear in the method signature and the method body, but not outside the method.
The method may be invoked as follows:
In the first line, boxing converts 1, 2, 3 from int to Integer. Packing the arguments into an array
is cumbersome. The vararg feature permits a spe- cial, more convenient syntax for the case in
which the last argument of a method is an array. To use this feature, we replace T[] with T…
in the method declaration:
class Lists {
public static <T> List<T> toList(T... arr) { List<T> list = new ArrayList<T>();
for (T elt : arr) list.add(elt); return list;
}
}
Now the method may be invoked as follows:
List<Integer> ints = Lists.toList(1, 2, 3);
List<String> words = Lists.toList("hello", "world");
This is just shorthand for what we wrote above. At run time, the arguments are packed into an
array which is passed to the method, just as previously.
Any number of arguments may precede a last vararg argument. Here is a method that accepts
a list and adds all the additional arguments to the end of the list:
public static <T> void addAll(List<T> list, T... arr) { for (T elt : arr) list.add(elt);
}
Whenever a vararg is declared, one may either pass a list of arguments to be implicitly packed
into an array, or explicitly pass the array directly. Thus, the preceding method may be invoked
as follows:
List<Integer> ints = new ArrayList<Integer>(); Lists.addAll(ints, 1, 2);
Lists.addAll(ints, new Integer[] { 3, 4 }); assert ints.toString().equals("[1, 2, 3, 4]");
We will see later that when we attempt to create an array containing a generic type, we will
always receive an unchecked warning. Since varargs always create an array, they should be
used only when the argument does not have a generic type.
In the preceding examples, the type parameter to the generic method is inferred, but it may also
be given explicitly, as in the following examples:
List<Integer> ints = Lists.<Integer>toList();
List<Object> objs = Lists.<Object>toList(1, "two");
Explicit parameters are usually not required, but they are helpful in the examples given here.
In the first example, without the type parameter there is too little information for the type
inference algorithm used by Sun's compiler to infer the correct type. It infers that the argument
to toList is an empty array of an arbitrary generic type rather than an empty array of integers,
and this triggers the unchecked warning described earlier. (The Eclipse compiler uses a
different inference algorithm, and compiles the same line correctly without the explic it
parameter.) In the second example, without the type parameter there is too much informa tio n
for the type inference algorithm to infer the correct type. You might think that Object is the
only type that an integer and a string have in common, but in fact they also both implement the
interfaces Serializable and Comparable. The type inference algorithm cannot choose which of
these three the correct type is.
In general, the following rule of thumb suffices: in a call to a generic method, if there are one
or more arguments that correspond to a type parameter and they all have the same type then
the type parameter may be inferred; if there are no arguments that correspond to the type
parameter or the arguments belong to different subtypes of the intended type then the type
parameter must be given explicitly.
When a type parameter is passed to a generic method invocation, it appears in angle brackets
to the left, just as in the method declaration. The Java grammar requires that
type parameters may appear only in method invocations that use a dotted form. Even if the
method toList is defined in the same class that invokes the code, we cannot shorten it as follows :
List<Integer> ints = <Integer>toList(); // compile-time error
The Java platform contains three general-purpose Set implementations: HashSet, TreeSet,
and LinkedHashSet. HashSet, which stores its elements in a hash table, is the best-performing
implementation; however it makes no guarantees concerning the order of iteration. TreeSet,
which stores its elements in a red-black tree, orders its elements based on their values; it is
substantially slower than HashSet. LinkedHashSet, which is implemented as a hash table with
a linked list running through it, orders its elements based on the order in which they were
inserted into the set (insertion-order). LinkedHashSet spares its clients from the unspecified,
generally chaotic ordering provided by HashSet at a cost that is only slightly higher.
Here's a simple but useful Set idiom. Suppose you have a Collection, c, and you want to create
another Collection containing the same elements but with all duplicates eliminated. The
following one-liner does the trick.
Or, if using JDK 8 or later, you could easily collect into a Set using aggregate operations:
c.stream()
.collect(Collectors.toSet()); // no duplicates
Here's a slightly longer example that accumulates a Collection of names into a TreeSet:
import java.util.*;
import java.util.stream.*;
import java.util.*;
Note that the code always refers to the Collection by its interface type (Set) rather than by its
implementation type. This is a strongly recommended programming practice because it gives
you the flexibility to change implementations merely by changing the constructor. If either of
the variables used to store a collection or the parameters used to pass it around are declared to
be of the Collection's implementation type rather than its interface type, all such variables and
parameters must be changed in order to change its implementation type.
Furthermore, there's no guarantee that the resulting program will work. If the program uses any
nonstandard operations present in the original implementation type but not in the new one, the
program will fail. Referring to collections only by their interface prevents you from using any
nonstandard operations.
The implementation type of the Set in the preceding example is HashSet, which makes no
guarantees as to the order of the elements in the Set. If you want the program to print the word
list in alphabetical order, merely change the Set's implementation type
from HashSet to TreeSet. Making this trivial one-line change causes the command line in the
previous example to generate the following output.
To calculate the union, intersection, or set difference of two sets nondestructively (without
modifying either set), the caller must copy one set before calling the appropriate bulk operation.
The following are the resulting idioms.
Let's revisit the FindDups program. Suppose you want to know which words in the argument
list occur only once and which occur more than once, but you do not want any duplicates
printed out repeatedly. This effect can be achieved by generating two sets — one containing
every word in the argument list and the other containing only the duplicates. The words that
occur only once are the set difference of these two sets, which we know how to compute. Here's
how the resulting program looks.
import java.util.*;
// Destructive set-difference
uniques.removeAll(dups);
Positional access — manipulates elements based on their numerical position in the list.
This includes methods such as get, set, add, addAll, and remove.
Search — searches for a specified object in the list and returns its numerical position.
Search methods include indexOf and lastIndexOf.
Iteration — extends Iterator semantics to take advantage of the list's sequential nature.
The listIterator methods provide this behavior.
Range-view — The sublist method performs arbitrary range operations on the list.
The Java platform contains two general-purpose List implementations. ArrayList, which is
usually the better-performing implementation, and LinkedList which offers better performance
under certain circumstances.
list1.addAll(list2);
Here's a nondestructive form of this idiom, which produces a third List consisting of the second
list appended to the first.
Like the Set interface, List strengthens the requirements on the equals and hashCode methods
so that two List objects can be compared for logical equality without regard to their
implementation classes. Two List objects are equal if they contain the same elements in the
same order.
The addAll operation inserts all the elements of the specified Collection starting at the
specified position. The elements are inserted in the order they are returned by the
specified Collection's iterator. This call is the positional access analog
of Collection's addAll operation.
import java.util.*;
import java.util.*;
The three methods that ListIterator inherits from Iterator (hasNext, next, and remove) do
exactly the same thing in both interfaces. The hasPrevious and the previous operations are
exact analogues of hasNext and next. The former operations refer to the element before the
(implicit) cursor, whereas the latter refer to the element after the cursor. The previous operation
moves the cursor backward, whereas next moves it forward.
Intuitively speaking, the cursor is always between two elements — the one that would be
returned by a call to previous and the one that would be returned by a call to next.
The n+1 valid index values correspond to the n+ 1 gap between elements, from the gap before
the first element to the gap after the last one. The following figure shows the five possible
cursor positions in a list containing four elements.
Calls to next and previous can be intermixed, but you have to be a bit careful. The first call
to previous returns the same element as the last call to next. Similarly, the first call to next after
a sequence of calls to previous returns the same element as the last call to previous.
It should come as no surprise that the nextIndex method returns the index of the element that
would be returned by a subsequent call to next, and previousIndex returns the index of the
element that would be returned by a subsequent call to previous. These calls are typically used
either to report the position where something was found or to record the position of
the ListIterator so that another ListIterator with identical position can be created.
It should also come as no surprise that the number returned by nextIndex is always one greater
than the number returned by previousIndex. This implies the behaviour of the two boundary
cases: (1) a call to previousIndex when the cursor is before the initial element returns -1 and
(2) a call to nextIndex when the cursor is after the final element returns list.size(). To make all
this concrete, the following is a possible implementation of List.indexOf.
public int indexOf(E e) {
for (ListIterator<E> it = listIterator();
it.hasNext(); )
if (e == null ? it.next() == null : e.equals(it.next()))
return it.previousIndex();
// Element not found
return -1;
}
Note that the indexOf method returns it.previousIndex() even though it is traversing the list in
the forward direction. The reason is that it.nextIndex() would return the index of the element
we are about to examine, and we want to return the index of the element we just examined.
The Iterator interface provides the remove operation to remove the last element returned
by next from the Collection. For ListIterator, this operation removes the last element returned
by nextor previous. The ListIterator interface provides two additional operations to modify the
list — set and add. The set method overwrites the last element returned
by next or previous with the specified element. The following polymorphic algorithm
uses set to replace all occurrences of one specified value with another.
The add method inserts a new element into the list immediately before the current cursor
position. This method is illustrated in the following polymorphic algorithm to replace a ll
occurrences of a specified value with the sequence of values contained in the specified list.
This method eliminates the need for explicit range operations (of the sort that commonly exist
for arrays). Any operation that expects a List can be used as a range operation by passing
a subList view instead of a whole List. For example, the following idiom removes a range of
elements from a List.
list.subList(fromIndex, toIndex).clear();
Similar idioms can be constructed to search for an element in a range.
Any polymorphic algorithm that operates on a List, such as the replace and shuffle examples,
works with the List returned by subList.
Here's a polymorphic algorithm whose implementation uses subList to deal a hand from a deck.
That is, it returns a new List (the "hand") containing the specified number of elements taken
from the end of the specified List (the "deck"). The elements returned in the hand are removed
from the deck.
import java.util.*;
% java Deal 4 5
Although the subList operation is extremely powerful, some care must be exercised when using
it. The semantics of the List returned by subList become undefined if elements are added to or
removed from the backing List in any way other than via the returned List. Thus, it's highly
recommended that you use the List returned by subList only as a transient object — to perform
one or a sequence of range operations on the backing List. The longer you use
the subList instance, the greater the probability that you'll compromise it by modifying the
backing List directly or through another subListobject. Note that it is legal to modify a sublist
of a sublist and to continue using the original sublist (though not concurrently).
Each Queue method exists in two forms: (1) one throws an exception if the operation fails, and
(2) the other returns a special value if the operation fails (either null or false, depending on the
operation). The regular structure of the interface is illustrated in the following table.
Queues typically, but not necessarily, order elements in a FIFO (first-in- first-out) manner.
Among the exceptions are priority queues, which order elements according to their values.
Whatever ordering is used, the head of the queue is the element that would be removed by a
call to remove or poll. In a FIFO queue, all new elements are inserted at the tail of the queue.
Other kinds of queues may use different placement rules. Every Queue implementation must
specify its ordering properties.
It is possible for a Queue implementation to restrict the number of elements that it holds; such
queues are known as bounded. Some Queue implementations in java.util.concurrent are
bounded, but the implementations in java.util are not.
The add method, which Queue inherits from Collection, inserts an element unless it would
violate the queue's capacity restrictions, in which case it throws IllegalStateExceptio n.
The offer method, which is intended solely for use on bounded queues, differs from add only
in that it indicates failure to insert an element by returning false.
The remove and poll methods both remove and return the head of the queue. Exactly which
element gets removed is a function of the queue's ordering policy.
The remove and poll methods differ in their behavior only when the queue is empty. Under
these circumstances, remove throws NoSuchElementException, while poll returns null.
The element and peek methods return, but do not remove, the head of the queue. They differ
from one another in precisely the same fashion as remove and poll: If the queue is
empty, element throws NoSuchElementException, while peek returns null.
The Queue interface does not define the blocking queue methods, which are common in
concurrent programming. These methods, which wait for elements to appear or for space to
become available, are defined in the interface java.util.concurrent.BlockingQueue, which
extends Queue.
In the following example program, a queue is used to implement a countdown timer. The queue
is preloaded with all the integer values from a number specified on the command line to zero,
in descending order. Then, the values are removed from the queue and printed at one-second
intervals. The program is artificial in that it would be more natural to do the same thing without
using a queue, but it illustrates the use of a queue to store elements prior to subsequent
processing.
import java.util.*;
while (!queue.isEmpty()) {
System.out.println(queue.remove());
Thread.sleep(1000);
}
}
}
In the following example, a priority queue is used to sort a collection of elements. Again this
program is artificial in that there is no reason to use it in favor of the sort method provided
in Collections, but it illustrates the behavior of priority queues.
while (!queue.isEmpty())
result.add(queue.remove());
return result;
}
Note that the Deque interface can be used both as last-in-first-out stacks and first-in- first- o ut
queues. The methods given in the Deque interface are divided into three parts:
4.10.1 Insert
The addfirst and offerFirst methods insert elements at the beginning of the Deque instance.
The methods addLast and offerLast insert elements at the end of the Deque instance. When the
capacity of the Deque instance is restricted, the preferred methods
are offerFirst and offerLast because addFirst might fail to throw an exception if it is full.
4.10.2 Remove
The removeFirst and pollFirst methods remove elements from the beginning of
the Deque instance. The removeLast and pollLast methods remove elements from the end. The
methods pollFirst and pollLast return null if the Deque is empty whereas the
methods removeFirst and removeLast throw an exception if the Deque instance is empty.
4.10.3 Retrieve
The methods getFirst and peekFirst retrieve the first element of the Deque instance. These
methods dont remove the value from the Deque instance. Similarly, the
methods getLast and peekLastretrieve the last element. The
methods getFirst and getLast throw an exception if the deque instance is empty whereas the
methods peekFirst and peekLast return NULL.
The 12 methods for insertion, removal and retieval of Deque elements are summarized in the
following table:
Deque Methods
addFirst(e) addLast(e)
Insert
offerFirst(e) offerLast(e)
removeFirst() removeLast()
Remove
pollFirst() pollLast()
getFirst() getLast()
Examine
peekFirst() peekLast()
The Java platform contains three general-purpose Map implementations: HashMap, TreeMap,
and LinkedHashMap. Their behavior and performance are precisely analogous
to HashSet, TreeSet, and LinkedHashSet.
Modeling real-world objects is a common task in object-oriented programming, so it is
reasonable to think that some programs might, for example, group employees by department:
// Cascade Collectors
Map<String, Map<String, List<Person>>> peopleByStateAndCity
= personStream.collect(Collectors.groupingBy(Person::getState,
Collectors.groupingBy(Person::getCity)))
import java.util.*;
8 distinct words:
{to=3, delegate=1, be=1, it=2, up=1, if=1, me=1, is=2}
Suppose you'd prefer to see the frequency table in alphabetical order. All you have to do is
change the implementation type of the Map from HashMap to TreeMap. Making this four-
character change causes the program to generate the following output from the same command
line.
8 distinct words:
{be=1, delegate=1, if=1, is=2, it=2, me=1, to=3, up=1}
Similarly, you could make the program print the frequency table in the order the words first
appear on the command line simply by changing the implementation type of the map
to LinkedHashMap. Doing so results in the following output.
8 distinct words:
{if=1, it=2, is=2, to=3, be=1, up=1, me=1, delegate=1}
This flexibility provides a potent illustration of the power of an interface-based framework.
static <K, V> Map<K, V> newAttributeMap(Map<K, V>defaults, Map<K, V> overrides) {
Map<K, V> result = new HashMap<K, V>(defaults);
result.putAll(overrides);
return result;
}
4.11.3 Collection Views
The Collection view methods allow a Map to be viewed as a Collection in these three ways:
The Collection views provide the only means to iterate over a Map. This example illustra tes
the standard idiom for iterating over the keys in a Map with a for-each construct:
The idiom for iterating over values is analogous. Following is the idiom for iterating over key-
value pairs.
With all three Collection views, calling an Iterator's remove operation removes the associated
entry from the backing Map, assuming that the backing Map supports element removal to
begin with. This is illustrated by the preceding filtering idiom.
With the entrySet view, it is also possible to change the value associated with a key by calling
a Map. Entry's setValue method during iteration (again, assuming the Map supports value
modification to begin with). Note that these are the only safe ways to modify a Map during
iteration; the behavior is unspecified if the underlying Map is modified in any other way while
the iteration is in progress.
The Collection views support element removal in all its many forms
— remove, removeAll, retainAll, and clear operations, as well as
the Iterator.remove operation. (Yet again, this assumes that the backing Map supports element
removal.)
The Collection views do not support element addition under any circumstances. It would make
no sense for the keySet and values views, and it's unnecessary for the entrySet view, because
the backing Map's put and putAll methods provide the same functionality.
Collections.sort(l);
If the List consists of String elements, it will be sorted into alphabetical order. If it consists
of Date elements, it will be sorted into chronological order. How does this
happen? String and Date both impleme nt
the Comparable interface. Comparable implementations provide a natural ordering for a class,
which allows objects of that class to be sorted automatically. The following table summar izes
some of the more important Java platform classes that implement Comparable.
String Lexicographic
Date Chronological
import java.util.*;
Name objects are immutable. All other things being equal, immutable types are the way
to go, especially for objects that will be used as elements in Sets or as keys in Maps.
These collections will break if you modify their elements or keys while they're in the
collection.
The constructor checks its arguments for null. This ensures that all Name objects are
well formed so that none of the other methods will ever throw a NullPointerExceptio n.
The hashCode method is redefined. This is essential for any class that redefines
the equals method. (Equal objects must have equal hash codes.)
The equals method returns false if the specified object is null or of an inappropriate
type. The compareTo method throws a runtime exception under these circumstances.
Both of these behaviors are required by the general contracts of the respective methods.
The toString method has been redefined so it prints the Name in human-readable form.
This is always a good idea, especially for objects that are going to get put into
collections. The various collection types' toString methods depend on
the toString methods of their elements, keys, and values.
Since this section is about element ordering, let's talk a bit more
about Name's compareTo method. It implements the standard name-ordering algorithm, where
last names take precedence over first names. This is exactly what you want in a natural
ordering. It would be very confusing indeed if the natural ordering were unnatural!
Take a look at how compareTo is implemented, because it's quite typical. First, you compare
the most significant part of the object (in this case, the last name). Often, you can just use the
natural ordering of the part's type. In this case, the part is a String and the natural
(lexicographic) ordering is exactly what's called for. If the comparison results in anything other
than zero, which represents equality, you're done: You just return the result. If the most
significant parts are equal, you go on to compare the next most-significant parts. In this case,
there are only two parts — first name and last name. If there were more parts, you'd proceed in
the obvious fashion, comparing parts until you found two that weren't equal or you were
comparing the least-significant parts, at which point you'd return the result of the comparison.
Just to show that it all works, here's a program that builds a list of names and sorts them.
import java.util.*;
// Endpoints
E first();
E last();
// Comparator access
Comparator<? super E> comparator();
}
The Iterator returned by the iterator operation traverses the sorted set in order.
The array returned by toArray contains the sorted set's elements in order.
Although the interface doesn't guarantee it, the toString method of the Java
platform's SortedSet implementations returns a string containing all the elements of the sorted
set, in order.
Sorted sets provide three range-view operations. The first, subSet, takes two endpoints,
like subList. Rather than indices, the endpoints are objects and must be comparable to the
elements in the sorted set, using the Set's Comparator or the natural ordering of its elements,
whichever the Set uses to order itself. Like subList, the range is half open, including its low
endpoint but excluding the high one.
Thus, the following line of code tells you how many words between "doorbell" and "pickle ",
including "doorbell" but excluding "pickle", are contained in a SortedSet of strings
called dictionary:
dictionary.subSet("f", "g").clear();
A similar trick can be used to print a table telling you how many words begin with each letter.
Thus, the following one-liner tells you how many words between "doorbell" and "pickle ",
including doorbell and pickle, are contained in the dictionary.
The following idiom obtains the first element that is less than a specified object o in the element
space.
In a GUI program, the user is much more in control of the program: There are buttons to be
pressed, the window can be moved, the menu activated, or an animation may be playing. This
requires a different style of programming: Instead of following a fixed sequence of actions, a
GUI program reacts to events. There are many different kinds of events: the user may click
with the mouse, move the mouse button, press some key, close the window, move or resize the
window, and so on.
At first sight, it's therefore a bit difficult to figure out what is happing in a GUI program. There
are always two steps: First, we need to set up the windows and configure their contents. Then,
the program doesn't do anything actively — it only acts by responding to events. These
responses to events have been set up in the first phase. We will use the Scala Swing library for
building our user interfaces. Swing is actually a Java library, but using the Scala version is
easier and more elegant.
5.1 Basics-Creating a Window
As usual, when we run GuiProgramOne, its main method is executed. This method creates a
new window, a class of type UI. We make the window appear on the screen by setting its visib le
member.
If you look carefully, you will see that the output "End of main function" appears in the
terminal, but the program does not stop. Even though the main method of GuiProgra mO ne
returns, the program keeps running — the Swing library takes care of that.
We have not set up any responses to events in this program, but some actions already work
fine: For instance, you can move and resize the window, you can iconify it or maximize it. The
content is nicely moved to appear in the centre. Finally, when you press the X symbol in the
title bar, the window closes—and the program terminates! All these events are handled by the
Swing library itself, so we do not have to worry about them.
Let's have a look at the UI (for UserInterface) class. This class extends the Swing class
scala.swing.MainFrame, which is a window that can appear on the screen. This means that a
UI object has all the methods and fields of the MainFrame class in addition to its own methods
and fields, and can be used like a MainFrame.
In this small program, the UI constructor only sets three of its fields (which it has "inherited "
from the MainFrame class), namely title (which is the title that appears on top of the window),
the preferred size (which is the starting size of the window), and its contents. The contents is
what will appear inside the window, and it has to be a component, that is, an object of a class
that extends scala.swing.Component. There are many such component classes. Here we use a
very simple one, scala.swing.Label, which only displays a string.
When we run the program, it looks similar to the previous one, but the color of the window is
different, and when you move the mouse over it you notice that it is actually a large button:
When you press the button, the text "Thank you" appears on the terminal. Try it!
By the way, we could have written the second argument list with the usual syntax with round
parentheses:
The following example can be compiled as it stands to create a simple "HelloWor ld "
scala.swing program as depicted in Figure 5.1:
import swing. _
object HelloWorld extends SimpleSwingApplication {
def top = new MainFrame {
title = "Hello, World!"
contents = new Button {
text = "Click Me!"
}
}
}
Most simple scala.swing applications consist of a main object such as HelloWorld above that
extends class SimpleSwingApplication. The main object needs to implement method top that
takes no arguments and returns a Frame, or as in this case, a MainFrame. A frame is a standard
operating system window with decorations such as a close button and a resize handle.
A MainFrame is a frame that automatically quits the application when closed and is thus used
by most applications that need only a single frame.
On startup, class SimpleSwingApplication takes care of initializing the Swing framework and
opens the frame returned by method top. The above implementation creates a main frame using
the standard scala.swing notation for member initialization. We are really creating an
anonymous class with the following expression.
new MainFrame {
title = "Hello, World!"
contents = new Button {
text = "Click Me!"
}
}
Anonymous class syntax lets us access all members of class MainFrame inside the inner curly
braces. We are setting the frame title property to the string "Hello, World!" and the contents of
the frame to a simple click button that reads "Click Me!" and does nothing when clicked. Many
classes provide a small number of convenience constructors, which are similar to those
provided by Java Swing and make component creation more concise. For the button
instantiation above, we could have written
Adding a button to the above panel with a BorderLayout manager looks as follows:
panel.add(new JButton("click me"), BorderLayout.CENTER)
The JPanel.add methods call the layout manager through a common interface to make a
given component and its layout constraints available.
Different from Java Swing, containers and layout manager in scala.swing are coupled. This
allows for an interface that is more concise and catches more type errors at compile time. In
Java Swing, layout constraints can only be of type Object or Int as dictated by the add methods’
interface above. A generic panel class that takes its layoutmanager and constraints type as a
type argument could cater for a better typed but also more complex interface:
class JGenericPanel [ L <: LayoutManager[C],
C <: LayoutConstraint ] extends JComponent {
protected def layout: L
def add(comp: Component, constraints: C) = {
...
layout.addLayoutComponent(comp, constraints)
...
}
}
Furthermore, by using object composition for layout managers and containers we do not gain
any flexibility, as changing a layout manager inevitably results in resetting incompatib le
constraints for every child component. This results in the same amount of work as creating a
new container and readding all child components.
The above two arguments have led us to the following unified layout container interface:
listenTo(fahrenheit, celsius)
reactions += {
case EditDone(‘fahrenheit‘) =>
val f = Integer.parseInt(fahrenheit.text)
val c = (f 32)* 5 / 9
celsius.text = c.toString
case EditDone(‘celsius‘) =>
val c = Integer.parseInt(celsius.text)
val f = c * 9 / 5 + 32
fahrenheit.text = f.toString
}
We first indicate that our main object is interested in events from each text field by calling
method listenTo from class Reactor, which is a base class of SimpleSwingApplication. Then
we add two reactions to EditDone events from text field celsius and text field Fahrenheit that
each update the contents of the other text field that has not been edited. Events are usually case
classes or classes with extractors, so that clients can easily pattern match on them as in the
example above.
The complete source code for this example can be found in the scala.swing test package.
new JComponent {
addMouseListener(new MouseAdapter {
@Override
def mouseClicked(e: MouseEvent) {
System.out.println("Mouse clicked at " + e.getPoint)
}
})
}
Note that we are using the convenience class MouseAdapter, which implements all methods
from interface MouseListener, in order to avoid implementing the other methods from
MouseListener.
This interface is one example were we first dispatch on the event’s Java type (MouseEvent)
and then refine the match by implementing one of the listener’s methods which all take the
same parameter (using listener MouseListener and method mouseClicked). The equivalent in
scala.swing, which matches on the event’s type, looks as follows.
new Component {
listenTo(mouse)
reactions += {
case e: MouseClicked =>
println("Mouse clicked at " + e.point)
}
}
For efficiency reasons, mouse events are not published by a component itself, but by its
member mouse. Therefore, we first have to listenTo the mouse publisher.
which notifies all registered reactors. The sole purpose of trait Event for now is to indicate that
some class defines an event. To make an object a publisher, simply extend class Publisher and
call the above method to publish events. A reactor can be registered and deregistered to one or
more publishers with the following methods in class Reactor:
5.5.3 Actions
Java Swing provides an interface javax.swing.Action which encapsulates code to be executed
as a reaction to an event. Different from a listener, though, an action also captures informa tio n
that can be useful for several types of user interface components. Often used properties are an
action’s name that usually manifests itself as a button’s or menu item’s title and an action’s
mnemonic key which is used as a keyboard shortcut for various kinds of buttons and menu
items.
In scala.swing, we represent actions by the Action trait that is wrapper around it’s Java Swing
peer similar to components. The Action companion object defines several convenie nce
members. The following method lets clients create actions very conveniently.
def apply(title: String)(block: =>Unit) = new Action(title) {
def apply() { block }
}
We can create an action by writing:
Action("Run me") {
println("Someone executed this action.")
}
A component that can be associated with an action extends trait Action.Triger. The following
shows one way to associate a click button with an action.
val button = new Button {
action = Action("Click me") {
println("Someone executed clicked button " + this)
}
}
As it is very common for buttons to have actions, there is a convenience factory method in
companion object Button. The following is equivalent to the previous example.
A component with a NoAction uses it’s own properties instead of those of the action. In Java
Swing, the equivalent to NoAction is null.
In contrast to Java Swing, ListView and its constructor are polymorphic in the type of items:
class ListView[A] extends Component {
def this(items: Seq[A]) = ...
...
}
ListView defines a member selection that lets clients query information about the current
selection, for instance, the sequence of currently selected items. In order to see how we can
take advantage of class ListView being generic, we modify the above example slightly:
case class City(name: String, country: String, population: Int, capital: Boolean)
val items = List(City("Lausanne", "Switzerland", 129273, false),
City("Paris", "France", 2203817, true),
City("New York", "USA", 8363710 , false),
City("Berlin", "Germany", 3416300, true),
City("Tokio", "Japan", 12787981, true))
val view = new ListView(items)
We can now query the currently selected items and receive a sequence of City objects we
can immediately operate on:
In Java Swing, we would have to perform casts or go through the list of selected indices, both
leading to more error-prone and less concise code.
5.6.2 Renderers
If we actually take the list view code showing city objects from above, and put it inside a frame ,
we will get the result shown in Figure 5.5.
Figure 5.5: A ListView component showing a list of City objects
The reason is that by default, a list view calls toString on its items and displays each as a label
containing that string. Since for a case class such as City, method toString returns a string
composed of the class name and its constructor arguments.
Technically, class ListView uses an instance of class ListView.Renderer to render each item.
Companion object ListView contains a number of renderer related member that are useful to
clients. Object GenericRenderer is just a wrapper for the default renderer imposed by Java
Swing and calls toString for each item as mentioned above.
It returns a renderer for item types A that uses an implicitly supplied renderer for item types B.
The resulting renderer obtains items of type B by applying function f to each item. Using this
function and the implicit GenericRenderer object, we can create a better city view:
import ListView._
val view = new ListView(items) {
renderer = Renderer(_.name)
}
The last useful class from object ListView is class AbstractRenderer that can be used to create
more elaborate item views. Its constructor takes a component whose paintComponent method
used to draw an item:
Clients need to configure that component for each item by implementing the following method:
Method configure for a view showing items of class Item could be implemented as follows:
def configure(list: ListView[_], isSelected: Boolean, focused: Boolean,icon: Icon, index: Int)
{
component.icon = icon
component.xAlignment = Alignment.Center
if(isSelected) {
component.border = Swing.LineBorder(list.selectionBackground, 3)
} else {
component.border = Swing.EmptyBorder(3)
}
}
A similar example can be found in the combo boxes demo from the sample package.
5.7 CustomPainting
Custom component painting in scala.swing is very similar to Java Swing. Most users that want
to perform custom painting inside a component will want to instantiate Component or one of
its subclasses and override method paintComponent. Here is how it is done in the LinePainting
demo from the scala.swing sample package:
new Component {
...
override def paintComponent(g: Graphics2D) = {
super.paintComponent(g)
g.setColor(new Color(100,100,100))
g.drawString("Press left mouse button and drag to paint.",10, size.height10)
g.setColor(Color.black)
g.draw(path)
}
}
This calls super.paintComponent() to make sure the component draws its background and
decorations properly. It then draws a custom string and a path that is maintained by the demo
while the user drags the mouse.
The available paint methods provided in class Component are
protected def paint(g: Graphics2D)
protected def paintBorder(g: Graphics2D)
protected def paintChildren(g: Graphics2D)
protected def paintComponent(g: Graphics2D)
Method paint is called by the Swing painting mechanism to draw a component and in its
standard implementation invokes the other three methods to paint the component specific
decorations, its border, and its children, respectively. Note that these methods take a
Graphics2D object in contrast to corresponding methods in javax.swing.Component which take
the Graphics object. Internally, Swing deals with Graphics2D objects in any event. The
scala.swing signatures eliminate the need to perform the common cast
g.asInstanceOf[Graphics2D] to access the newer interface of class Graphics2D.
5.8 Panels
The JPanel class provides general-purpose containers for lightweight components. By default,
panels do not add colors to anything except their own background; however, you can easily
add borders to them and otherwise customize their painting.
In many types of look and feel, panels are opaque by default. Opaque panels work well as
content panes and can help with painting efficiently. You can change a panel's transparency by
invoking the setOpaque method. A transparent panel draws no background, so that any
components underneath show through.
Like other containers, a panel uses a layout manager to position and size its components. By
default, a panel's layout manager is an instance of FlowLayout , which places the panel's contents
in a row. You can easily make a panel use any other layout manager by invoking the setLayout
method or by specifying a layout manager when creating the panel. The latter approach is
preferable for performance reasons, since it avoids the unnecessary creation of a FlowLayout
object.
Here is an example of how to set the layout manager when creating the panel.
package net.codejava.swing.jpanel;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JPasswordField;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
/**
* This program demonstrates how to use JPanel in Swing.
* @author www.codejava.net
*/
public class SwingJPanelDemo extends JFrame {
public SwingJPanelDemo() {
super("JPanel Demo Program");
constraints.gridx = 1;
newPanel.add(textUsername, constraints);
constraints.gridx = 0;
constraints.gridy = 1;
newPanel.add(labelPassword, constraints);
constraints.gridx = 1;
newPanel.add(fieldPassword, constraints);
constraints.gridx = 0;
constraints.gridy = 2;
constraints.gridwidth = 2;
constraints.anchor = GridBagConstraints.CENTER;
newPanel.add(buttonLogin, constraints);
pack();
setLocationRelativeTo(null);
}
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
new SwingJPanelDemo().setVisible(true);
}
});
}
}
Reference: https://www.codejava.net/java-se/swing/jpanel-basic-tutorial-and-examples
Initial Threads – Threads which execute initial application code. This is where the
application logic begins.
Event Dispatch Thread (EDT) – Threads where all event handling code is executed.
Event handling code is the major part of code in Swing programming that interacts with
Swing framework.
Worker Threads / Background Threads – Threads where time consuming
background tasks (long-running tasks) are executed.
We do not need to explicitly provide code for creating these threads; Swing framework or run
time provides them. We can just utilize these threads to create a responsive Swing program.
Example :
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
new MainFrame("SwingWorker Demo");
}
});
SwingUtilities.invokeAndWait(new Runnable(){
@Override
public void run(){
new MainFrame("InvokeAndWait Example");
}
});
Code running on the event, dispatches thread as a series of short tasks. Most tasks are
invocations of event-handling methods, such as ActionListener.actionPerformed. Other tasks
can be scheduled by application code, using invokeLater or invokeAndWait. Tasks on the event
dispatch thread must finish quickly; otherwise, unhandled events back up and the UI becomes
unresponsive.
Example:
}
}
statusLabel.setText("Completed.");
System.out.println("Invoke and Wait...");
}
};
worker.start();
}
Worker threads are also known as background threads. For executing long-running tasks,
Swing program uses worker threads.
Tasks that run on Worker threads are created using javax.swing.SwingWorker. Each task
running on a worker thread is represented by an instance of this class. Communication and
coordination between worker thread tasks and the tasks on other threads are featured by this
class.
SwingWorker is an abstract class; we must define a subclass in order to create a SwingWorker
object; anonymous inner classes are often useful for creating very simple SwingWorker objects.
Important point to note: For each new background task, a new instance of
javax.swing.SwingWorker is needed. The instances are not reusable.
SwingWorker is a generic class, with two type parameters. The first type parameter specifies a
return type for doInBackground, and also for the get method, which is invoked by other threads
to retrieve the object returned by doInBackground. SwingWorker's second type parameter
specifies a type for interim results returned while the background task is still active. Void can
be used as a placeholder, if our code doesn't return any interim results.
MODULE 6:
THE SOFTWARE DEVELOPMENT PROCESS
The document that results from requirements analysis is the input to the design phase. In this
phase, a modular decomposition of a program satisfying the specification is developed. In the
next phase, the individual modules are implemented and then tested to ensure that they perform
as intended. Unit test is used in which individual modules are tested in isolation, and integratio n
tests are those in which modules are tested in combination.
At best, integration testing shows that the modules together satisfy the implementor's
interpretation of the specification. The implementor may have misinterpreted the specifica tio n
or neglected to test some portion of the program's behavior, though, and the customer therefore
needs some other basis for deciding whether or not the program does what it is supposed to do.
This typically takes the form of acceptance tests. Acceptance tests provide an evaluation of the
program behavior that is independent of the design, and they are generally performed by an
organization other than the one that worked on the design and implementation. They should
include both trial runs under conditions approximating those the customer will actually
encounter and tests derived directly from the requirements specification.
When the program has passed the acceptance tests, it enters the production phase and becomes
a product that the customer can use. The useful life of the program occurs during this phase,
but the program is unlikely to remain unchanged even here. First, it almost certainly harbors
undetected errors that must be corrected during production. Correcting such errors is called
program maintenance. Second, the customer's requirements are likely to change. Responding
to such changes requires program modification.
Figure 6.1 illustrates the waterfall model, an idealized form of the software developme nt
process previously described, in which each phase is completed before work starts on the next
phase. The waterfall model is neither realistic nor practical: the software development process
is unlikely to proceed sequentially through the phases. There are two reasons for this. First,
some work can be done in parallel. The second reason that development isn’t entirely
sequential is errors, which may make it necessary to back to an earlier stage of the process.
Figure 6.2 illustrates the more realistic spiral model. In this model, phases can start before their
predecessor phase is complete, and the process includes many feedback loops.
A good way to get started on requirements analysis is to examine how the customer does things
at present. Almost always a product is intended to replace an existing system, possibly non-
computerized, or consisting of some combination of programs and external processes, or even
a program in which the performance was not satisfactory. The current system will contain
methods for normal processing and for coping with errors and a variety of contingencies. It can
be a source of ideas not only about how to do things, but about what needs to be done.
Additionally, the product must be compatible with other systems already in use. Studying the
environment in which the new product will be used can help ensure that it will fit in smoothly.
For example, studying the customer's organization will help insure that the program will fit
well into that organization and will be compatible with current practices.
Requirements analysis must consider both normal situations and errors. Studying normal case
behavior means defining the effect of all non-erroneous user interactions with a program under
the assumption that the program is in a normal state.
Cases with no errors represent only a small part of program behavior, however, and it is
essential to consider and describe how a program behaves in the presence of errors. This part
of the analysis should never be neglected or underemphasized. The analyst must try to uncover
all possible errors that might occur and develop the appropriate responses for each case. Errors
come from two sources: users interacting with the program, and hardware and software
malfunctions.
A good approach to studying both normal case behavior and behavior in the presence of user
errors is to work out scenarios. A scenario is step-by-step walk-through of an interaction with
the system, consisting of use/response pairs: the user or environment requests some action,
leading to a particular response by the system.
As far as software errors are concerned, the analyst must decide how much effort should be
expended to detect and cope with such errors. For example, if it is important to limit the scope
of software errors, the output of critical modules can be checked for reasonableness, and the
system shut down if the checks fail; the shutdown might simply terminate processing until the
problem is fixed, or it might be followed by a restart in a clean state, which is often suffic ie nt
for continued service. If this approach is taken, it is important to log information about failures
so that information about the errors that led to them is not lost.
The analyst must also decide what to do about hardware failures. It may be important for a
system to be highly available; that is, it should be highly likely that the system is up and running
all the time (or at certain times) Satisfying such a requirement may involve the use of redundant
hardware and software. A related requirement is that the system be highly reliable. The main
concern is to avoid loss of information because of failures. Determining how ambitious the
system must be in trying to recover from hardware and software malfunctions is an important
aspect of requirements analysis.
In addition to functional requirements, a program must satisfy performance requirements.
These requirements should be considered as the functional requirements are developed, and
also on their own, just to double-check that nothing has been forgotten. Time and space
efficiency should be considered together since it is often necessary to trade one off against the
other.
The customer's space and time requirements must be checked for compatibility with the
functional requirements, the hardware the customer intends to use, and the price the customer
is willing to pay for the system. For example, the customer may want some activity to satisfy
performance requirements that are either not possible given the hardware or can be satisfied
only with very sophisticated software. If an incompatibility is discovered, negotiations may
be necessary to produce new requirements.
These issues affect the content of the requirements specification directly. A number of other
issues should be considered during requirements analysis, not because they affect the
specification, but because they provide useful input for the designers. Two such issues are
modifiability and reusability. There will usually be areas with fixed requirements, and others
in which changes are likely. Information about likely changes is useful because a design can
be shaped in such a way as to make certain changes easy.
Pinning down constraints on the delivery schedule is another important part of requireme nts
analysis. Knowing that the customer is in a hurry, for example, may encourage the designer
of the software to trade noncritical features for simplicity.
The result of the requirements phase is a requirements document. This document contains the
requirements specification, which describes the program behavior, including its behavior in the
presence of errors. In addition, the document should explain the performance requirements, the
decisions made during analysis, and if it can be done with a reasonable amount of effort, the
alternatives that were rejected (and why they were rejected). The latter information is useful
when requirements must be rethought because of errors or changing customer needs.
The requirements document can be the input to two activities in addition to the design. It can
be used to produce acceptance tests and as a basis for a system user's manual. The user's manual
is something that must be produced anyway, but its production can provide an independent
check on the suitability of the specification. If the system is hard to use, this may be evident
when the manual is written. Also, by reading the manual, the customer may notice deficienc ies
in the specification that were overlooked earlier.
6.3 Requirement Specification
A program is a data object, and its specification will be similar to those for abstract types.
Specifications for abstract types rely on the overview section to define a model for the states
of their objects. In those specifications, we were able to make use of simple models that were
based on a small set of mathematical concepts. For requirements specifications, however, such
simple models aren't sufficient. Now we need to model the state of an entire program. This
state often has a complex structure, even when we limit our concerns to just that part of the
structure that is visible to users of the program. For example, users of a file system need to
understand about files and directories and how they are connected to one another.
Thus, we need a way to describe the program state. We can define the program state by means
of a data model. The model is then used in the requirements specification.
6.3.1 Data Models
A data model consists of a graph and a textual description. The graph defines the kinds of data
being manipulated and how they are related to one another. The graph and textual description
together define constraints on what the program does. Defining these constraints forces us to
pay attention to details that might otherwise be overlooked during requirements analysis.
The graph contains nodes and edges. The nodes represent the kinds of data being manipula ted
by a program. Each node is a named set of items. For example, a data model for a file system
would contain a File node, representing files, and a Dir node, representing directories. Each set
contains all items that exist at a particular moment (e.g., all files in existence at this time).
The items in the sets are structureless; no detail is given for them, except by means of
relationships to other sets. The edges represent these relationships. The purpose of the graph is
to provide a convenient pictorial mechanism for showing the relationships. The notation also
allows the relationships to be constrained in ways that will be explained later. Thus, a graph
expresses certain kinds of invariants.
The model describes the state of the system. This state may change over time (e.g., as a result
of the program responding to a user request). The model expresses the mutability in two ways.
First, the sets themselves can change: as the program state changes, items may be added to or
removed from sets. Second, the relationship between the sets can change.
6.3.1.1 Subsets
Some sets represented by nodes in the graph are subsets of other sets. We will call sets that
have no supersets domains. Each domain is disjoint from all other domains.
Subset edges are used to indicate that one set is a subset of another. We represent this
information with an arrow with a closed head. The arrow goes from the subset to the superset.
The arrowhead indicates whether the subset contains all elements of its superset (a filled
arrowhead) or just some elements of the superset (an unfilled arrowhead).
Subsets can share an arrow; in this case, they are mutually disjoint, and the arrowhead indicates
whether or not their union exhausts the superset. Subsets that don't share an arrow are not
necessarily disjoint.
Three constraints are useful to define for subsets. First, subsets can sometimes be fixed. This
means that the subset’s membership is fixed for all time; the subset never gains or loses
elements. A fixed subset is indicated by double lines on both sides of its node.
6.3.1.2 Relations
6.3.2 Requirements Specification
6.4 Designs