Category : Java | Sub Category : Java Interview questions | By Prasad Bonam Last updated: 2020-10-20 11:14:53 Viewed : 924
Variable Arguments
allow calling a method with different number of parameters. Consider the
example method sum below. This sum method can be called with 1 int parameter or
2 int parameters or more int parameters.
//int(type)
followed ... (three dot`s) is syntax of a variable argument.
public int sum(int... numbers) {
//inside the method a variable argument is
similar to an array.
//number can be treated as if it is declared
as int[] numbers;
int sum = 0;
for (int number: numbers) { sum += number;
}
return sum;
}
public static void main(String[] args) {
VariableArgumentExamples example = new VariableArgumentExamples();
//3
Arguments System.out.println(example.sum(1,
4, 5));//10
//4 Arguments
System.out.println(example.sum(1, 4, 5, 20));//30
//0
Arguments System.out.println(example.sum());//0
}
Assertions are
introduced in Java 1.4. They enable you to validate assumptions. If an assert
fails (i.e. returns false), AssertionError is thrown (if assertions are
enabled). Basic assert is shown in the example below
private int computerSimpleInterest(int principal,float interest,int years){
assert(principal>0); return 100;
}
Assertions should
not be used to validate input data to a public method or command line argument.
IllegalArgumentException would be a better option. In public method, only use
assertions to check for cases which are never supposed to happen.
Garbage Collection
is a name given to automatic memory management in Java. Aim of Garbage
Collection is to Keep as much of heap available (free) for the program as
possible. JVM removes objects on the heap which no longer have references from
the heap.
Let`s say the below method is called
from a function.
void method(){
Calendar calendar =
new GregorianCalendar(2000,10,30);
System.out.println(calendar);
}
An object of the
class GregorianCalendar is created on the heap by the first line of the
function with one reference variable calendar.
After the function
ends execution, the reference variable calendar is no longer valid. Hence,
there are no references to the object created in the method.
JVM recognizes this and removes the object from the heap.
This is called Garbage Collection.
Garbage Collection
runs at the whims and fancies of the JVM (it isn`t as bad as that). Possible
situations when Garbage Collection might run are
·
when available
memory on the heap is low
·
when cpu is free
Programmatically,
we can request (remember it`s just a request - Not an order) JVM to run Garbage
Collection by calling System.gc() method.
JVM might throw an
OutOfMemoryException when memory is full and no objects on the heap are
eligible for garbage collection.
finalize() method
on the objected is run before the object is removed from the heap from the
garbage collector. We recommend not to write any code in finalize();
Initialization Blocks - Code which runs when an object is
created or a class is loaded
There are two types of Initialization Blocks
Static Initializer: Code that runs when a class is loaded.
Instance Initializer: Code that runs when a new object is created.
Look at the example below:
public class InitializerExamples {
static int count;
int i;
//This is a static initializers. Run only
when Class is first loaded.
//Only
static variables can be accessed System.out.println("Static
Initializer");
//i = 6;//COMPILER ERROR
System.out.println("Count
when Static Initializer is run is " + count);
}
public static void main(String[] args) {
InitializerExamples
example = new InitializerExamples(); InitializerExamples example2
= new InitializerExamples();
InitializerExamples example3 = new InitializerExamples();
}
}
Code within static{ and } is called a static initializer.
This is run only when class is first loaded. Only static variables can be
accessed in a static initializer.
Example Output
Static Initializer
Count when Static Initializer is run is 0
Even though three instances are created static initializer
is run only once.
Let`s look at an example
public class InitializerExamples {
static int count;
int i;
{
//This is an instance initializers. Run
every time an object is created.
//static
and instance variables can be accessed System.out.println("Instance
Initializer");
i = 6;
count = count
+ 1;
System.out.println("Count
when Instance Initializer is run is " + count);
}
public static void main(String[] args) {
InitializerExamples
example = new InitializerExamples(); InitializerExamples example1
= new InitializerExamples();
InitializerExamples example2 = new InitializerExamples();
}
}
Code within instance initializer is run every time an instance of the class is created.
Instance Initializer
Count when Instance
Initializer is run is 1 Instance Initializer
Count when Instance
Initializer is run is 2 Instance Initializer
Count when Instance
Initializer is run is 3
Regular
Expressions make parsing, scanning and splitting a string very easy. We will
first look at how you can evaluate a regular expressions in Java – using Patter,
Matcher and Scanner
classes. We will then look into how to write a regular expression.
Delimiter can in
itself be any of the regular expression(s) we looked at earlier. String.split(regex)
function takes regex as an argument.
private static void tokenize(String
string,String regex) { String[] tokens = string.split(regex); System.out.println(Arrays.toString(tokens));
}
tokenize("ac;bd;def;e",";");//[ac, bd,
def, e]
private static void tokenizeUsingScanner(String
string,String regex) { Scanner scanner = new Scanner(string);
scanner.useDelimiter(regex);
List<String> matches = new ArrayList<String>();
while(scanner.hasNext()){ matches.add(scanner.next());
}
System.out.println(matches);
}
tokenizeUsingScanner("ac;bd;def;e",";");//[ac, bd, def,
e]
Lets now look at
adding a few hours to a date object. All date manipulation to date needs to be
done by adding milliseconds to the date. For example, if we want to add 6 hour,
we convert 6 hours into millseconds. 6 hours = 6 * 60 * 60 * 1000 milliseconds.
Below examples shows specific code.
Date date = new Date();
//Increase time by 6 hrs date.setTime(date.getTime() + 6 * 60 * 60 * 1000); System.out.println(date);
//Decrease time by 6 hrs date = new Date();
date.setTime(date.getTime()
-‐ 6 *
60 * 60 * 1000); System.out.println(date);
//Formatting Dates System.out.println(DateFormat.getInstance().format(
date));//10/16/12 5:18 AM
System.out.println(DateFormat.getDateInstance( DateFormat.FULL,
new Locale("it",
"IT"))
.format(date));//marted“ 16 ottobre 2012
System.out.println(DateFormat.getDateInstance( DateFormat.FULL,
Locale.ITALIAN)
.format(date));//marted“ 16 ottobre
2012
//This
uses default locale US System.out.println(DateFormat.getDateInstance(
DateFormat.FULL).format(date));//Tuesday, October 16, 2012
System.out.println(DateFormat.getDateInstance()
.format(date));//Oct 16, 2012 System.out.println(DateFormat.getDateInstance(
DateFormat.SHORT).format(date));//10/16/12 System.out.println(DateFormat.getDateInstance(
DateFormat.MEDIUM).format(date));//Oct 16, 2012
System.out.println(DateFormat.getDateInstance( DateFormat.LONG).format(date));//October 16, 2012
from a
date. It also provide lot of details about a date (which day of the year? Which
week of the year? etc.)
//Calendar calendar = new Calendar();
//COMPILER ERROR Calendar calendar =
Calendar.getInstance();
calendar.set(Calendar.DATE,
24); calendar.set(Calendar.MONTH, 8);//8 -‐ September calendar.set(Calendar.YEAR, 2010);
System.out.println(calendar.get(Calendar.YEAR));//2010 System.out.println(calendar.get(Calendar.MONTH));//8 System.out.println(calendar.get(Calendar.DATE));//24 System.out.println(calendar.get(Calendar.WEEK_OF_MONTH));//4 System.out.println(calendar.get(Calendar.WEEK_OF_YEAR));//39 System.out.println(calendar.get(Calendar.DAY_OF_YEAR));//267 System.out.println(calendar.getFirstDayOfWeek());//1 -‐> Calendar.SUNDAY
Number format is used to
format a number to different locales and different formats.
System.out.println(NumberFormat.getInstance().format(321.24f));//321.24
Formatting a number using Netherlands locale System.out.println(NumberFormat.getInstance(new Locale("nl")).format(4032.3f));//4.032,3
Formatting a number using Germany locale System.out.println(NumberFormat.getInstance(Locale.GERMANY).format(4032.3f));//4.032, 3
System.out.println(NumberFormat.getCurrencyInstance().format(40324.31f));//$40,324.31
Formatting a Currency
using Netherlands locale
System.out.println(NumberFormat.getCurrencyInstance(new Locale("nl")).format(40324.31f));//? 40.324,31
Collections
are used in situations where data is dynamic. Collections allow adding an
element, deleting an element and host of other operations. There are a number
of Collections in Java allowing to choose the right Collection for the right
context.
Most important methods declared in the
collection interface are the methods to add and remove an element. add method
allows adding an element to a collection and delete method allows deleting
an element from a collection.
interface Collection<E> extends Iterable<E>
{
boolean
add(E
paramE);
boolean
remove(Object
paramObject);
int size(); boolean isEmpty(); void clear();
boolean contains(Object paramObject);
boolean containsAll(Collection<?>
paramCollection);
boolean addAll(Collection<? extends E>
paramCollection); boolean removeAll(Collection<?>
paramCollection); boolean retainAll(Collection<?> paramCollection);
Iterator<E>
iterator();
//A NUMBER OF OTHER
METHODS AS WELL..
}
Most
important thing to remember about a List interface -‐ any implementation of the List interface would maintain
the insertion order. When an element A is inserted into a List (without
specifying position) and then another element B is inserted, A is stored before
B in the List.
However,
We can also use the void add(int position, E paramE); method to insert an
element at a specific position.
Listed below are some of the important methods in the List
interface (other than those inherited from Collection interface):
interface List<E> extends Collection<E>
{
boolean
addAll(int paramInt,
Collection<? extends
E> paramCollection);
E get(int paramInt);
E set(int paramInt,
E paramE);
void add(int paramInt, E paramE); E
remove(int paramInt);
int indexOf(Object
paramObject);
int lastIndexOf(Object
paramObject);
ListIterator<E> listIterator();
ListIterator<E> listIterator(int paramInt); List<E> subList(int paramInt1,
int paramInt2);
}
A Map interface
supports Collections
that use a key value pair.
A key-‐value pair is a set of linked data items: a key, which is a unique
identifier for some item of data, and the value,
which is either
the data or a pointer to the data. Key-‐value pairs are used in lookup tables, hash tables and configuration files. A key value pair in a Map interface is
called an Entry.
V put(K paramK, V
paramV);
V get(Object
paramObject);
interface Map<K, V>
{
int size();
boolean
isEmpty();
boolean containsKey(Object
paramObject);
boolean containsValue(Object
paramObject);
V get(Object paramObject); V put(K paramK, V paramV);
V remove(Object paramObject);
void putAll(Map<? extends K, ? extends V> paramMap);
void
clear();
Set<K> keySet(); Collection<V> values();
Set<Entry<K, V>> entrySet();
boolean equals(Object paramObject);
int hashCode();
public
static abstract interface Entry<K, V>
{
K getKey();
V getValue();
V setValue(V paramV);
boolean
equals(Object
paramObject);
int hashCode();
}
}
Main difference
between Set and SortedSet is -‐ an implementation
of SortedSet interface maintains its elements in a sorted order. Set interface
does not guarantee any Order. For example, If elements 4,5,3 are inserted into
an implementation of Set interface, it might store the elements in any order. However,
if we use SortedSet, the elements are sorted. The SortedSet implementation
would give an output 3,4,5.
public interface SortedSet<E> extends Set<E>
{
SortedSet<E> subSet(E fromElement, E toElement);
SortedSet<E> headSet(E toElement);
SortedSet<E> tailSet(E fromElement);
E first();
E last();
Comparator<?
super E>
comparator();
}
Methods are available in the interface to get a ranges of
values based on their keys.
public interface SortedMap<K, V> extends Map<K,
V> { Comparator<? super K>
comparator();
SortedMap<K, V> subMap(K fromKey, K toKey);
SortedMap<K, V> headMap(K toKey); SortedMap<K, V> tailMap(K
fromKey);
K firstKey();
K lastKey();
}
Queue
interface offers methods peek() and poll() which get the element at head of the
queue. The difference is that poll() method removes the head from queue also.
peek() would keep head of the queue unchanged.
interface Queue<E> extends Collection<E>
{
boolean offer(E paramE); E remove();
E poll();
E element();
E peek();
}
hasNext() checks
if there is another element in the collection being iterated. next() gets the
next element.
public interface Iterator<E> {
boolean
hasNext();
E next();
}
Let`s look at how to instantiate an ArrayList of integers.
List<Integer>
integers = new
ArrayList<Integer>();
integers.add(5);//new Integer(5)
Add method (by default) adds the element at the end of the
list.
List<String>
arraylist = new
ArrayList<String>();
//adds at the end of list arraylist.add("Sachin");//[Sachin]
//adds at the end of list arraylist.add("Dravid");//[Sachin, Dravid]
//adds at
the index 0
arraylist.add(0, "Ganguly");//[Ganguly, Sachin,
Dravid]
//List allows duplicates -‐ Sachin is present in the list twice arraylist.add("Sachin");//[
Ganguly, Sachin,
Dravid,
Sachin]
System.out.println(arraylist.size());//4 System.out.println(arraylist.contains("Dravid"));//true
Iterator<String>
arraylistIterator = arraylist
.iterator();
while (arraylistIterator.hasNext())
{ String str = arraylistIterator.next();
System.out.println(str);//Prints
the 4 names in the list on separate lines.
}
List<String> numbers = new ArrayList<String>();
numbers.add("one");
numbers.add("two");
numbers.add("three"); numbers.add("four");
System.out.println(numbers);//[one,
two, three, four]
//Strings -‐ By Default -‐ are sorted alphabetically Collections.sort(numbers);
System.out.println(numbers);//[four, one, three, two]
class Cricketer implements Comparable<Cricketer> {
int runs; String
name;
public Cricketer(String name, int runs) {
super(); this.name
= name; this.runs = runs;
}
@Override
public
String
toString() {
return
name + " " + runs;
}
@Override
public
int compareTo(Cricketer that) {
if (this.runs > that.runs)
{
return
1;
}
if (this.runs < that.runs)
{
return -‐1;
}
return
0;
}
}
List<Cricketer> cricketers = new ArrayList<Cricketer>();
cricketers.add(new
Cricketer("Bradman",
9996));
cricketers.add(new Cricketer("Sachin", 14000));
cricketers.add(new Cricketer("Dravid", 12000));
cricketers.add(new Cricketer("Ponting",
11000)); System.out.println(cricketers);
//[Bradman 9996, Sachin
14000, Dravid 12000, Ponting 11000]
Collections.sort(cricketers);
System.out.println(cricketers);
//[Bradman
9996, Ponting 11000, Dravid 12000, Sachin 14000]
class DescendingSorter implements Comparator<Cricketer> {
//compareTo returns -‐1 if cricketer1 < cricketer2
// 1
if cricketer1 > cricketer2
// 0
if cricketer1 = cricketer2
//Since we
want to sort in descending order,
//we should return
-‐1 when runs are
more @Override
public int compare(Cricketer
cricketer1, Cricketer cricketer2) {
if (cricketer1.runs > cricketer2.runs)
{
return -‐1;
}
if (cricketer1.runs < cricketer2.runs)
{
return
1;
}
return
0;
}
}
Collections
.sort(cricketers, new DescendingSorter());
System.out.println(cricketers);
//[Sachin 14000, Dravid
12000, Ponting 11000, Bradman 9996]
List<String>
numbers1 = new
ArrayList<String>();
numbers1.add("one");
numbers1.add("two"); numbers1.add("three"); numbers1.add("four");
String[] numbers1Array = new String[numbers1.size()]; numbers1Array
= numbers1.toArray(numbers1Array); System.out.println(Arrays.toString(numbers1Array));
//prints [one, two, three, four]
Object[] numbers1ObjArray = numbers1.toArray(); System.out.println(Arrays
.toString(numbers1ObjArray));
//[one,
two, three, four]
String values[] = { "value1",
"value2", "value3"
}; List<String> valuesList = Arrays.asList(values); System.out.println(valuesList);//[value1, value2, value3]
Vector has the
same operations as an ArrayList. However, all methods in Vector are synchronized.
So, we can use Vector if we share a list between two threads and we would want
to them synchronized.
ArrayList
uses an Array kind of structure to store elements. So, inserting and deleting
from an ArrayList are expensive operations. However, search of an ArrayList is
faster than LinkedList.
LinkedList
uses a linked representation. Each object holds a link to the next element. Hence,
insertion and deletion are faster than ArrayList. But searching is slower.
HashSet, LinkedHashSet and
TreeSet implement the Set interface.
HashSet implements
set interface. So, HashSet does not allow duplicates. However, HashSet does not
support ordering. The order in which elements are inserted is not maintained.
Set<String> hashset
= new HashSet<String>();
hashset.add("Sachin");
System.out.println(hashset);//[Sachin]
hashset.add("Dravid");
System.out.println(hashset);//[Sachin, Dravid]
hashset.add("Sachin");//returns false since element is not added System.out.println(hashset);//[Sachin, Dravid]
LinkedHashSet
implements set interface and exposes similar operations to a HashSet.
Difference is that LinkedHashSet maintains insertion order. When we iterate a
LinkedHashSet, we would get the elements back in the order in which they were
inserted.
Set<String> treeSet
= new TreeSet<String>();
treeSet.add("Sachin");
System.out.println(treeSet);//[Sachin]
//Alphabetical order treeSet.add("Dravid");
System.out.println(treeSet);//[Dravid, Sachin]
treeSet.add("Ganguly");
System.out.println(treeSet);//[Dravid, Ganguly,
Sachin]
//Sachin
is Duplicate. So will not be added. returns false. treeSet.add("Sachin");//returns false since element is not added System.out.println(treeSet);//[Dravid, Ganguly, Sachin]
TreeSet<Integer> numbersTreeSet = new TreeSet<Integer>();
numbersTreeSet.add(55);
numbersTreeSet.add(25); numbersTreeSet.add(35);
numbersTreeSet.add(5); numbersTreeSet.add(45);
Lower
method finds the highest element lower than specified element. Floor method
finds the highest element lower than or equal to specified element.
Corresponding methods for finding lowest number
higher than specified element are higher and ceiling. A few examples
using the Set created earlier below.
//Find the highest number which is lower
than 25 System.out.println(numbersTreeSet.lower(25));//5
//Find the highest number which is lower
than or equal to 25 System.out.println(numbersTreeSet.floor(25));//25
//Find the lowest number higher than 25 System.out.println(numbersTreeSet.higher(25));//35
//Find the lowest number higher than or
equal to 25 System.out.println(numbersTreeSet.ceiling(25));//25
HashMap and TreeMap.
HashMap implements Map interface – there by supporting key
value pairs. Let`s look at an example.
HashMap Example
Map<String, Cricketer> hashmap = new HashMap<String,
Cricketer>(); hashmap.put("sachin",
new Cricketer("Sachin",
14000)); hashmap.put("dravid",
new Cricketer("Dravid",
12000)); hashmap.put("ponting", new Cricketer("Ponting",
11500));
hashmap.put("bradman",
new Cricketer("Bradman", 9996));
System.out.println(hashmap.get("ponting"));//Ponting 11500
//if key is not found, returns null. System.out.println(hashmap.get("lara"));//null
//In the
example below, an entry with key "ponting"
is already present.
//Runs are updated to 11800. hashmap.put("ponting",
new Cricketer("Ponting",
11800));
//gets the recently updated value System.out.println(hashmap.get("ponting"));//Ponting 11800
Map<String, Cricketer> treemap = new TreeMap<String,
Cricketer>(); treemap.put("sachin",
new Cricketer("Sachin",
14000)); System.out.println(treemap);
//{sachin=Sachin 14000}
treemap.put("dravid",
new Cricketer("Dravid",
12000)); System.out.println(treemap);
//{dravid=Dravid 12000, sachin=Sachin 14000}
treemap.put("ponting",
new Cricketer("Ponting", 11500));
System.out.println(treemap);
//{dravid=Dravid
12000, ponting=Ponting 11500, sachin=Sachin 14000}
treemap.put("bradman",
new Cricketer("Bradman", 9996));
System.out.println(treemap);
//{bradman=Bradman 9996, dravid=Dravid 12000,
ponting=Ponting 11500, sachin=Sachin
14000}
53.
Can you give an example of implementation of
NavigableMap Interface? TreeMap
is a good example of a NavigableMap interface implementation. Note that keys in
TreeMap are sorted.
TreeMap<Integer, Cricketer> numbersTreeMap = new TreeMap<Integer,
Cricketer>(); numbersTreeMap.put(55, new Cricketer("Sachin",
14000));
numbersTreeMap.put(25, new Cricketer("Dravid", 12000));
numbersTreeMap.put(35, new Cricketer("Ponting", 12000));
numbersTreeMap.put(5,
new Cricketer("Bradman",
9996)); numbersTreeMap
.put(45, new Cricketer("Lara",
10000));
//Find the highest key which is lower than
25 System.out.println(numbersTreeMap.lowerKey(25));//5
//Find the highest key which is lower than
or equal to 25 System.out.println(numbersTreeMap.floorKey(25));//25
//Find the lowest key higher than 25 System.out.println(numbersTreeMap.higherKey(25));//35
//Find the lowest key higher than or equal
to 25 System.out.println(numbersTreeMap.ceilingKey(25));//25
//Using default constructor -‐ uses
natural ordering of numbers
//Smaller
numbers have higher priority
PriorityQueue<Integer>
priorityQueue = new
PriorityQueue<Integer>();
Adding an element into priority queue -‐ offer method priorityQueue.offer(24); priorityQueue.offer(15);
priorityQueue.offer(9);
priorityQueue.offer(45); System.out.println(priorityQueue);//[9, 24, 15, 45]
//peek method get the element with highest
priority. System.out.println(priorityQueue.peek());//9
//peek method does not change the queue System.out.println(priorityQueue);//[9, 24, 15, 45]
//poll method gets the element with highest
priority. System.out.println(priorityQueue.poll());//9
//peek method removes the highest priority
element from the queue. System.out.println(priorityQueue);//[24, 15, 45]
//This comparator gives high priority to the
biggest number. Comparator reverseComparator = new Comparator<Integer>() {
public int compare(Integer
paramT1, Integer paramT2) {
return paramT2 -‐ paramT1;
}
};
o
Can be used only on sorted list
·
static void reverse(List)
·
static Comparator reverseOrder();
·
static void sort(List)
·
static void sort(List, Comparator)
Generics are used
to create Generic Classes and Generic methods which can work with different Types(Classes).
class MyList {
private
List<String>
values;
void add(String value) { values.add(value);
}
void remove(String value) { values.remove(value);
}
}
MyList myList = new MyList(); myList.add("Value 1");
myList.add("Value 2");
To store integers, we need to create a new class. This is problem that Generics solve. Instead of hard-‐ coding String class as the only type
the class can work with, we make the class type a parameter to the class.
class
MyListGeneric<T>
{
private
List<T>
values;
void add(T value) { values.add(value);
}
void remove(T value) { values.remove(value);
}
T get(int index) {
return
values.get(index);
}
}
MyListGeneric<String> myListString = new MyListGeneric<String>();
myListString.add("Value 1");
myListString.add("Value 2");
MyListGeneric<Integer> myListInteger = new MyListGeneric<Integer>();
myListInteger.add(1);
myListInteger.add(2);
class
MyListGeneric<T>
Instead of T, We can use any valid identifier
-‐ method (return type or argument), member variable etc. For
Example: See how T is used as a parameter and return type in the class
MyListGeneric.
class MyListRestricted<T extends Number> {
private List<T> values;
void add(T value) { values.add(value);
}
void remove(T value) { values.remove(value);
}
T get(int index) {
return
values.get(index);
}
}
MyListRestricted<Integer>
restrictedListInteger = new MyListRestricted<Integer>();
restrictedListInteger.add(1); restrictedListInteger.add(2);
//MyListRestricted<String>
restrictedStringList =
// new
MyListRestricted<String>();//COMPILER ERROR
In
MyListGeneric, Type T is defined as part of class declaration. Any Java Type
can be used a type for this class. If we would want to restrict the types
allowed for a Generic Type, we can use a Generic Restrictions. In declaration
of the class, we specified a constraint "T super Number". We can use
the class MyListRestricted with any class that is a super class of Number
class.
Consider the
method below:
static <X extends Number> X
doSomething(X number){ X result = number;
//do
something with result
return
result;
}
Integer i = 5;
Integer k = doSomething(i);
Exception Handling helps us to recover from an unexpected
situations – File not found or network connection is down. The important part
in exception handling is the try – catch block. Look at the example below.
public
static void main(String[] args) {
method1();
System.out.println("Line
after Exception -‐ Main");
}
private static void method1() {
method2();
System.out.println("Line
after Exception -‐ Method 1");
}
private static void method2() {
String str = null; str.toString();
System.out.println("Line after Exception -‐ Method 2");
} catch (Exception e) {
// NOT PRINTING EXCEPTION TRACE-‐ BAD PRACTICE
System.out.println("Exception
Handled -‐ Method 2");
}
}
Program
Output
Exception Handled - Method 2 Line after Exception - Method
1 Line after Exception - Main
When exception is handled in a method, the calling methods
will not need worry about that exception. Since Exception Handling is added in
the method method2, the exception did not propogate to method1
i.e. method1 does not
know about the exception in method2.
Few important things to
remember from this example.
·
If exception is handled, it does not propogate further.
·
In a try block, the lines after the line throwing the
exception are not executed.
When an exception happens, the code after the line
throwing exception is not executed. If code for things like closing a
connection is present in these lines of code, it is not executed. This leads to
connection and other resource leaks.
Code written in finally
block is executed even when there is an exception.
Consider the example below. This is code without a finally
block . We have Connection class with open and close methods. An exception
happens in the main method. The connection is not closed because there is no
finally block.
class Connection {
void
open()
{
System.out.println("Connection Opened");
}
void close() { System.out.println("Connection Closed");
}
}
public
class ExceptionHandlingExample1 {
public static void main(String[] args) {
Connection connection = new Connection();
connection.open();
// LOGIC
String str = null; str.toString();
connection.close();
} catch (Exception
e) {
// NOT PRINTING EXCEPTION TRACE-‐ BAD PRACTICE
System.out.println("Exception
Handled -‐ Main");
}
}
}
Connection Opened Exception Handled - Main
Connection
that is opened is not closed. This results in a dangling (un-closed)
connection.
Finally block is used when code needs to be executed
irrespective of whether an exception is thrown. Let us now move
connection.close(); into a finally block. Also connection declaration is moved
out of the try block to make it visible in the finally block.
public static void main(String[]
args) { Connection connection = new Connection(); connection.open();
// LOGIC
String str = null; str.toString();
} catch (Exception e) {
// NOT PRINTING EXCEPTION TRACE
-‐ BAD
PRACTICE
System.out.println("Exception
Handled -‐ Main");
} finally {
connection.close();
}
}
Connection Opened Exception Handled - Main Connection
Closed
Connection is closed even when exception is thrown. This
is because connection.close() is called in the finally block.
Finally block is always executed (even when an exception
is thrown). So, if we want some code to be always executed we can move it to
finally block.
Code in finally is NOT
executed only in two situations.
1.
If exception is thrown in
finally.
2.
If JVM Crashes in between (for example, System.exit()).
private static void
method2() {
Connection connection = new Connection();
connection.open();
// LOGIC
String str = null;
str.toString(); return;
} catch (Exception
e) {
// NOT PRINTING EXCEPTION TRACE
-‐ BAD
PRACTICE
System.out.println("Exception
Handled -‐ Method 2");
} finally {
connection.close();
}
}
Connection connection = new Connection();
connection.open();
// LOGIC
String str = null; str.toString();
} finally {
connection.close();
}
}
Below method would give a
Compilation Error!! (End of try block)
private
static void method2() {
Connection connection = new Connection();
connection.open();
// LOGIC
String str = null; str.toString();
}//COMPILER ERROR!!
}
Throwable is the highest
level of Error Handling classes.
Below class definitions
show the pre-defined exception hierarchy in Java.
//Pre-‐defined Java Classes
class
Error extends
Throwable{}
class
Exception extends Throwable{}
class
RuntimeException extends Exception{}
Below class definitions
show creation of a programmer defined exception in Java.
//Programmer
defined classes
class
CheckedException1 extends Exception{}
class
CheckedException2 extends CheckedException1{}
class UnCheckedException extends RuntimeException{}
class
UnCheckedException2 extends UnCheckedException{}
Error is used in situations when there is nothing a
programmer can do about an error. Ex: StackOverflowError, OutOfMemoryError.
Exception is used when a programmer can handle the exception.
RuntimeException and classes that extend RuntimeException
are called unchecked exceptions. For Example:
RuntimeException,UnCheckedException,UnCheckedException2 are unchecked or
RunTime Exceptions. There are subclasses of RuntimeException (which means they are
subclasses of Exception also.)
Other Exception Classes (which don`t fit the earlier
definition). These are also called Checked Exceptions. Exception,
CheckedException1,CheckedException2 are checked exceptions. They are subclasses
of Exception which are not subclasses of RuntimeException.
Consider the example below. The method addAmounts throws a
new Exception. However, it gives us a compilation error because Exception is a
Checked Exception.
All classes that are not RuntimeException or subclasses of
RuntimeException but extend Exception are called CheckedExceptions. The rule
for CheckedExceptions is that they should be handled or thrown. Handled means
it should be completed handled - i.e. not throw out of the method. Thrown means
the method should declare that it throws the exception
class AmountAdder {
static Amount
addAmounts(Amount amount1, Amount amount2) {
if (!amount1.currency.equals(amount2.currency))
{
throw new Exception("Currencies don`t match");// COMPILER ERROR!
// Unhandled exception type Exception
}
return new Amount(amount1.currency, amount1.amount
+ amount2.amount);
}
}
Let`s look at how to declare
throwing an exception from a method.
Look at the line "static Amount addAmounts(Amount
amount1, Amount amount2) throws Exception". This is how we declare that a
method throws Exception.
class AmountAdder {
static Amount addAmounts(Amount
amount1, Amount amount2) throws Exception {
if (!amount1.currency.equals(amount2.currency))
{
throw
new Exception("Currencies don`t match");
}
return new Amount(amount1.currency, amount1.amount
+ amount2.amount);
}
}
We can create a custom exception by extending Exception
class or RuntimeException class. If we extend Exception class, it will be a
checked exception class. If we extend RuntimeException class, then we create an
unchecked exception class.
class
CurrenciesDoNotMatchException extends Exception{
}
Let`s now create some sample code to use CurrenciesDoNotMatchException.
Since it is a checked exception we need do two things a. throw new CurrenciesDoNotMatchException();
b. throws CurrenciesDoNotMatchException
(in method declaration).
class AmountAdder {
static Amount
addAmounts(Amount amount1, Amount amount2)
throws CurrenciesDoNotMatchException
{
if (!amount1.currency.equals(amount2.currency))
{
throw
new CurrenciesDoNotMatchException();
}
return new Amount(amount1.currency, amount1.amount
+ amount2.amount);
}
}
Specific Exception catch blocks should be before the catch
block for a Generic Exception. For example, CurrenciesDoNotMatchException
should be before Exception. Below code gives a compilation error.
public static void main(String[] args) {
AmountAdder.addAmounts(new Amount("RUPEE", 5), new Amount("DOLLAR", 5));
} catch (Exception e) { //
COMPILER ERROR!! System.out.println("Handled
Exception");
} catch (CurrenciesDoNotMatchException e)
{ System.out.println("Handled CurrenciesDoNotMatchException");
}
}
Never Completely Hide Exceptions. At the least log them.
printStactTrace method prints the entire stack trace when an exception occurs.
If you handle an exception, it is always a good practice to log the trace.
public
static void main(String[] args) {
AmountAdder.addAmounts(new Amount("RUPEE", 5), new Amount("RUPEE", 5));
String string = null; string.toString();
} catch (CurrenciesDoNotMatchException e) {
System.out.println("Handled CurrenciesDoNotMatchException");
e.printStackTrace();
}
}
File file = new File("FileName.txt");
System.out.println(file.exists());
System.out.println(file.createNewFile());
System.out.println(file.getAbsolutePath()); System.out.println(file.isFile());//true System.out.println(file.isDirectory());//false
File fileWithNewName = new File("NewFileName.txt"); file.renameTo(fileWithNewName);
//There is
no method file.renameTo("NewFileName.txt");
File directory = new File("src/com/rithus");
System.out.println(directory.getAbsolutePath());
System.out.println(directory.isDirectory());//true
File fileInDir = new File(directory,"NewFileInDirectory.txt");
System.out.println(fileInDir.createNewFile()); //true -‐ First Time
System.out.println(Arrays.toString(directory.list()));
Creating a directory
File
newDirectory = new
File("newfolder");
System.out.println(newDirectory.mkdir());//true -‐ First Time
File notExistingDirectory = new File("notexisting"); File newFile = new File(notExistingDirectory,"newFile");
//Will
throw Exception if uncommented: No such file or directory
//newFile.createNewFile(); System.out.println(newDirectory.mkdir());//true -‐ First Time
We can write to a file using FileWriter class.
//FileWriter helps to write stuff into the
file FileWriter fileWriter = new FileWriter(file); fileWriter.write("How are you doing?");
//Always flush before close. Writing to file
uses Buffering. fileWriter.flush();
fileWriter.close();
FileWriter fileWriter2 = new FileWriter("FileName.txt"); fileWriter2.write("How are you doing Buddy?");
//Always flush before close. Writing to file
uses Buffering. fileWriter2.flush();
fileWriter2.close();
File Reader can be used to read entire content from a file
at one go.
FileReader fileReader = new FileReader(file);
char[] temp = new char[25];
//fileReader reads entire file and stores it into temp System.out.println(fileReader.read(temp));//18 -‐ No of characters Read from file
System.out.println(Arrays.toString(temp));//output
below
//[H, o, w, ,
a, r, e, , y, o, u, , d, o, i, n, g, ?, , , , , ,] fileReader.close();//Always
close anything you opened:)
FileReader fileReader2 = new FileReader("FileName.txt"); System.out.println(fileReader2.read(temp));//24 System.out.println(Arrays.toString(temp));//output below
BufferedWriter
class helps writing to a class with better buffering than FileWriter.
BufferedWriter Constructors only accept another Writer as argument.
FileWriter fileWriter3 = new FileWriter("BufferedFileName.txt"); BufferedWriter
bufferedWriter = new BufferedWriter(fileWriter3); bufferedWriter.write("How are you doing Buddy?");
bufferedWriter.newLine();
bufferedWriter.write("I`m Doing Fine");
//Always flush before close. Writing to file
uses Buffering. bufferedWriter.flush();
bufferedWriter.close(); fileWriter3.close();
FileReader fileReader3 = new FileReader("BufferedFileName.txt"); BufferedReader bufferedReader
= new BufferedReader(fileReader3);
String line;
//readLine
returns null when reading the file is completed.
while((line=bufferedReader.readLine())
!= null){
System.out.println(line);
}
PrintWriter printWriter =
new PrintWriter("PrintWriterFileName.txt");
//writes " My Name" to the file printWriter.format("%15s",
"My Name");
printWriter.println(); //New Line
printWriter.println("Some Text");
//writes "Formatted Number:
4.50000" to the file printWriter.printf("Formatted Number: %5.5f", 4.5);
printWriter.flush();//Always flush a writer printWriter.close();
Serialization helps us to
save and retrieve the state of an object.
·
Serialization => Convert object state to some internal
object representation.
·
De-Serialization => The reverse. Convert internal
representation to object.
Two important methods
·
ObjectOutputStream.writeObject() // serialize and write to file
·
ObjectInputStream.readObject() // read from file and deserialize
To serialize an object it should implement Serializable
interface. In the example below, Rectangle class implements Serializable
interface. Note that Serializable interface does not declare any methods to be
implemented.
Below example shows how an instance of an object can be
serialized. We are creating a new Rectangle object and serializing it to a file
Rectangle.ser.
class Rectangle implements Serializable {
public
Rectangle(int length,
int breadth)
{
this.length = length; this.breadth = breadth; area
= length * breadth;
}
int length; int breadth; int area;
}
FileOutputStream fileStream = new FileOutputStream("Rectangle.ser"); ObjectOutputStream
objectStream = new
ObjectOutputStream(fileStream); objectStream.writeObject(new Rectangle(5,
6));
objectStream.close();
Below example show how a object can be deserialized from a
serialized file. A rectangle object is deserialized from the file Rectangle.ser
FileInputStream fileInputStream = new FileInputStream("Rectangle.ser"); ObjectInputStream
objectInputStream = new ObjectInputStream(
fileInputStream);
Rectangle rectangle = (Rectangle)
objectInputStream.readObject(); objectInputStream.close(); System.out.println(rectangle.length);// 5
System.out.println(rectangle.breadth);// 6
System.out.println(rectangle.area);// 30
We mark all the properties of the object which should not
be serialized as transient. Transient attributes in an object are not
serialized. Area in the previous example is a calculated value. It is
unnecessary to serialize and deserialize. We can calculate it when needed. In
this situation, we can make the variable transient. Transient variables are not
serialized. (transient
int area;)
//Modified
Rectangle class
class Rectangle implements Serializable {
public
Rectangle(int length,
int breadth)
{
this.length = length; this.breadth = breadth; area
= length * breadth;
}
int length;
int breadth;
transient
int area;
}
If you run the program
again, you would get following output
System.out.println(rectangle.length);// 5
System.out.println(rectangle.breadth);// 6
System.out.println(rectangle.area);// 0
Note that the value of rectangle.area is set to 0.
Variable area is marked transient. So, it is not stored into the serialized
file. And when de-serialization happens area value is set to default value i.e.
0.
Objects of one class might contain objects of other
classes. When serializing and de-serializing, we might need to serialize and
de-serialize entire object chain. All classes that need to be serialized have
to implement the Serializable interface. Otherwise, an exception is thrown.
Look at the class below. An object of class House contains an object of class Wall.
class
House implements
Serializable {
public
House(int number)
{
super();
this.number
= number;
}
Wall wall;
int number;
}
class
Wall{
int length; int breadth; int color;
}
House implements Serializable. However, Wall doesn`t
implement Serializable. When we try to serialize an instance of House class, we
get the following exception.
Output:
Exception in thread "main" java.io.NotSerializableException: com.rithus.serialization.Wall
at
java.io.ObjectOutputStream.writeObject0(Unknown Source)
at
java.io.ObjectOutputStream.defaultWriteFields(Unknown Source)
This is because Wall is
not serializable. Two solutions are possible.
1.
Make Wall transient. Wall object will not be serialized.
This causes the wall object state to be lost.
2.
Make Wall implement Serializable. Wall object will also be
serialized and the state of wall object along with the house will be stored.
class
House implements
Serializable {
public
House(int number)
{
super();
this.number
= number;
}
transient
Wall
wall;
int number;
}
class
Wall implements
Serializable {
int length; int breadth; int color;
}
With both these programs,
earlier main method would run without throwing an exception.
If you try de-serializing, In Example2, state of wall
object is retained whereas in Example1, state of wall object is lost.
No. When a class is De-serialized, initialization
(constructor`s, initializer`s) does not take place. The state of the object is
retained as it is.
Static Variables are not
part of the object. They are not serialized.
Threads
allow Java code to run in parallel. Let`s look at an example to understand what
we can do with Threads.
·
STEP I: Download and Store Bowling Statistics => 60 Minutes
· STEP III: Download
and Store Fielding Statistics => 15 Minutes
Steps
I, II and III are independent and can be run in parallel to each other. Run
individually this program takes 160 minutes. We would want to run this program
in lesser time. Threads can be a solution to this problem. Threads allow us to
run STEP I, II and III in parallel and run Step IV when all Steps I, II and III
are completed.
ThreadExamples example = new ThreadExamples();
example.downloadAndStoreBattingStatistics(); example.downloadAndStoreBowlingStatistics();
example.downloadAndStoreFieldingStatistics();
example.mergeAndAnalyze();
This is
where Threads come into picture.
Using Multi-‐Threading
we can
run each
of the
above steps in parallel and
synchronize when needed. We will understand more about synchronization later.
Creating
a Thread class in Java can be done in two ways. Extending Thread class and
implementing Runnable interface. Let`s create the BattingStatisticsThread
extending Thread class and BowlingStatisticsThread implementing Runnable
interface.
Look
at the example below: A dummy implementation for BattingStatistics is provided
which counts from 1 to 1000.
class BattingStatisticsThread extends Thread {
//run
method without parameters
public
void run() {
for (int i = 0; i < 1000; i++) System.out
.println("Running Batting
Statistics Thread "
+ i);
}
}
class BowlingStatisticsThread implements Runnable {
//run
method without parameters
public
void run() {
for (int i = 0; i < 1000; i++) System.out
.println("Running Bowling
Statistics Thread "
+ i);
}
}
Running a Thread in Java is slightly different based on
the approach used to create the thread.
BattingStatisticsThread battingThread1 = new BattingStatisticsThread();
battingThread1.start();
·
Create an object of the BowlingStatisticsThread(class implementing Runnable).
· Call the start
method on the thread.
BowlingStatisticsThread battingInterfaceImpl = new BowlingStatisticsThread();
Thread battingThread2 = new Thread(
battingInterfaceImpl); battingThread2.start();
·
NEW;
·
RUNNING;
· TERMINATED/DEAD;
Let`s consider the example that we discussed earlier.
Example Program
LINE 1: BattingStatisticsThread battingThread1 = new BattingStatisticsThread();
LINE 2: battingThread1.start();
LINE 3: BowlingStatisticsThread battingInterfaceImpl = new BowlingStatisticsThread();
LINE 4: Thread battingThread2 = new Thread(battingInterfaceImpl);
LINE
5:battingThread2.start();
A
thread is in RUNNABLE state when it is eligible to run, but not running yet. (A
number of Threads can be in RUNNABLE state. Scheduler selects which Thread to
move to RUNNING state). In the above example, sometimes the Batting Statistics
thread is running and at other time, the Bowling Statistics Thread is running.
When Batting Statistics thread is Running, the Bowling Statistics thread is
ready to run. It`s just that the scheduler picked Batting Statistics
thread to run at that instance and vice-‐versa. When Batting Statistics thread is Running, the Bowling Statistics Thread
is in Runnable state (Note that the Bowling Statistics Thread is not waiting for anything except
for the Scheduler to pick it up and run it).
A
thread is in BLOCKED/WAITING/SLEEPING state when it is not eligible to be run
by the Scheduler. Thread is alive but is waiting for something. An example can
be a Synchronized block. If Thread1 enters synchronized block, it blocks all
the other threads from entering synchronized code on the same instance or
class. All other threads are said to be in Blocked state.
What is priority of a thread? How do you change the priority of a thread? Scheduler
can be requested to allot more CPU to a thread by increasing the threads priority.
Each thread in Java is assigned
a default Priority
5. This priority can be increased or decreased (Range 1 to 10).
If two
threads are waiting, the scheduler picks the thread with highest priority to be
run. If all threads have equal priority, the scheduler then picks one of them
randomly. Design programs so that they don`t depend on priority.
class ThreadExample extends Thread {
public
void run() {
for (int i = 0; i < 1000; i++) System.out
.println( this.getName() + "
Running "
+ i);
}
}
ThreadExample thread1 = new ThreadExample();
thread1.setPriority(8);
Java also provides
predefined constants Thread.MAX_PRIORITY(10), Thread.MIN_PRIORITY(1),
Thread.NORM_PRIORITY(5) which can be used to assign priority to a thread.
Let`s
first look at an example where this problem can occur. Consider the code in the
setAndGetSum method.
int setandGetSum(int a1, int a2, int a3) { cell1 = a1;
sleepForSomeTime(); cell2 =
a2; sleepForSomeTime(); cell3 = a3;
sleepForSomeTime();
return cell1 + cell2 + cell3;
}
The way you can prevent multiple
threads from executing the same method is by using the synchronized
keyword on the method. If a method is marked synchronized, a different thread
gets access to the method only when there is no other thread currently
executing the method.
synchronized int setandGetSum(int a1,
int a2,
int a3)
{ cell1 = a1;
sleepForSomeTime(); cell2 =
a2; sleepForSomeTime(); cell3 = a3;
sleepForSomeTime();
return cell1 + cell2 + cell3;
}
void synchronizedExample2() {
//All code
goes here..
}
}
synchronized static int getCount(){
return
count;
}
static
int getCount2(){
synchronized
(SynchronizedSyntaxExample.class)
{
return
count;
}
}
Let`s consider the thread`s declared below: thread2,
thread3, thread4
ThreadExample thread2 = new ThreadExample();
ThreadExample thread3 = new ThreadExample(); ThreadExample thread4 = new ThreadExample();
Let`s say we would
want to run thread2 and thread3 in parallel but thread4 can only run when
thread3 is finished. This can be achieved using join method.
Join method example
thread3.start(); thread2.start();
thread3.join();//wait for
thread 3 to complete System.out.println("Thread3
is completed."); thread4.start();
thread3.join()
method call force the execution of main method to stop until thread3 completes
execution. After that, thread4.start() method is invoked, putting thread4 into
a Runnable State.
thread4.join(2000);
In above example,
main method thread would wait for 2000 ms or the end of execution of
thread4, whichever is minimum.
A call to yield
method changes the state of thread from RUNNING to RUNNABLE. However, the
scheduler might pick up the same thread
to run again, especially if it is the thread
with highest priority.
Summary is yield
method is a request from a thread to go to Runnable state. However, the
scheduler can immediately put the thread back to RUNNING state.
sleep
is a static method in Thread class. sleep method can throw a
InterruptedException. sleep method causes the thread in execution to go to
sleep for specified number of milliseconds.
Let`s
consider a situation where thread1 is waiting for thread2 ( thread1 needs an
object whose synchronized code is being executed by thread1) and thread2 is
waiting for thread1. This situation is called a Deadlock. In a Deadlock
situation, both these threads would wait for one another for ever.
Important methods
are wait, notify and notifyAll.
synchronized(thread){
thread.start(); thread.wait();
}
synchronized (this)
{ calculateSumUptoMillion(); notify();
}
If more than one
thread is waiting for an object, we can notify all the threads by using
notifyAll method. thread.notifyAll();
package
com.rithus.threads;
class Calculator extends Thread {
long
sumUptoMillion;
long
sumUptoTenMillion;
public
void run() {
synchronized (this)
{ calculateSumUptoMillion(); notify();
}
calculateSumUptoTenMillion();
}
private void calculateSumUptoMillion() {
for (int i = 0; i < 1000000; i++) { sumUptoMillion += i;
}
System.out.println("Million done");
}
private
void calculateSumUptoTenMillion() {
for (int i = 0; i < 10000000; i++) { sumUptoTenMillion += i;
}
System.out.println("Ten Million done");
}
}
public class ThreadWaitAndNotify {
public static void main(String[]
args) throws
InterruptedException { Calculator thread = new Calculator();
synchronized(thread){
thread.start(); thread.wait();
}
System.out.println(thread.sumUptoMillion);
}
}
Output Million done 499999500000
Ten Million done