Java Programming
Java Programming
LEARNING OUTCOMES: By the end of this course students should be able to:
COURSE CONTENT
UNIT 1: APPLYING THE ADVANCED CONCEPTS OF JAVA COLLECTIONS
A lot has changed since the inception of the Java programming language, but
the Java Collections Framework (JCF) has been a consistent backbone of Java
since its early days. Although many improvements have been made to the JCF,
not the least of which has been the addition of generics and concurrency, the JCF
has remained largely the same. Throughout this time, collections have become an
indispensable part of nearly every Java application and learning to use these
interfaces and classes has become a requirement for most aspiring Java
developers.
The primary collection data structures: Lists, sets, and queues. The fundamentals
coverage of the primary methods of each interface and class, the intention
During the early days of Java, no common collection framework existed; the best
that was available were loosely collected data structures in the form of
time, Java did not combine these disparate types into a framework, with common
Template Library (STL) in C++ in 1994, Java lagged behind in creating a common
Up to the release of Java Development Kit (JDK) 2 in late 1998, the most popular,
Library (JGL) by Object Space and Collections Package by Doug Lea. Using the
JGL as a basis, Joshua Bloch (author of the Effective Java series) designed much
of the JCF that we know today. As a matter of fact, the author tags for many of
the collection classes still have his name to this day. With the advent of JDK 2, a
framework got a major upgrade in Fall of 2004 with the release of JDK 5. This
Java release introduced type-erased generics, which transformed the JCF from a
Since that time, various upgrades have been made to the JCF, including the
Application Programming Interface (API), but the JCF has largely remained the
same. Being that it is one of the most widely used of the Java frameworks,
updates and improvements to the JCF have been one of the foremost concerns
iteration order). Additionally, collections must provide queries for its current state,
such as the current number of elements it contains, if the collection is empty, and
if an arbitrary element is within the collection; they must also provide conversion
the addition of the Streams (API) in JDK 8, collections must also be convertible to
responsibilities:
contains
The contains(Object o) method checks if the supplied object is contained in the collection
according to the following rule:
It is important to note that this does not mean that the equals method of a will actually be
invoked. It is left to the implementation to decide how to test for equality. Optimizations
may be made, depending on the nature of the collection. For
example, the hashCode() method specification states that two objects with
different hashCode() values are by definition not equal, and therefore, an implementation
may favor this means of testing equality, rather than explicitly executing a.equals(b).
iterator
The Collection interface is actually not the top of the collections hierarchy: It extends
the Iterable interface, which defines a type that can be iterated over. The JDK 9
definition for the Iterable interface is as follows:
The first method, iterator(), simply returns an Iterator object (which we address
shortly). The forEach method is a straightforward default implementation that allows a
non-null action to be performed on each of the elements in a collection. This method
utilizes the for-each loop (formally called the enhanced for loop), which is a syntactic
optimization that allows a for loop to be condensed for any iterable object:
class FooIterable implements Iterable<Foo> { /* ... */ }
FooIterable fooIterable;
for (Foo foo: fooIterable) {
// Do something with "foo"
}
There are some limitations to this style, such as removing an element during iteration,
as we will see shortly. The last method creates a Spliterator, which can partition a
collection and facilitates iteration over these partitions. Spliterators are a complex topic
and are closely related with parallelism in the collections framework, and are therefore
not covered in this article. The curious reader should consult
the Spliteratordocumentation and Java 8: A Quick Introduction to Parallelism and the
Spliterator for more information.
The main purpose of the Iterable interface is to create an Iterator object. An Iterator is
the primary interface in the Iterator pattern and allows for iteration over a collection.
The Iterator interface is defined as follows for JDK 9:
Following the Iterator pattern, the primary methods in this interface are
the hasNext() method, which returns true if there are more elements to iterate, and
the next() method, which returns the next element to be iterated and advances the
current element in the iterator (i.e. ensure that the next call to next() will produce the
next element of the collection, not the same element ad infinitum). Beyond these
fundamental methods, the Iterator interface also includes two important
methods: remove() and forEachRemaining(Consumer<? super E> action).
The remove()) method is essential to removing elements from a collection during
iteration. In general, it is not permissible to iterate over a collection using an enhanced
for loop and remove an element from the collection in the body of the for loop. This
results in a ConcurrentModificationException being thrown. For example, the following
results in a ConcurrentModificationException:
Note that next() must be called before calling remove(), as the next() method advances the
current element in the iterator. While the combination of the next(), hasNext(),
and remove() method cover a vast majority of the functionality that a developer will
commonly use when dealing with iterators, there are countless great resources that go
much deeper on this important topic. For more information, consult the Iterator
documentation and the Oracle Collection Interface documentation.
toArray
Due to the limitation of Java generics, conversions of collections to arrays is quirky and
two methods are provided. The first is the simple toArray() method that returns an array
of Objects that holds the elements of the collection ordered by the same rules that
govern the order of the elements obtained through an iterator associated with the
collection (i.e. if there are ordering rules established for an iterator returned
by iterator(), these rules also govern the order of the elements in this array). This
method is a non-generic method and the type of the elements in the array do not reflect
the type of the elements, as specified by the formal generic parameter for the collection.
The second method is the toArray(T[] a) method, which returns an array of the elements
in the collection, but retains the type of the elements. Thus, the array returned by this
method is an array of objects with the same type as the formal generic parameter of the
collection. Due type erasure of generics in Java, the type of the formal generic parameter
is inaccessible at run-time, and therefore, creating an array at run-time with the same
type as the elements is not feasible. Thus, the caller is responsible for providing the type
of array at run-time (in the form of an array of the element type). If the provided array
has a size equal to greater than the size of the collection, the supplied array is filled with
the elements of the collection and the element directly following the last element of the
collection is set to null (if the length of the array minus the size of the collection is
greater than or equal to 1). If the length of the supplied array is less than the size of the
collection, a new array is returned with a length that matches the size of the collection
and the type of the supplied array.
For example, a call to this parameterized method would resemble the following:
It can be tempting to optimize this call by pre-allocating an array of the same length as
the size of the collection. For example:
As noted by Joshua Bloch in Effective Java, 3rd Edition (pg. 248), Item 55, this pre-
allocation optimization should be avoided. For a quantitative analysis of the
performance differences between these two array pre-allocation techniques, see Arrays
of Wisdom of the Ancients.
add
The add(E e) method adds an element to a collection and returns true if the collection
was changed. Implementations are left to decide if e is acceptable. For example, some
implementations may not accept duplicate values (i.e. if contains(e) is true) while others
may not accept null values.
remove
The remove(Object o) method removes one element from a collection if at least one
element equal to the supplied object is contained in the collection. If an element is
removed, this method returns true. The equality rules for removal are identical to those
of the contains(Object o) method.
The containsAll(Collection<?> c) method returns true if and only if all of the elements
of c are contained in the collection. Likewise, the addAll(Collection<?> c) method adds all
of the elements in c to the collection, returning true if the collection is altered (i.e. at
least one addition was performed). Note that the behavior of the addAll method is
undefined if the collection is modified between the initiation of the addAll method and
its completion. Lastly, removeAll(Collection<?> c) removes all elements in common with c,
returning true if the collection was modified (i.e. if at least one element was removed).
Upon completion, the collection is guaranteed to contain common elements with c.
removeIf
The removeIf(Predicate<? super E> filter) default implementation removes all elements
that satisfy the supplied predicate. In practice, this method filters out any element that
satisfies the supplied predicate and returns true if the collection was modified (i.e. at
least one element was removed). The implementation of this method is as follows:
retainAll
4, 6] is supplied to the retainAll method, the original collection will be reduced to [1, 2,
2, 4]. A value of true is returned if this method modifies the collection (i.e. if at least one
element is removed).
clear
Removes all elements from the collection. Upon completion of this method, the
collection is considered empty.
spliterator
One of the major additions to JDK 8 was the inclusion of the Streams API. This API
introduces functional programming semantics to Java collections, treating collections as
a stream of data that can be mapped, filtered, reduced, etc. The default implementations
of the stream-based methods for the Collection interface are as follows:
As with Spliterators, streams are a very involved topic and beyond the scope of this
series. The curious reader can find more information at the Stream class
documentation.
Implicit Rules
While most of the rules regarding collection implementations are checked at compile-
time with language constructs, some rules are extra-linguistic. Although not checked at
compile-time, all implementations of collections should abide by the following rules:
Concrete Collection
With a solid understanding of the general characteristics of a list and details of each method
of the List interface, we can examine the concrete implementations of the List interface.
The JDK 9 List hierarchy (disregarding concurrent List implementations for brevity) is
illustrated in the following figure, where green boxes represent interfaces, blue boxes
represent abstract classes, and purple boxes represent concrete implementations.
ArrayList
An ArrayList is a general-purpose List implementation that is backed by an internal array
that expands to store the elements of the list. At any given time, the length of the internal
array is called its capacity and may be greater than or equal to the size of the list (the number
of elements in the list, not the size of the array used to store the elements). For example, it is
possible that an ArrayList contains 10 elements, but its capacity is 12 (enough to store the
10 elements).
Additionally, the ArrayList class implements all optional operations in the List interface
and allows for any element, including duplicates and null elements, to be added.
An ArrayList is essentially a Vector without the overhead of synchronization. Unless there
is a specific need to use a Vector, an ArrayList should be used in place of a Vector. For
more information, see the official ArrayList documentation.
LinkedList
A ListedList is a doubly linked list that allows for bidirectional traversal of the elements in
the list. This internal doubly link representation is based on the private static Node class,
which includes a data field (called an item) of the type of the elements in the LinkedList
(matching the type of the formal generic parameter of the LinkedList), a previous link, and
a next link, as illustrated in the listing below.
E item;
Node<E> next;
Node<E> prev;
this.item = element;
this.next = next;
this.prev = prev;
}
Using this Node class, the LinkedList implementation stores a reference to the head and tail
of the list, allowing for forward traversal from the head of the list or backward traversal from
the tail of the list (depending on which is closer to the needed index). This implementation
should be used if elements are frequently added to the head of the list or if an iterator is used
to delete elements far from the head or the tail of the list, as these operations are performed in
constant time as compared to the linear time of an ArrayList. Note, though, that positional
access requires linear time in a LinkedList but is a constant time operation for an
ArrayList. For more information on the performance trade-offs
between LinkedList and ArrayList, see the official List Implementation tutorial.
The LinkedList class also implements the Queue interface (which will be discussed in a later
article in this series) and includes the following methods not included in List interface:
• addFirst
• getFirst
• removeFirst
• addLast
• getLast
• removeLast
For more information on the LinkedList implementation, see the official LinkedList
documentation.
Vector
A legacy implementation predates JDK 2. This implementation is essentially an ArrayList
with a simplistic internal synchronization mechanism (each mutation method is marked
as synchronized). Due to the unneeded overhead of synchronization, an ArrayList should
generally be used instead of a Vector. For more information, see the official Vector
documentation.
Stack
A legacy Last-in-First-out (LIFO) stack implementation. This implementation includes stack-
based methods, such as push, pop, and peek to add an element to the stack, retrieve and
remove the top element from the stack, and to retrieve the top element without removing it,
respectively. This implementation, like Vector, uses internal synchronization and a Deque
implementation should generally be used instead. For more information, see the official
Stack documentation.
Comparison
The following table captures some of the trade-offs and benefits of each of the
standard List implementations covered in this section:
LIST
DESCRIPTION
TYPE
A good, general purpose list implementation that is efficient in most cases. This implementation
uses an internal array to store the elements and must expand when the number of elements
exceeds the capacity of the array.If a list frequently accesses its elements by index, this
ArrayLi
implementation provides constant time access. If instead, elements are frequently added to the
st
head of the list or are removed from the center of the list (generally halfway between the head
and tail of the list), these operations are linear and a LinkedList should be used instead.
When in doubt, use an ArrayList by default.
An efficient implementation for adding elements to the head of the list or sequentially removing
elements (or traversing the list and removing selective elements along the way). This list is
LinkedL
internally implemented as a doubly linked list with references to both the head and tail of the
ist
list, allowing for efficient forward traversal from the head of the list or backward from the tail of
the list.
Uses internal synchronization. In most cases, ArrayList should be used in place of
a Vector since the overhead of the internal synchronization of the Vector class is inefficient.
The synchronization performed by the Vector class is a method-by-method synchronization,
Vector
where each method that modifies the list is marked as synchronized. Synchronization
mechanics for a list can be more efficiency introduced by using a concurrent list implementation,
such as the CopyOnWriteArrayList implementation.
Uses internal synchronization. A legacy implementation that supports stack-based
Stack operations (such as push and pop). The Deque list type should be used instead of this
implementation.
Conclusion
Lists are one of the most frequently used collection types, and for good reason. By definition,
lists are ordered and its elements can be randomly accessed by index, as well as traversed
through a special-purpose ListIterator. In addition, due to the ordered nature of a list, the
elements in a List are sortable by either an explicit Comparator or through natural ordering.
While some of the mutation methods (such as add) are optional operations, the two most
common List implementations, ArrayList and LinkedList, provide implementations for
all of these optional operations. Although we have not covered every aspect of
the List interface and its various implementations, we have covered a wide breadth of
knowledge that will allow us to start in the correct direction, digging into the official
documentation where needed. In the next entry in this series, we will cover one of the more
mathematical collection representations: The set.
Although we can use an array to store a group of elements of the same type
(either primitives or objects). The array, however, does not support so-
called dynamic allocation - it has a fixed length which cannot be changed once
require more complex data structure such as linked list, stack, hash table, sets, or
trees.
A collection, as its name implied, is simply an object that holds a collection (or a
1. A set of interfaces,
Prior to JDK 1.2, Java's data structures consist of array, Vector, and Hashtable that
were designed in a non-unified way with inconsistent public interfaces. JDK 1.2
introduced the unified collection framework, and retrofits the legacy classes
JDK 1.5 introduced Generics (which supports passing of types), and many related
new features.
1.5).
Multithreading as a widespread programming and execution model allows multiple threads to exist
within the context of a single process. These threads share the process' resources but are able to
execute independently. The threaded programming model provides developers with a useful
abstraction of concurrent execution. However, perhaps the most interesting application of the
technology is when it is applied to a single process to enable parallel execution on a multiprocessor
system.
That means that a single process can have many different "functions" executing concurrently,
allowing the application to better use the available hardware (multiple cores/processors). Threads
can communicate between them (they have shared memory), but its a hard problem to have every
thread behave well with others when accesing shared objects/memory.
Threading allows an application to remain responsive, without the use of a catch all application
loop, when doing lengthy operations.
Java is a multi-threaded programming language which means we can develop multi-threaded program using
Java. A multi-threaded program contains two or more parts that can run concurrently and each part can
handle a different task at the same time making optimal use of the available resources specially when your
computer has multiple CPUs.
By definition, multitasking is when multiple processes share common processing resources such as a CPU.
Multi-threading extends the idea of multitasking into applications where you can subdivide specific
operations within a single application into individual threads. Each of the threads can run in parallel. The OS
divides processing time not only among different applications, but also among each thread within an
application.
Multi-threading enables you to write in a way where multiple activities can proceed concurrently in the same
program.
Live Demo
class RunnableDemo implements Runnable {
private Thread t;
private String threadName;
Live Demo
class ThreadDemo extends Thread {
private Thread t;
private String threadName;
Thread Methods
Following is the list of important methods available in the Thread class.
Sr.No. Method & Description
public void start()
1 Starts the thread in a separate path of execution, then invokes the run() method
on this Thread object.
public void run()
2 If this Thread object was instantiated using a separate Runnable target, the run()
method is invoked on that Runnable object.
public final void setName(String name)
3 Changes the name of the Thread object. There is also a getName() method for
retrieving the name.
public final void setPriority(int priority)
4
Sets the priority of this Thread object. The possible values are between 1 and 10.
public final void setDaemon(boolean on)
5
A parameter of true denotes this Thread as a daemon thread.
public final void join(long millisec)
6
The current thread invokes this method on a second thread, causing the current
thread to block until the second thread terminates or the specified number of
milliseconds passes.
public void interrupt()
7 Interrupts this thread, causing it to continue execution if it was blocked for any
reason.
public final boolean isAlive()
8 Returns true if the thread is alive, which is any time after the thread has been
started but before it runs to completion.
The previous methods are invoked on a particular Thread object. The following methods in the Thread class
are static. Invoking one of the static methods performs the operation on the currently running thread.
Sr.No. Method & Description
public static void yield()
1 Causes the currently running thread to yield to any other threads of the same
priority that are waiting to be scheduled.
public static void sleep(long millisec)
2 Causes the currently running thread to block for at least the specified number of
milliseconds.
public static boolean holdsLock(Object x)
3
Returns true if the current thread holds the lock on the given Object.
public static Thread currentThread()
4 Returns a reference to the currently running thread, which is the thread that
invokes this method.
public static void dumpStack()
5 Prints the stack trace for the currently running thread, which is useful when
debugging a multithreaded application.
Example
The following ThreadClassDemo program demonstrates some of these methods of the Thread class. Consider
a class DisplayMessage which implements Runnable −
// File Name : DisplayMessage.java
// Create a thread to implement Runnable
System.out.println("Starting thread3...");
Thread thread3 = new GuessANumber(27);
thread3.start();
try {
thread3.join();
} catch (InterruptedException e) {
System.out.println("Thread interrupted.");
}
System.out.println("Starting thread4...");
Thread thread4 = new GuessANumber(75);
thread4.start();
System.out.println("main() is ending...");
}
}
This will produce the following result. You can try this example again and again and you will get a different
result every time.
Output
Starting hello thread...
Starting goodbye thread...
Hello
Hello
Hello
Hello
Hello
Hello
Goodbye
Goodbye
Goodbye
Goodbye
Goodbye
Syntax
synchronized(objectidentifier) {
// Access shared variables and other shared resources
}
Example
class PrintDemo {
try {
} catch (Exception e) {
System.out.println("Thread interrupted.");
private Thread t;
threadName = name;
PD = pd;
PD.printCount();
if (t == null) {
t.start ();
T1.start();
T2.start();
try {
T1.join();
T2.join();
} catch ( Exception e) {
System.out.println("Interrupted");
This produces a different result every time you run this program −
Output
Starting Thread - 1
Starting Thread - 2
Counter --- 5
Counter --- 4
Counter --- 3
Counter --- 5
Counter --- 2
Counter --- 1
Counter --- 4
Thread Thread - 1 exiting.
Counter --- 3
Counter --- 2
Counter --- 1
Thread Thread - 2 exiting.
Example
class PrintDemo {
try {
} catch (Exception e) {
System.out.println("Thread interrupted.");
private Thread t;
threadName = name;
PD = pd;
synchronized(PD) {
PD.printCount();
if (t == null) {
t.start ();
T1.start();
T2.start();
try {
T1.join();
T2.join();
} catch ( Exception e) {
System.out.println("Interrupted");
This produces the same result every time you run this program −
Output
Starting Thread - 1
Starting Thread - 2
Counter --- 5
Counter --- 4
Counter --- 3
Counter --- 2
Counter --- 1
Thread Thread - 1 exiting.
Counter --- 5
Counter --- 4
Counter --- 3
Counter --- 2
Counter --- 1
Thread Thread - 2 exiting.
There are three simple methods and a little trick which makes thread
communication possible. All the three methods are listed below −
Causes the current thread to wait until another thread invokes the
notify().
Wakes up all the threads that called wait( ) on the same object.
These methods have been implemented as final methods in Object, so
they are available in all the classes. All three methods can be called only
from within a synchronized context.
Example
This examples shows how two threads can communicate
using wait() and notify() method. You can create a complex system
using the same concept.
class Chat {
if (flag) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println(msg);
flag = true;
notify();
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println(msg);
flag = false;
notify();
Chat m;
String[] s1 = { "Hi", "How are you ?", "I am also doing fine!" };
this.m = m1;
m.Question(s1[i]);
Chat m;
this.m = m2;
m.Answer(s2[i]);
new T2(m);
Hi
Hi
How are you ?
I am good, what about you?
I am also doing fine!
Great!
Thread Priorities
Every Java thread has a priority that helps the operating system determine the order in which threads are
scheduled.
Java thread priorities are in the range between MIN_PRIORITY (a constant of 1) and MAX_PRIORITY (a
constant of 10). By default, every thread is given priority NORM_PRIORITY (a constant of 5).
Threads with higher priority are more important to a program and should be allocated processor time before
lower-priority threads. However, thread priorities cannot guarantee the order in which threads execute and
are very much platform dependent.
t1.setPriority(2);
t2.setPriority(5);
t3.setPriority(8);
// Main thread
System.out.print(Thread.currentThread().getName());
System.out.println("Main thread priority : "
+ Thread.currentThread().getPriority());
Output:
t1 thread priority : 5
t2 thread priority : 5
t3 thread priority : 5
t1 thread priority : 2
t2 thread priority : 5
t3 thread priority : 8
Note:
Thread with highest priority will get execution chance prior to other threads. Suppose there are
3 threads t1, t2 and t3 with priorities 4, 6 and 1. So, thread t2 will execute first based on
maximum priority 6 after that t1 will execute and then t3.
Default priority for main thread is always 5, it can be changed later. Default priority for all other
threads depends on the priority of parent thread.
Example:
// Java program to demonstrat ethat a child thread
// gets same priority as parent
import java.lang.*;
Output:
t1 thread priority : 6
If two threads have same priority then we can’t expect which thread will execute first. It depends
on thread scheduler’s algorithm(Round-Robin, First Come First Serve, etc)
If we are using thread priority for thread scheduling then we should always keep in mind that
underlying platform should provide support for scheduling based on thread priority.
• Runnable − After a newly born thread is started, the thread becomes runnable. A thread in this state is
considered to be executing its task.
• Waiting − Sometimes, a thread transitions to the waiting state while the thread waits for another thread
to perform a task. A thread transitions back to the runnable state only when another thread signals
the waiting thread to continue executing.
• Timed Waiting − A runnable thread can enter the timed waiting state for a specified interval of time. A
thread in this state transitions back to the runnable state when that time interval expires or when
the event it is waiting for occurs.
Terminated (Dead) − A runnable thread enters the terminated state when it completes its task or
otherwise terminates.
Address Types
Some IP addresses and some patterns of addresses have special meanings. For
instance, I’ve already mentioned that 127.0.0.1 is the local loopback address. IPv4
addresses in the range 224.0.0.0 to 239.255.255.255 are multicast addresses that
send to several subscribed hosts at once. Java includes 10 methods for testing
whether an InetAddress object meets any of these criteria:
The isMCGlobal() method returns true if the address is a global multicast address,
false otherwise. A global multicast address may have subscribers around the world.
All multicast addresses begin with FF. In IPv6, global multicast addresses begin with
FF0E or FF1E depending on whether the multicast address is a well known
permanently assigned address or a transient address. In IPv4, all multicast addresses
have global scope, at least as far as this method is concerned. As you’ll see
in Chapter 13, IPv4 uses time-to-live (TTL) values to control scope rather than
addressing.
METHODS OF TEACHING
Lectures
Group discussions
Tutorials
Exercises
ASSESSMENT METHODS
Test 20%
Practical Communication Assignment 20%
PRESCRIBED READINGS
Deitel P (2007) “ Advanced Java 2 Platform-HOW TO PROGRAM, -
Prentice Hall
RECOMMENDED READINGS
Goncalves A (2007) “ Beginning Java EE Platform with GlassFish 3
From Novice to professional, - Prentice Hall