Core Java - III:
Marker Interface:
This is an empty interface (no fields or methods).
Serializable, Cloneable, and Remote interfaces are examples of marker interfaces.
Serialization and Deserialization:
Serialization:
Process of converting the state of an object into a byte stream after the program exists.
This byte stream can be saved as a file (.ser) which is platform-independent.
Through this process, the information of the object is saved into the file.
Steps to serialize:
Object class should implement a ‘Serializable’ interface.
Use the code below.
import java.io.serializable; FileoutputStream fileOut = new FileOutputStream("file path"); ObjectOutputStream out = new ObjectOutputStream(fileOut); out.writeObject(objectName); out.close(); fileOut.close();
Deserialization:
The reverse process of converting a byte stream into an object.
Steps to deserialize:
Create a class same as the one that’s serialized and declare the object (don’t instantiate).
Object class should implement a ‘Serializable’ interface.
Use the code below.
import java.io.Serializable; FileInputStream fileIn = new FileInputStream("file path"); ObjectInputStream in = new ObjectInputStream(fileIn); objectName = (<datatype of the object>) in.readObject(); in.close(); fileIn.close();
Important Notes:
Children classes of a parent class that implements ‘Serializable’ will do so as well.
Static fields are not serialized. Because they belong to the class itself, and not to an individual object.
The class definition itself is not recorded. So while deserializing, cast the input stream as the object type.
Fields declared as “transient” aren’t serialized, they’re ignored.
SerialVersionUID:
Unique ID that functions like a version number.
Verifies that the sender and receiver of a serialized object, have loaded classes for that object that match.
Ensures objects will be compatible with machines.
The number must match, otherwise, this will cause ‘InvalidClassException’.
SerialVersionUID will be calculated based on class properties, members, etc...
A serializable class can declare its own ‘serialVersionUID’ explicitly (recommended).
Timer and TimerTask:
Timer:
- A facility for threads to schedule tasks for future execution in a background thread.
TimerTask:
- A task that can be scheduled for one-time or repeated execution by a Timer.
Threads:
A thread is a lightweight sub-process, the smallest unit of processing.
Threads are independent. So, if there occurs an exception in one thread, it doesn’t affect other threads. It uses a shared memory area.
Java provides a Thread class to achieve thread programming.
JVM allows an application to have multiple threads running concurrently.
Each thread can execute parts of the code in parallel with the main thread.
Each thread has a priority.
Threads with higher priority are executed in preference compared to threads with lower priority.
JVM continues to execute threads until either of the following occurs,
The exit method of class Runtime has been called.
All user threads have died.
When a JVM starts up, there is a thread that calls the main method. This thread is called as main.
There are two kinds of threads:
Daemon thread
User thread
A Daemon thread is a low-priority thread that runs in the background to perform tasks such as garbage collection.
JVM terminates itself when all user threads (non-daemon threads) finish their execution.
There are two ways to create threads.
Extending the Thread class.
Creating an instance of a class that implements the Runnable interface, and passes the instance within the constructor of a Thread class.
Example:
Thread t = new Thread(new Runnable(){ @Override public void run() { } });
Multithreading:
Process of executing multiple threads simultaneously.
Helps maximize utilization of CPU.
Threads are independent. So, if there occurs an exception in one thread, it doesn’t affect other threads.
Useful for multiple clients, multiplayer games, etc...
Collections Framework:
The Collection interface (java.util.Collection) and Map interface (java.util.Map) are the two main root interfaces of java collection classes.
Advantages of Collections:
Consistent API → The API has a basic set of interfaces like Collection, Set, List, or Map and all the classes like ArrayList, LinkedList, Vector, etc... that implement these interfaces and have some common methods.
Reduces programming effort → A developer doesn’t have to worry about the design of the collections.
Increases program speed and quality → Increases performance by providing high-performance implementations of useful data structures and algorithms.
Hierarchy of the Collection framework:
Root interfaces:
Iterable interface:
Root interface for the entire collection framework.
The collection interface extends the iterable interface.
Therefore all the interfaces, and classes implement this interface.
This interface provides an iterator to iterate through all the collections.
This interface contains only one abstract method which is the iterator.
Collection interface:
This interface extends the iterable interface and is implemented by all the classes in the collections framework.
This interface contains all the basic methods which every collection has like addition, removing, clearing the data, etc...
Having these methods in this interface ensures that the names of the methods are universal for all the collections.
This interface builds the foundation on which all the collection classes are implemented.
List interface:
This is a child interface of the Collection interface.
This interface is dedicated to the data of the list type in which we can store all the ordered collections of the objects.
This also allows duplicate data to be present in it.
This list interface is implemented by various classes like ArrayList, Vector, Stack, etc.
Since all the subclasses implement the list, we can instantiate a list object with any of these classes as shown below.
List <T> al = new ArrayList<> (); List <T> ll = new LinkedList<> (); List <T> v = new Vector<> ();
Queue interface:
As the name suggests, a queue interface maintains the FIFO(First In First Out) order similar to a real-world queue line.
This interface is dedicated to storing all the elements where the order of the elements matters.
There are various classes like PriorityQueue, ArrayDeque, etc.
Since all these subclasses implement the queue, we can instantiate a queue object with any of these classes.
Queue <T> pq = new PriorityQueue<> (); Queue <T> ad = new ArrayDeque<> ();
Dequeue interface:
This is a very slight variation of the Queue Data Structure.
It is also known as a double-ended queue, which is a data structure where we can add and remove the elements from both ends of the queue.
This interface extends the queue interface. The class which implements this interface is ArrayDeque.
Deque<T> ad = new ArrayDeque<> ();
Set interface:
A set is an unordered collection of objects in which duplicate values cannot be stored.
This collection is used when we wish to avoid the duplication of the objects and wish to store only the unique objects.
This set interface is implemented by various classes like HashSet, TreeSet, LinkedHashSet, etc.
Since all the subclasses implement the set, we can instantiate a set object with any of these classes.
Set<T> hs = new HashSet<> (); Set<T> lhs = new LinkedHashSet<> (); Set<T> ts = new TreeSet<> ();
Sorted Set interface:
This interface is very similar to the set interface.
The only difference is that this interface has extra methods that maintain the ordering of the elements.
The sorted set interface extends the set interface and is used to handle the data which needs to be sorted.
The class which implements this interface is TreeSet.
Since this class implements the SortedSet, we can instantiate a SortedSet object with this class.
SortedSet<T> ts = new TreeSet<> ();
Map interface:
A map is a data structure that supports the key-value pair mapping for the data.
This interface doesn’t support duplicate keys because the same key cannot have multiple mappings.
A map is useful if there is data and we wish to perform operations based on the key.
This map interface is implemented by various classes like HashMap, TreeMap, etc.
Since all the subclasses implement the map, we can instantiate a map object with any of these classes.
Map<T> hm = new HashMap<> (); Map<T> tm = new TreeMap<> ();
Classes that implement List interface:
ArrayList:
ArrayList provides us with dynamic arrays in Java.
Though, it may be slower than standard arrays but can be helpful in programs where lots of manipulation in the array is needed.
The size of an ArrayList is increased automatically if the collection grows or shrinks when the objects are removed from the collection.
ArrayList<Integer> al = new ArrayList<Integer>();
LinkedList:
LinkedList class is an implementation of the LinkedList Data Structure which is a linear data structure where the elements are not stored in contiguous locations and every element is a separate object with a data part and address part.
The elements are linked using pointers and addresses.
Each element is known as a node.
LinkedList<Integer> ll = new LinkedList<Integer>();
Vector:
A vector provides us with dynamic arrays in Java.
Though, it may be slower than standard arrays but can be helpful in programs where lots of manipulation in the array is needed.
This is identical to ArrayList in terms of implementation.
However, the primary difference between a vector and an ArrayList is that a Vector is synchronized and an ArrayList is non-synchronized.
Synchronized means only one thread at a time can access the code.
Vector<Integer> v = new Vector<Integer>();
Stack:
Stack class models and implements the Stack Data Structure.
The class is based on the basic principle of last-in-first-out.
In addition to the basic push and pop operations, the class provides three more functions empty, search and peek.
This class can also be referred to as the subclass of Vector.
Stack is a subclass of Vector and a legacy class. It is thread-safe which might be overhead in an environment where thread safety is not needed. An alternate to Stack is to use ArrayDequeue which is not thread-safe and has faster array implementation.
Stack<String> stack = new Stack<String>();
Classes that implement Queue interface:
PriorityQueue:
A PriorityQueue is used when the objects are supposed to be processed based on priority.
It is known that a queue follows the First-In-First-Out algorithm, but sometimes the elements of the queue are needed to be processed according to the priority and this class is used in these cases.
The PriorityQueue is based on the priority heap. The elements of the priority queue are ordered according to the natural ordering, or by a Comparator provided at queue construction time, depending on which constructor is used.
PriorityQueue<Integer> pQueue = new PriorityQueue<Integer>();
Classes that implement Deque interface:
ArrayDeque class provides us with a way to apply a resizable array.
This is a special kind of array that grows and allows users to add or remove an element from both sides of the queue.
Array deques have no capacity restrictions and they grow as necessary to support usage.
ArrayDeque<Integer> de_que = new ArrayDeque<Integer>(10);
Classes that implement the Set interface:
HashSet:
The HashSet class is an inherent implementation of the Hash Table Data Structure.
The objects that we insert into the HashSet do not guarantee to be inserted in the same order.
The objects are inserted based on their hashcode.
This class also allows the insertion of NULL elements.
HashSet<String> hs = new HashSet<String>();
LinkedHashSet:
A LinkedHashSet is very similar to a HashSet.
The difference is that this uses a doubly linked list to store the data and retains the ordering of the elements.
LinkedHashSet<String> lhs = new LinkedHashSet<String>();
Classes that implement the Sorted Set interface:
TreeSet:
The TreeSet class uses a Tree for storage.
The ordering of the elements is maintained by a set using their natural ordering whether or not an explicit comparator is provided.
This must be consistent with equals if it is to correctly implement the Set interface.
It can also be ordered by a Comparator provided at a set creation time, depending on which constructor is used.
TreeSet<String> ts = new TreeSet<String>();
Classes that implement Map interface:
HashMap:
HashMap provides the basic implementation of the Map interface of Java.
It stores the data in (Key, Value) pairs.
To access a value in a HashMap, we must know its key.
HashMap uses a technique called Hashing.
Hashing is a technique of converting a large String to a small String that represents the same String so that the indexing and search operations are faster.
HashSet also uses HashMap internally.
HashMap<Integer, String> hm = new HashMap<Integer, String>();
TreeMap:
The TreeMap in Java has been used to implement Map and NavigableMap interfaces along with the AbstractMap Class.
The map is sorted according to the natural ordering of its keys, or by a Comparator provided at map creation time, depending on which constructor is used.
This proves to be an efficient way of sorting and storing the key-value pairs.
The storing order maintained by the treemap must be consistent with equals just like any other sorted map, irrespective of the explicit comparators.
The treemap implementation is not synchronized in the sense that if a map is accessed by multiple threads, concurrently and at least one of the threads modifies the map structurally, it must be synchronized externally.
Lambda:
A lambda expression is a short block of code that takes in parameters and returns a value.
Lambda expressions are similar to methods, but they do not need a name and they can be implemented right in the body of a method.
(n) -> { System.out.println(n); }