Welcome to the Java Additional Topics tutorial offered by Simplilearn. The tutorial is a part of the Java Certification Training Course.
Let us begin with the objectives of this tutorial in the next section.
Let us now see the topics covered in this Java additional topics tutorial -
Explain Inner Classes and Inner Interfaces
Define String API
Define Thread
Determine Collection Framework
Explain Comparable Comparator and other Functional Interfaces
Identify File Handling and Serialization
Let us learn the Java inner classes and inner interfaces in the next section
Let us now learn about the java inner class.
The inner class is a class declared inside the class or interface that can access all the members of the outer class, including private data members and methods.
class Outer_class
{ //code
class Inner_class{ //code
}
}
The code snippet above gives the scope of the outer class. Within the outer class, we have defined an inner class.
The advantages of doing so include -
Accessible to all data members and methods of the outer class, including private data members and methods.
It develops a more maintainable and readable code.
And it requires less coding effort. In our classes are also referred to as nested classes.
Inner interface, also known as the nested interface, declares an interface inside another interface.
public interface Map
{
interface Entry{
int getKey();
}
void clear();
}
Here we observe the outer loop of the interface and within it, we have in an interface that has been declared.
The advantages include -
It can be used to group interfaces together.
Encapsulation can be done using inner interfaces
Interfaces are used to maintain and read codes better.
In the next section, we will learn about the Java threads.
You too can join the high earne’s club. Enroll in our Java Certification Course and join the high earner’s club.
A thread is an independent part of the execution within a program. The java.lang.Thread class enables you to create and control threads. A Java thread is composed of three main parts.
They are -
The virtual CPU,
The code that the CPU executes, and
The data on which the code works.
All our systems have a processor, and ideally, then you run the public static void main program, You are typically running a single process on top of your processor. Although, your processor has much more capability.
Whenever the processor has some spare time, we can start parallel processes that run in parallel within the main thread so that we can utilize the time of the processor to effectively perform some computational action.
Hence, within the main thread that is already running, we can start parallel child processes or child threads that are running so that when the processor has some idle time, we can effectively utilize that idle time to do parallel processing and have multiple threads running in parallel.
Each thread can then be assigned a work, which could be a function to execute or some code block to execute.
A Java thread can be created by the thread class or by implementing Runnable Interface. Commonly used constructors of the Java thread class include -
Thread()
Thread(String name)
Thread(Runnable r)
Thread(Runnable r, String name)
They accept either a string name of the thread or a runnable object or both. To create a thread, the java.lang.The Runnable interface is preferred over inheriting from java.lang.Thread. Since Java doesn't support multiple inheritances, the extended Java thread class will lose the chance to further extend or inherit other classes.
Implementing Runnable interface will make the code easily maintainable as it does a logical separation of the task from the runner. In object-oriented programming, extending a class means modifying or improving the existing class, implementing Runnable is hence a good practice.
There are two ways in which we could create a thread, one of them is where you can go ahead and inherit the thread class, in which case you cannot further inherit from any other class as java follows the single inheritance. The other way which is also a better way is to implement the Runnable interface, and then you can go ahead and inherit from a class of your choice in parallel.
In the next section, let us look at an example of creating a Java Thread.
Let us look at a simple example of creating a thread. Here, we have a class called ThreadTester and HelloRunner. The HelloRunner class implements the Runnable interface. Run method is the method that will be called the moment we start the thread and this method has been defined inside the Runnable interface.
public class ThreadTester
{
public static void main(String args[ ]) {
HelloRunner r = new HelloRunner();
Thread t = new Thread(r);
t.start();
}
}
class HelloRunner implements Runnable {
int i;
public void run() {
i=0;
while (true) {
System.out.println(“Hello” + i++);
if(i==50) {
break;
}
}
}
}
In the next section, we will look at synchronizing a Java thread.
Synchronized keyword enables a programmer to control threads that are sharing data. Synchronization can be done in two ways -
It can be applied before a thread.
It can be applied after the trip.
Every object is associated with a flag called object lock flag, and this flag is enabled by a synchronized keyword.
Let us now look at Applying synchronized before a thread and Applying synchronized after a thread.
To apply synchronized before a thread. When a thread reaches the synchronized statement, it examines the object, passed as the argument, and tries to obtain the lock flag from that object before continuing to the next step.
Here, the moment this particular code block is accessed, the thread simply tries to acquire a lock over this block of code and then proceeds with processing this code block.
In this example, we see that the synchronization is applied after a thread. That is where it will try to acquire a lock on this particular block of code. This is an example of the applying or acquiring a synchronized lock after the thread.
In the next section, we will learn about Java Deadlock.
Deadlock is a part of the Java multithreading. It occurs when a thread is waiting for an object lock that is held by another thread. But, the other thread is waiting for an object lock that has already been held by the first thread.
Let us understand deadlock with an example.
public class TestThread
{
public static Object Lock1 = new Object();
public static Object Lock2 = new Object();
public static void main(String args[]) {
Thread1 T1 = new Thread1();
Thread2 T2 = new Thread2();
T1.start();
T2.start(); }
private static class Thread1 extends Thread {
public void run() {
synchronized (Lock1) {
System.out.println("Thread 1: Hold lock 1...");
try { Thread.sleep(15); }
catch (InterruptedException e) {}
System.out.println("Thread 1: Wait lock 2...");
synchronized (Lock2) {
System.out.println("Thread 1: hold lock 1 & 2...");
}
}
}
}
private static class Thread2 extends Thread
{
public void run() {
synchronized (Lock2) {
System.out.println("Thread 2: Hold lock 2...");
try { Thread.sleep(10); }
catch (InterruptedException e) {}
System.out.println("Thread 2: Wait for lock 1...");
synchronized (Lock1) {
System.out.println("Thread 2: Hold lock 1 &
2...");
}
}
}
}
}
Output:
Thread 1: Hold lock 1…
Thread 2: Hold lock 2…
Thread 1: Wait for lock 1…
Thread 2: Wait for lock 2…
In the next section, we will look at the Java collection framework.
This is part of the java.util.Collections package.
The java.util.Collections class consists exclusively of static methods that operate on or return collections. It contains polymorphic algorithms that operate on collections, "wrappers" (which return a new collection backed by a specified collection), and a few other odds and ends.
The methods of this class all throw a NullPointerException if the collections or class objects provided to them are null.
The fields for java.util.Collections class:
Modifier and Type |
Field and Description |
static List EMPTY_LIST |
This is the empty list. |
static Set EMPTY_SET |
This is the empty set. |
static Map EMPTY_MAP |
This is the empty map |
There are several purposes of implementation of core interfaces (Set, List, and Map) available as part of the Collection framework. This allows us to store information in memory which could be without a restriction often array where an array has a fixed size whereas collections do not have a fixed size.
Hash Table |
Resizable Array |
Balanced Tree |
Linked List |
Hash Table + Linked List |
|
Set |
HashSet |
TreeSet |
LinkedHashSet |
||
List |
ArrayList |
LinkedList |
|||
Map |
HashMap |
TreeMap |
LinkedHashMap |
General Purpose Collection Implementations
Let us now look at an example of the HashSet.
public class HashSetExample {
public static void main(String args[ ]) {
// HashSet declaration
Set<String> hset = new HashSet<String>();
// Adding elements to the HashSet
hset.add("Apple");
hset.add("Mango");
hset.add("Grapes");
hset.add("Orange");
hset.add("Fig");
//Addition of duplicate elements
hset.add("Apple");
hset.add("Mango");
//Addition of null values
hset.add(null);
hset.add(null);
//Displaying HashSet elements
System.out.println(hset);
}
}
The declaration of the HashSet is specialized to the type string, so it will only accept the string type. We then add a few values to the HashSet. We know that, collections also accept duplicate values, therefore, in our code we are adding two duplicate values. We can also add null values as collections accept reference types and reference types are allowed to point to nothing.
This is an example of the ArrayList.
public class ArrayListExample {
public static void main(String args[ ]) {
/*Creation of ArrayList: add String elements so I made it of string type */
List<String> obj = new ArrayList<String>();
/*This is how elements should be added to the array list*/
obj.add("Ajit");
obj.add(“Sam");
obj.add(“Robert");
obj.add("Steve");
obj.add(“Soumya");
/* Displaying array list elements */
System.out.println("Currently the array list has following elements:"+obj);
/*Add element at the given index*/
obj.add(0, "Rahul");
obj.add(1, "Justin");
}
}
Here, we create a List object and specialize it to the ArrayList with a generic implementation of the type string which mentions that it can take only strings. We then add a few string values and then we can prit out the entire list.
Let us now look at the next collection which is the HashMap. Let us look at an example where at the start of the program, we are creating an object of the map. The map takes, a key and a pair value. We are specializing this to an integer so that all key should be of the type INT and all the values to the corresponding key should be of the type string.
public class Details
{
public static void main(String args[ ]) {
/* This is how to declare HashMap */
Map<Integer, String> hmap = new HashMap<Integer, String>();
/*Adding elements to HashMap*/
hmap.put(1, “Sam");
hmap.put(2, "Rahul");
hmap.put(3, "Singh");
hmap.put(9, "Ajeet");
hmap.put(14, "Anuj");
/* Display contentCollection Implementation Example using Iterator*/ —HashMap in Map
Iterator<Map.Entry<Integer,String>> it= hmap.entrySet().iterator();
while(it.hasNext()){
Map.Entry<Integer,String> e=it.next();
System.out.print("key is: "+ e.getKey() + " & Value is: ");
System.out.println(e.getValue());
}
/* Get values based on key*/
String var= hmap.get(2);
System.out.println("Value at index 2 is: "+var)
}
}
This is the hierarchy of the collection framework. These are the interfaces that are available. There are three interfaces that inherit from Java collection, which is:
the list,
queue,
set
In terms of the list, we have ArrayList that we've used earlier; in terms of queues, we have linked list in PriorityQueue, which is implemented as a queue. In terms of sets, which hold the key pair values, as we've seen in the HashSet.
In terms of the map, we have HashMap, HashTable, SortedMap, and TreeSet. So these are the set off collection classes that also provide, the hash table or a key pair value storage mechanism.
The comparable interface is a member of the “java.lang” package. By implementing the Java comparable interface, you can provide order to objects of any class. You can also sort collections that contain objects of classes that implement the Comparable interface.
Some Java classes that can implement the comparable interface are Byte, Long, String, Date, and Float. To write custom comparable types, you need to implement the compareTo method of the Comparable interface.
The Comparable interface can be used to implement a compareTo method. This compareTo method can be used specifically when we are sorting custom objects. We can use it to evaluate whether the value of one object is larger than the other object and accordingly decide how to sort those objects.
In the next section, we will learn about the Java comparator.
The comparator interface is used to order the objects of the user-defined class. For example, consider the Student class described previously; the sorting of students was restricted to sorting on GPAs. A comparator object is capable of comparing two objects of two different classes.
In the next section, we will look at the Java comparable and comparator example.
Let us see an example. We are creating a new package called comparable comparator demo. We create a class called bus that implements the comparable interface. The comparable interface is specialized to the type Bus.
We have getter and the setter for the bus id and the bus name to retreive and store values. We also have a getter and setter for the fare and also for the ratings.
package comparable_comparator_demo;
public class Bus implements Comparable<Bus>{
private Integer busId;
private String busName;
private Double fare;
private Double ratings;
public Integer getBusId() {
return busId;
}
public void setBusId(Integer busId) {
this.busId = busId;
}
public String getBusName() {
return busName;
}
public void setBusName(String busName) {
this.busName = busName;
}
public Double getFare() {
return fare;
}
public void setFare(Double fare) {
this.fare = fare;
}
public Double getRatings() {
return ratings;
}
public void setRatings(Double ratings) {
this.ratings = ratings;
}
@Override
public String toString() {
return "Bus [busId=" + busId + ", busName="
+ busName + ", fare="
+ fare + ", ratings=" +
ratings + "]";
}
public Bus() {
}
public Bus(Integer busId, String busName, Double fare, Double
ratings) {
super();
this.busId = busId;
this.busName = busName;
this.fare = fare;
this.ratings = ratings;
}
@Override
public int compareTo(Bus o) {
// TODO Auto-generated method stub
return o.busId.compareTo(this.busId);
}
}
From the above example, we will now create the main class:
package comparable_comparator_demo;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class BusMain {
public static void main(String[] args) {
// TODO Auto-generated method stub
Bus b1 = new Bus(1000, "Wipro Travels", 1500.50d, 4.8d);
Bus b2 = new Bus(1200, "Java Travels", 1200.50d, 3.8d);
Bus b3 = new Bus(1100, "J2EE Travels", 1750.50d, 4.9d);
Bus b4 = new Bus(1010, "JME Travels", 1250.50d, 2.1d);
Bus b5 = new Bus(1001, "List Travels", 1100.50d, 3.2d);
Bus b6 = new Bus(1900, "WOOW Travels", 1800.50d, 4.1d);
List<Bus> busList = new ArrayList<>();
busList.add(b1);
busList.add(b2);
busList.add(b3);
busList.add(b4);
busList.add(b5);
busList.add(b6);
Collections.sort(busList);
System.out.println("Printing all the buses");
for (int i = 0; i < busList.size(); i++) {
System.out.println(busList.get(i));
}
System.out.println();
System.out.println();
Collections.sort(busList, new FareComparator());
System.out.println("Printing all the buses sorted
based on Fare");
for (int i = 0; i < busList.size(); i++) {
System.out.println(busList.get(i));
}
System.out.println();
System.out.println();
Collections.sort(busList, new RatingComparator());
System.out.println("Printing all the buses sorted
based on Ratings");
for (int i = 0; i < busList.size(); i++) {
System.out.println(busList.get(i));
}
System.out.println("Printing busses using
foreach");
for(Bus b:busList){
System.out.println(b);
}
}
}
Rate Comparator syntax from the example:
package comparable_comparator_demo;
import java.util.Comparator;
public class RatingComparator implements Comparator<Bus> {
@Override
public int compare(Bus o1, Bus o2) {
// TODO Auto-generated method stub
return
o2.getRatings().compareTo(o1.getRatings());
}
}
Fare Comparator syntax from the example:
package comparable_comparator_demo;
import java.util.Comparator;
public class FareComparator implements Comparator<Bus> {
@Override
public int compare(Bus o1, Bus o2) {
// TODO Auto-generated method stub
return o1.getFare().compareTo(o2.getFare());
}
}
Iterator is used for looping through various collection classes such as HashMap, ArrayList, LinkedList, and so on. It is used to traverse collection object elements one by one. It is applicable for all Collection classes. Therefore, it is also known as Universal Java Cursor. It supports both READ and REMOVE Operations.
The syntax of Iterator:
Iterator<E> iterator()
Let us now look at an example for the iterator. Here, we are importing three packages, the iterator package, the LinkedList package, and the List package. We create an object of the type LinkedList and add three names to it, John, James, and Joseph.
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
public class ExternalIteratorDemo
{
public static void main(String[] args)
{
List<String> names = new LinkedList<>();
names.add(“John");
names.add(“James");
names.add(“Joseph"); // Getting Iterator
Iterator<String> namesIterator = names.iterator();
while(namesIterator.hasNext()){ // Traversing elements
System.out.println(namesIterator.next());
}
}
}
The For-each loop in Java is used to make the code clearly readable and also eliminates the programming errors.
The Syntax for Iterator For-each Loop is given by :
for(data_type variable : array | collection){ }
Let us now look at an example of the for-each loop. We have a simple integer array that takes four elements.
class ForEachExample1
{
public static void main(String args[ ]){
int arr[]={17,26,65,24};
for(int i:arr){
System.out.println(i);
}
}
}
Output:
17
24
26
65
The various Java file handling methods include -
Creating File objects
Manipulating File objects
Reading and Writing file streaming
File handling is saving data by creating a file on your file system. The file could be in the form of a text, a binary, or an XML. You can also manipulate those files by opening them, deleting them, inserting data, appending data to the file etc.
While reading and writing, we make use of the file stream. We can save all the data in the file stream and simply flush that data at one shot into the file. Hence, instead of having three round trips over the network, we will have a single round trip to push data into the file.
This is what is meant by file streaming. It is a temporary storage area, therefore, it is not required to keep doing I/O operations. I/O operations or reading and writing to the file frequently is expensive from a performance perspective and will hence degrade the performance of the application. Thus, the streaming feature prevents performance degradation.
The File class provides several utilities for handling files and obtaining information about them. You can create a File object that represents a directory and then use it to identify other files.
File myFile;
myFile = new File (“myfile.txt”);
myFile = new File (“MyDocs”, “myfile.txt”);
File myDir = new File (“MyDocs”);
myFile = new File (myDir, “myfile.txt”);
After creating a File object, we can use one of the following methods to gather information about the file.
File Names:
The following are the methods that return file names:
String getName()
String getPath()
String getAbsolutePath()
String getParent()
boolean renameTo (File newname)
General File Information and Utilities:
The following methods return General File Information:
long lastModified()
long length()
boolean delete()
Directory Utilities:
The following methods will provide directory utilities:
boolean mkdir()
String[ ] list()
File Tests:
The following methods will provide information about file
attributes:
boolean exists()
boolean canWrite()
boolean canRead()
boolean isFile()
boolean is Directory()
boolean isAbsolute()
boolean isHidden()
In Java, we can use java.io.BufferedReader to read content from a file.
import java.io.*;
public class ReadFile
{
public static void main (String [ ] args) {
File = new File (args[0]);
try {
//Create a buffered reader
// to read each line from a file.
BufferedREader in = new BufferedReader (new FileReader(file));
String s;
To write a file we make use of the following method:
Files.write(Paths.get(fileName), content.getBytes(), StandardOpenOption.CREATE);
This is explained with an example below using the try-catch blocks.
try
{
// Read each line from the file
s = in.readline();
while (s != null) {
System.out.println(“Read: “ + s);
s = in.readline();
}
} finally {
// Close the buffered reader
in.close();
}
}
catch (FileNotFOundException e1)
{
// If this file does not exist
System.err.println(“File not found: “ +
file);
s = in.readline();
}
catch (IOException e2) {
// Catch any other IO exceptions.
e2.printStackTrace();
}
}
}
Serialization is a mechanism for saving the objects as a sequence of bytes and rebuilding the
byte sequence back into a copy of the object later. For a class to be serialized, the class must implement the java.io.Serializable interface.
The Serializable interface has no methods and only serves as a marker that indicates that the
class that implements the interface can be considered for serialization. The difference between the file.io and serialization is that in file.io we simply take data and save it to the file, whereas in serialization, we are actually saving an object.
Serialization stores data along with structure and hence we can serialize an entire object to a file. Thus, we can retrieve both the state and structure of the object and this process is called as deserialization.
Java Certification Training caught your attention? Check out our course preview now!
When a field is a reference to an object, the fields of that referenced object are also serialized if
that object’s class is serializable. The tree or structure of an object’s fields, including these sub-objects, constitutes the object graph.
If the object graph contains a non-serializable object reference, the object can still be serialized
if the reference is marked with the transient keyword.
public class MyClass implements Serializable
{
public transient Thread myThread;
private String customerID
private int total;
}
From this example let us learn how exactly serialization is done.
Here, we are creating a new Date object and we are putting a try block because it helps in saving data to a file and there is a high probability that there could be an exception. We create an output stream giving the name of date.serialization (date.ser), then we create an object output stream and passing it with a reference of f; where, f is the name of the file to save.
public class SerializeDate
{
SerializeDate () {
Date d = new Date ();
try {
FileOutputStream f = newFileOutputStream (“date.ser”);
ObjectOutputStream s = new ObjectOutputStream (f);
s.writeObject (d); // Serialization starts here
s.close ();
} catch (IOException e) {
e.printStackTrace ();
}
}
public static void main (String args[]) {
new SerializeDate ();
}
}
After serializing the data from the previous example, we will now look at deserializing the data and converting it from the data we have saved in the file to an object. Thus, we create a date object and assign it to null.
We create an object input stream object and pass it a reference of file f. We then create, s.readObject which means it will read the object from the file called date.ser then we forcibly do a type cast to the type date and store that data in d.
public class DeSerializeDate
{
DeSerializeDate () {
Date d = null;
try {
FileInputStream f = newFileInputStream (“date.ser”);
ObjectInputStream s = new ObjectInputStream (f);
d = (Date) s.readObject ();// Serialization starts here
s.close ();
} catch (Exception e) {
e.printStackTrace ();
}
System.out.println(“Deserialized Date object from date.ser”);
System.out.println(“Date: ” +d);
}
public static void main (String args[]) {
new DeSerializeDate ();
}
}
Let us summarize what we have learned in this java additional topics tutorial :
Java inner class or nested class is a class that is declared inside the class or interface.
There are several purposes of implementations of core interfaces (Set, List, and
Map) in Collection framework.
A thread is an independent path of execution within a program.
By implementing the Comparable interface, you can provide order to the objects of any class. The Comparator interface provides greater flexibility with ordering.
Serialization is a mechanism for saving the objects as a sequence of bytes and rebuilding the byte sequence back into a copy of the object later.
Thus, we come to an end to the Java Additional Topics Tutorial.
Name | Date | Place | |
---|---|---|---|
Java Certification Training | 7 May -18 Jun 2021, Weekdays batch | Your City | View Details |
A Simplilearn representative will get back to you in one business day.