0% found this document useful (0 votes)
12 views

8. ClassNotes

The document provides an overview of Java I/O streaming, detailing the java.io package, which facilitates input and output operations through various classes such as InputStream and OutputStream. It explains the types of streams, including input, output, and error streams, and illustrates their usage with code examples for reading from and writing to files. Additionally, the document touches on filtering and piping streams for data processing, as well as the concept of Java Reflection.

Uploaded by

udayasai
Copyright
© © All Rights Reserved
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
12 views

8. ClassNotes

The document provides an overview of Java I/O streaming, detailing the java.io package, which facilitates input and output operations through various classes such as InputStream and OutputStream. It explains the types of streams, including input, output, and error streams, and illustrates their usage with code examples for reading from and writing to files. Additionally, the document touches on filtering and piping streams for data processing, as well as the concept of Java Reflection.

Uploaded by

udayasai
Copyright
© © All Rights Reserved
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 47

UNIT – 1 JAVA FUNDAMENTALS

1.1 Java I/O streaming

The java.io package is used to handle input and output operations. Java IO has various classes that handle input
and output sources. A stream is a sequence of data.

Java input stream classes can be used to read data from input sources such as keyboard or a file. Similarly
output stream classes can be used to write data on a display or a file again.

We can also perform File Handling using Java IO API.

Introduction to I/O Streams in Java

Before understanding IO streams, let us discuss streams. A Stream is also a sequence of data. It is neither a
data structure nor a store of data. For example, a river stream is where water flows from source to destination.
Similarly, these are data streams; data flows through one point to another.

We introduce a term called IO streamsto handle these sequences.

The java.io package helps the user perform all input-output operations. Java IO package is primarily focused on
input-output files, network streams, internal memory buffers, etc. Data is read and written from Java
IO's InputStream and OutputStream classes.

In other words, IO streams in Java help to read the data from an input stream, such as a file and write the data
into an output stream, such as the standard display or a file again. It represents the source as input and the
destination as output. It can handle all types of data, from primitive values to advanced objects.

What is Java IO?

The java.io package consists of output and input streams used to write and read data to files or other output and
input sources.

There are 3 categories of classes in java.io package:

 Input Streams.
 Output Streams.
 Error Streams.

Java supports three streams that are automatically attached with the console.

1. System.out: Standard output stream


2. System.in: Standard input stream
3. System.err: Standard error stream

Input Streams

As we know, an input source consists of data that needs to be read in order to extract information from it. Input
Streams help us read data from the input source. They are an abstract class that provides a programming
interface for all input streams.
Input streams are opened implicitly as soon as they are created. We use a close() method on the source object to
close the input stream.

Output Streams

The executed program's output must be stored in a file for further use. Output streams help us write data to an
output source(such as a file). Similarly to input streams, output streams are abstract classes that provide a
programming interface for all output streams.

The output stream is opened as soon as it is created and explicitly closed using the "close() "method.

Error Streams

Error streams are the same as output streams. In some ide’s, the error is displayed in different colors (other than
the color of the output color). It gives output on the console the same as output streams.

Why We Need IO Streams in Java?

In daily work, we do not enter input into programs manually. Also, the program's result needs to be stored
somewhere for further use.

So, Java IO streams provide input and output streams that help us extract data from files and write the data into
them. Normally, we can create, delete, and edit files using Java.io.

In short, all file manipulation is done using "Java IO streams, " which also handle user input functionality.

Types of Streams in Java

Depending on the types of operations, streams are divided into 2 primary classes.

Input Stream

It is an abstract superclass of the java.io package and is used to read the data from an input source. In other
words, it means reading data from files, using a keyboard, etc. We can create an object of the input stream class
using the new keyword. The input stream class has several types of constructors.

The following code takes the file name as a string, to read the data stored in the file.

InputStream f = new FileInputStream("input.txt");


InputStream Hierarchy

Useful methods of InputStream

1. public abstract int read() throws IOException

The method above returns the data of the next byte in the input stream. The value returned is between 0and 255.
If no byte is read, the code returns -1, indicating the file's end.

2. public int available() throws IOException

The method above returns the number of bytes that can be read from the input stream.

3. public void close() throws IOException

The method above closes the current input stream and releases any associated system resources .

4. public void mark(int readlimit)

It marks the current position in the input stream. The readlimit argument tells the input stream to read that many
bytes before the mark position becomes invalid.

5. public boolean markSupported()

It tells whether a particular input stream supports the mark() and reset() method. It returns true if the particular
input stream supports the mark and reset methods or returns false.

6. public int read(byte[ ] b) throws IOException


The method above reads the bytes from the input stream and stores every byte in the buffer array. It returns the
total number of bytes stored in the buffer array. If there is no byte in the input stream, it returns -1 as the
stream is at the end of the file.

7. public int read(byte[ ] b , int off , len) throws IOException

It reads up to len bytes of data from the input stream and returns the total number of bytes stored in the buffer.
Here, the “off” is the start offset in buffer array b where the data is written, and the “len” represents the
maximum number of bytes to read.

8. public void reset() throws IOException

It repositions the stream to the last called mark position. The reset method does nothing for input stream class
except throwing an exception.

9. public long skip(long n) throws IOException

This method discards n bytes of data from the input stream.

Examples

1. In the below example, we will use FileInputStream class to read the input file: input.txt.

 Create a file input.txt and place it in the same directory as Main.java


 Let us suppose input.txt contains the following content:

Scaler Topics
From InterviewBit

Code:

import java.io.*;

class Main {

public static void main(String[] args) throws IOException {


try {
// loading a file into f variable
FileInputStream f = new FileInputStream("input.txt");

// initializing x to 0
int x = 0;
// while loop untill the end of the file.
while ((x = f.read()) != -1) {
// printing the character
System.out.print((char) x);
}
// closing a file
f.close();
} catch (Exception e) {
// printing exception
System.out.println(e);
}
}
}
Output:

Scaler Topics
From InterviewBit

2. In the below example, we will use BufferedInputStream class to read the file.

Code:

import java.io.*;

class Main {

public static void main(String[] args) throws IOException {


try {
// loading a file into f1 variable using FileInputStream
FileInputStream f1 = new FileInputStream("input.txt");

// loading a file into f2 variable using BufferInputStream


BufferedInputStream f2 = new BufferedInputStream(f1);

// using the available method


System.out.println("Available bytes: " + f2.available());

int x = 0;
// while loop untill the end of the file.
while ((x = f2.read()) != -1) {
// printing the character
System.out.print((char) x);
}
System.out.println();
// closing a file
f2.close();
} catch (Exception e) {
// printing exception
System.out.println(e);
}
}
}

Output:

Available bytes: 31
Scaler Topics
From InterviewBit

3. In the below example, we will use the ByteArrayInputStream class to read the file.

Code:

import java.io.*;

class Main {

public static void main(String[] args) throws IOException {


try {
// loading a file into f1 variable using FileInputStream
FileInputStream f1 = new FileInputStream("input.txt");
int x = 0;
String S = "";
// while loop untill the end of the file.
while ((x = f1.read()) != -1) {
// printing the character
S = S + (char) x;
}
// closing a input stream
f1.close();

// converting string to array of bytes


byte[] b = S.getBytes();
// declaring ByteArrayInputStream
ByteArrayInputStream b1 = new ByteArrayInputStream(b);

x = b1.read();
while (x != -1) {
System.out.print((char) x);
x = b1.read();
}
System.out.println();
// close the input stream
b1.close();
} catch (Exception e) {
// printing exception
System.out.println(e);
}
}
}

Output:

Scaler Topics
From InterviewBit
Output Stream

It is an abstract superclass of the java.io package that writes data to an output resource, which is, in other words,
writing the data into files. We can create an object of the output stream class using the new keyword. The output
stream class has several types of constructors.
OutputStream Hierarchy

Useful methods of OutputStream

1. public void close() throws IOException

This method closes the current output stream and releases any associated system resources. The closed stream
cannot be reopened, and operations cannot be performed on it.

2. public void flush() throws IOException

It flushes the current output stream and forces any buffered output to be written out.

3. Public void write(byte[ ] b) throws IOException

This method writes the b.length bytes from the specified byte array to the output stream.

4. Public void write(byte[ ] b ,int off ,int len) throws IOException

It writes up to len bytes of data for the output stream. Here, the “off” is the start offset in buffer array b, and
the “len” represents the maximum number of bytes to be written in the output stream.

5. Public abstract void write(int b) throws IOException

The method above writes the specific bytes to the output stream. It does not return a value.

Some methods that are inherited from class java.lang.Object. These methods are used for both input stream and
output stream purposes.

Example: clone, equals, finalise, getclass, hashCode, notify, notifyAll, toString, wait.
Examples

1. In the example below, we will use the FileOutputStream class to read the file.

Code:

import java.io.*;

class Main {

public static void main(String[] args) throws IOException {


try {
// loading a file into f variable
FileOutputStream f = new FileOutputStream("output.txt");

String s = "Scaler Topics";


char arr[] = s.toCharArray();
// initializing x to 0
int x = 0;
// while loop untill the end of the string.
while (x < s.length()) {
// writing a byte into "output.txt" file
f.write(arr[x++]);
}
// closing a file
f.close();
} catch (Exception e) {
// printing exception
System.out.println(e);
}
}
}

Output:

The output.txt file will contain the following text:

Scaler Topics

2. In the example below, we will use the BufferedOutputStream class to read the file.

Code:

import java.io.*;

class Main {

public static void main(String[] args) throws IOException {


try {
// loading a file into f variable
FileOutputStream f1 = new FileOutputStream("output.txt");

// declaring a f1 as BufferedOutputStream
BufferedOutputStream f2 = new BufferedOutputStream(f1);

String s = "Scaler Topics";


char arr[] = s.toCharArray();
// initializing x to 0
int x = 0;
// while loop untill the end of the string.
while (x < s.length()) {
// writing a byte into "output.txt" file
f2.write(arr[x++]);
}
// closing a file
f2.close();
f1.close();
} catch (Exception e) {
// printing exception
System.out.println(e);
}
}
}

Output:

The output.txt file will contain the following text:

Scaler Topics

3. In the below example, we will use the ByteArrayOutputStream class to read the file.

Code:

import java.io.*;

class Main {

public static void main(String[] args) throws IOException {


try {
// loading a file into f variable
FileOutputStream f = new FileOutputStream("output.txt");

String s = "Scaler Topics";

// declaring ByteArrayOutputStream
ByteArrayOutputStream b1 = new ByteArrayOutputStream();

// writing a data to "output.txt" file


b1.write(s.getBytes());
b1.writeTo(f);

// closing a file
b1.close();
} catch (Exception e) {
// printing exception
System.out.println(e);
}
}
}

Output:

The output.txt`` file will contain the following text:


Scaler Topics

1.2 Filter and pipe streams

In Java, streams provide a powerful way to process data in a declarative manner, especially when dealing with
collections or I/O operations. Two important concepts related to streams are filtering and piping:

Filtering Streams

Filtering streams allows you to selectively process elements based on certain criteria. In Java, you can filter
streams using the filter() method, which takes a Predicate as an argument. Here’s how it works:

1. Predicate Interface: This functional interface defines a single method test(T t)


that returns a boolean. It's commonly used for filtering elements in streams.
2. Filtering Syntax: In a stream pipeline, you typically call filter() after obtaining a
stream from a source (like a collection or another stream). For example:

java
Copy code
List<String> names = Arrays.asList("Alice", "Bob", "Charlie",
"David", "Emma");

// Filter names starting with 'A'


names.stream()
.filter(name -> name.startsWith("A"))
.forEach(System.out::println); // Output: Alice

In this example, .filter(name -> name.startsWith("A")) filters out names that


do not start with the letter 'A'.

Piping Streams

Piping streams, also known as chaining operations, involves connecting multiple stream operations together to
form a pipeline. Each operation in the pipeline processes the data and passes it to the next operation. This allows
for a concise and readable way to transform data sequentially.

1. Chaining Operations: Operations like map(), filter(), sorted(), distinct(),


limit(), etc., can be chained together. For example:

java
Copy code
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

// Filter even numbers, then map to their squares, and then print
numbers.stream()
.filter(num -> num % 2 == 0)
.map(num -> num * num)
.forEach(System.out::println); // Output: 4, 16, 36, 64, 100

Here, .filter(num -> num % 2 == 0) filters out odd numbers, .map(num -> num
* num) squares the even numbers, and .forEach(System.out::println) prints
each squared number.
2. Intermediate and Terminal Operations: Stream operations are categorized as
intermediate (like filter, map, sorted) and terminal (like forEach, collect,
reduce). Intermediate operations return a new stream, allowing for chaining, while
terminal operations produce a result or side-effect.

Example of Using Filter and Pipe in I/O Stream

When dealing with I/O streams, you can apply similar principles to filter and process data:

java
Copy code
import java.io.*;
import java.util.stream.*;

public class FileStreamExample {


public static void main(String[] args) throws IOException {
// Read lines from a file, filter those containing "Java", and
print them
BufferedReader reader = new BufferedReader(new
FileReader("data.txt"));

reader.lines()
.filter(line -> line.contains("Java"))
.forEach(System.out::println);

reader.close();
}
}

In this example, .filter(line -> line.contains("Java")) filters lines from the file
data.txt that contain the substring "Java".

Key Points

 Predicate: Used for filtering elements in streams based on conditions.


 Chaining Operations: Allows for a concise and readable way to process data sequentially.
 Stream Types: Streams can be applied to collections (Collection.stream()) or I/O
(BufferedReader.lines()).

By leveraging filtering and piping in Java streams, you can efficiently process and manipulate data, whether it's
from collections, files, or other sources. This functional approach enhances readability and maintainability of
code.

1.3 Reflection

Java Reflection is a process of examining or modifying the run time behavior of a class at run time.

The java.lang.Class class provides many methods that can be used to get metadata, examine and change the run
time behavior of a class.

The java.lang and java.lang.reflect packages provide classes for java reflection.
Where it is used

The Reflection API is mainly used

 IDE (Integrated Development Environment) e.g., Eclipse, MyEclipse, NetBeans etc.


 Debugger
 Test Tools etc.

java.lang.Class class

The java.lang.Class class performs mainly two tasks:

 provides methods to get the metadata of a class at run time.


 provides methods to examine and change the run time behavior of a class.

Commonly used methods of Class class:


Method Description

1) public String getName() returns the class


name

2) public static Class forName(String loads the class


className)throws ClassNotFoundException and returns the
reference of Class
class.

3) public Object newInstance()throws creates new


InstantiationException,IllegalAccessException instance.

4) public boolean isInterface() checks if it is


interface.

5) public boolean isArray() checks if it is


array.

6) public boolean isPrimitive() checks if it is


primitive.

7) public Class getSuperclass() returns the


superclass class
reference.

8) public Field[] getDeclaredFields()throws returns the total


SecurityException number of fields
of this class.

9) public Method[] getDeclaredMethods()throws returns the total


SecurityException number of
methods of this
class.

10) public Constructor[] returns the total


getDeclaredConstructors()throws SecurityException number of
constructors of
this class.

11) public Method getDeclaredMethod(String returns the


name,Class[] parameterTypes)throws method class
NoSuchMethodException,SecurityException instance.
How to get the object of Class class?

There are 3 ways to get the instance of Class class. They are as follows:

 forName() method of Class class


 getClass() method of Object class
 the .class syntax

1) forName() method of Class class

 is used to load the class dynamically.


 returns the instance of Class class.
 It should be used if you know the fully qualified name of class.This cannot be used for
primitive types.

Let's see the simple example of forName() method.

FileName: Test.java

1. class Simple{}
2.
3. public class Test{
4. public static void main(String args[]) throws Exception {
5. Class c=Class.forName("Simple");
6. System.out.println(c.getName());
7. }
8. }

Output:

Simple
2) getClass() method of Object class

It returns the instance of Class class. It should be used if you know the type. Moreover, it can be used with
primitives.

FileName: Test.java

1. class Simple{}
2.
3. class Test{
4. void printName(Object obj){
5. Class c=obj.getClass();
6. System.out.println(c.getName());
7. }
8. public static void main(String args[]){
9. Simple s=new Simple();
10.
11. Test t=new Test();
12. t.printName(s);
13. }
14.}
15.

Output:

Simple
3) The .class syntax

If a type is available, but there is no instance, then it is possible to obtain a Class by appending ".class" to the
name of the type. It can be used for primitive data types also.

FileName: Test.java

1. class Test{
2. public static void main(String args[]){
3. Class c = boolean.class;
4. System.out.println(c.getName());
5.
6. Class c2 = Test.class;
7. System.out.println(c2.getName());
8. }
9. }

Output:

boolean
Test
Determining the class object

The following methods of Class class are used to determine the class object:

1) public boolean isInterface(): determines if the specified Class object represents an


interface type.

2) public boolean isArray(): determines if this Class object represents an array class.

3) public boolean isPrimitive(): determines if the specified Class object represents a


primitive type.
Let's see the simple example of reflection API to determine the object type.

FileName: Test.java

1. class Simple{}
2. interface My{}
3.
4. class Test{
5. public static void main(String args[]){
6. try{
7. Class c=Class.forName("Simple");
8. System.out.println(c.isInterface());
9.
10. Class c2=Class.forName("My");
11. System.out.println(c2.isInterface());
12.
13. }catch(Exception e){System.out.println(e);}
14.
15. }
16.}

Output:

false
true
Pros and Cons of Reflection

Java reflection should always be used with caution. While the reflection provides a lot of advantages, it has
some disadvantages too. Let's discuss the advantages first.

Pros: Inspection of interfaces, classes, methods, and fields during runtime is possible using reflection, even
without using their names during the compile time. It is also possible to call methods, instantiate a clear or to set
the value of fields using reflection. It helps in the creation of Visual Development Environments and class
browsers which provides aid to the developers to write the correct code.

Cons: Using reflection, one can break the principles of encapsulation. It is possible to access the private
methods and fields of a class using reflection. Thus, reflection may leak important data to the outside world,
which is dangerous. For example, if one access the private members of a class and sets null value to it, then the
other user of the same class can get the NullReferenceException, and this behaviour is not expected.

Another demerit is the overhead in performance. Since the types in reflection are resolved dynamically, JVM
(Java Virtual Machine) optimization cannot take place. Therefore, the operations performed by reflections are
usually slow.

1.4 Threading
In Java, threads are a fundamental part of concurrent programming, allowing programs to execute multiple tasks
concurrently.
Typically, we can define threads as a subprocess with lightweight with the smallest unit of processes and also
has separate paths of execution. The main advantage of multiple threads is efficiency (allowing multiple things
at the same time. For example, in MS Word. one thread automatically formats the document while another
thread is taking user input. Another advantage is quick response, if we use multiple threads in a process and if a
thread gets stuck due to lack of resources or an exception, the other threads can continue to execution, allowing
the process (which represents an application) to continue to be responsive.

As we can observe in, the above diagram a thread runs inside the process and there will be context-based
switching between threads there can be multiple processes running in OS, and each process again can have
multiple threads running simultaneously. The Multithreading concept is popularly applied in games,
animation…etc.

The Concept Of Multitasking


To help users Operating System accommodates users the privilege of multitasking, where users can perform
multiple actions simultaneously on the machine. This Multitasking can be enabled in two ways:

1. Process-Based Multitasking
2. Thread-Based Multitasking

1. Process-Based Multitasking (Multiprocessing)

In this type of Multitasking, processes are heavyweight and each process was allocated by a separate memory
area. And as the process is heavyweight the cost of communication between processes is high and it takes a long
time for switching between processes as it involves actions such as loading, saving in registers, updating maps,
lists, etc.

2. Thread-Based Multitasking
As we discussed above Threads are provided with lightweight nature and share the same address space, and the
cost of communication between threads is also low.

Why Threads are used?


Now, we can understand why threads are being used as they had the advantage of being lightweight and can
provide communication between multiple threads at a Low Cost contributing to effective multi-tasking within a
shared memory environment.

Life Cycle Of Thread


There are different states Thread transfers into during its lifetime, let us know about those states in the following
lines: in its lifetime, a thread undergoes the following states, namely:

1. New State
2. Active State
3. Waiting/Blocked State
4. Timed Waiting State
5. Terminated State
We can see the working of different states in a Thread in the above Diagram, let us know in detail each and
every state:

1. New State

By default, a Thread will be in a new state, in this state, code has not yet been run and the execution process is
not yet initiated.

2. Active State

A Thread that is a new state by default gets transferred to Active state when it invokes the start() method, his
Active state contains two sub-states namely:

Runnable State: In This State, The Thread is ready to run at any given time and it’s the job of the Thread
Scheduler to provide the thread time for the runnable state preserved threads. A program that has obtained
Multithreading shares slices of time intervals which are shared between threads hence, these threads run for
some short span of time and wait in the runnable state to get their schedules slice of a time interval.

Running State: When The Thread Receives CPU allocated by Thread Scheduler, it transfers from the
“Runnable” state to the “Running” state. and after the expiry of its given time slice session, it again moves back
to the “Runnable” state and waits for its next time slice.

3. Waiting/Blocked State

If a Thread is inactive but on a temporary time, then either it is a waiting or blocked state, for example, if there
are two threads, T1 and T2 where T1 needs to communicate to the camera and the other thread T2 already using
a camera to scan then T1 waits until T2 Thread completes its work, at this state T1 is parked in waiting for the
state, and in another scenario, the user called two Threads T2 and T3 with the same functionality and both had
same time slice given by Thread Scheduler then both Threads T1, T2 is in a blocked state. When there are
multiple threads parked in a Blocked/Waiting state Thread Scheduler clears Queue by rejecting unwanted
Threads and allocating CPU on a priority basis.

4. Timed Waiting State

Sometimes the longer duration of waiting for threads causes starvation, if we take an example like there are two
threads T1, T2 waiting for CPU and T1 is undergoing a Critical Coding operation and if it does not exist the
CPU until its operation gets executed then T2 will be exposed to longer waiting with undetermined certainty, In
order to avoid this starvation situation, we had Timed Waiting for the state to avoid that kind of scenario as in
Timed Waiting, each thread has a time period for which sleep() method is invoked and after the time expires the
Threads starts executing its task.

5. Terminated State
A thread will be in Terminated State, due to the below reasons:

Termination is achieved by a Thread when it finishes its task Normally.

Sometimes Threads may be terminated due to unusual events like segmentation faults, exceptions…etc. and
such kind of Termination can be called Abnormal Termination.

A terminated Thread means it is dead and no longer available.

What is Main Thread?

As we are familiar, we create Main Method in each and every Java Program, which acts as an entry point for the
code to get executed by JVM, Similarly in this Multithreading Concept, Each Program has one Main Thread
which was provided by default by JVM, hence whenever a program is being created in java, JVM provides the
Main Thread for its Execution.

How to Create Threads using Java Programming Language?

We can create Threads in java using two ways, namely :

 Extending Thread Class


 Implementing a Runnable interface

1. By Extending Thread Class

We can run Threads in Java by using Thread Class, which provides constructors and methods for creating and
performing operations on a Thread, which extends a Thread class that can implement Runnable Interface. We
use the following constructors for creating the Thread:

 Thread
 Thread(Runnable r)
 Thread(String name)
 Thread(Runnable r, String name)

Sample code to create Threads by Extending Thread Class:


Java
import java.io.*;
import java.util.*;

public class GFG extends Thread {


// initiated run method for Thread
public void run()
{
System.out.println("Thread Started Running...");
}
public static void main(String[] args)
{
GFG g1 = new GFG();

// Invoking Thread using start() method


g1.start();
}
}

Output
Thread Started Running...
Sample code to create Thread by using Runnable Interface:
Java
import java.io.*;
import java.util.*;

public class GFG implements Runnable {


// method to start Thread
public void run()
{
System.out.println(
"Thread is Running Successfully");
}

public static void main(String[] args)


{
GFG g1 = new GFG();
// initializing Thread Object
Thread t1 = new Thread(g1);
t1.start();
}
}

Output
Thread is Running Successfully
Sample Code to create Thread in Java using Thread(String
name):
Java
import java.io.*;
import java.util.*;

public class GFG {


public static void main(String args[])
{
// Thread object created
// and initiated with data
Thread t = new Thread("Hello Geeks!");

// Thread gets started


t.start();

// getting data of
// Thread through String
String s = t.getName();
System.out.println(s);
}
}

Output
Hello Geeks!
Sample Java Code which creates Thread Object by using
Thread(Runnable r, String name):
Java
import java.io.*;
import java.util.*;

public class GFG implements Runnable {


public void run()
{
System.out.println(
"Thread is created and running successfully...");
}
public static void main(String[] args)
{
// aligning GFG Class with
// Runnable interface
Runnable r1 = new GFG();
Thread t1 = new Thread(r1, "My Thread");
// Thread object started
t1.start();
// getting the Thread
// with String Method
String str = t1.getName();
System.out.println(str);
}
}

Output
My Thread
Thread is created and running successfully...

Java Program to explore different Thread States:


Let us see the working of thread states by implementing them on Threads
t1 and t2.
Output:
Java
import java.io.*;
import java.util.*;

class GFG implements Runnable {


public void run()
{
// implementing try-catch Block to set sleep state
// for inactive thread
try {
Thread.sleep(102);
}
catch (InterruptedException i1) {
i1.printStackTrace();
}
System.out.println(
"The state for t1 after it invoked join method() on thread t2"
+ " " + ThreadState.t1.getState());

// implementing try-catch block


try {
Thread.sleep(202);
}
catch (InterruptedException i2) {
i2.printStackTrace();
}
}
}

// creation of ThreadState class


// to implement Runnable interface
public class ThreadState implements Runnable {
public static Thread t1;
public static ThreadState o1;
public static void main(String args[])
{
o1 = new ThreadState();
t1 = new Thread(o1);
System.out.println("post-spanning, state of t1 is"
+ " " + t1.getState());
// lets invoke start() method on t1
t1.start();
// Now,Thread t1 is moved to runnable state
System.out.println(
"post invoking of start() method, state of t1 is"
+ " " + t1.getState());
}
public void run()
{
GFG g1 = new GFG();
Thread t2 = new Thread(g1);
// Thread is created and its in new state.
t2.start();
// Now t2 is moved to runnable state
System.out.println(
"state of t2 Thread, post-calling of start() method is"
+ " " + t2.getState());
// create a try-catch block to set t1 in waiting
// state
try {
Thread.sleep(202);
}
catch (InterruptedException i2) {
i2.printStackTrace();
}
System.out.println(
"State of Thread t2 after invoking to method sleep() is"
+ " " + t2.getState());
try {
t2.join();
System.out.println(
"State of Thread t2 after join() is"
+ " " + t2.getState());
}
catch (InterruptedException i3) {
i3.printStackTrace();
}
System.out.println(
"state of Thread t1 after completing the execution is"
+ " " + t1.getState());
}
}

Output
post-spanning, state of t1 is NEW
post invoking of start() method, state of t1 is RUNNABLE
state of t2 Thread, post-calling of start() method is RUNNABLE
The state for t1 after it invoked join method() on thread t2 TIMED_WAITING
State of Thread t2 after invoking to method sleep() is TIMED_WAITING
State of Thread t2 after join() is TERMINATED
state of Thread t1 after completing the execution is RUNNABLE

1.5 Java Native Interfaces

Java Native Interface (JNI) is a framework that allows Java code running in the Java Virtual Machine (JVM) to
interoperate with applications and libraries written in other languages, particularly in C or C++. JNI enables
Java applications to call native methods and access native libraries, and conversely, allows native code to invoke
Java methods.

Key Concepts in JNI

1. Purpose:
o JNI facilitates integration of Java applications with native code when Java's built-in
capabilities (like platform-specific functionality or performance-critical operations) are
insufficient.

2. Components:
o Java Native Method Interface: This is the set of methods and rules for making native method
calls from Java code.
o JNI API: A set of programming interfaces for Java and native code interaction, including
functions for loading native libraries, calling native methods, and handling data conversion
between Java and native types.
3. Native Method Declaration:
o In Java, native methods are declared with the native keyword and are implemented in
native code (C/C++).

java
Copy code
public class NativeExample {
// Native method declaration
public native void nativeMethod();

// Load native library containing nativeMethod()


static {
System.loadLibrary("nativeLibraryName");
}

public static void main(String[] args) {


NativeExample example = new NativeExample();
example.nativeMethod(); // Call native method
}
}

4. Implementing Native Methods:


o Native methods are implemented in C/C++ and compiled into shared libraries (DLL on
Windows, shared object files on Unix-like systems).

c
Copy code
#include <jni.h>
#include "NativeExample.h" // Generated header from javah tool

JNIEXPORT void JNICALL Java_NativeExample_nativeMethod(JNIEnv *env,


jobject obj) {
// Native method implementation
printf("Native method called\n");
}

5. JNI Environment:
o The JNIEnv (Java Native Interface Environment) provides methods for accessing JVM
functionality, manipulating Java objects, and performing type conversions.

6. Accessing Java Objects:


o JNI provides functions (GetMethodID, CallMethod, etc.) for accessing and manipulating
Java objects and invoking Java methods from native code.

Steps to Use JNI

1. Write Java Code:

o Declare native methods using the native keyword.


o Create a class that loads the native library containing the native method(s).

2. Generate JNI Headers:

o Use the javah tool to generate C/C++ header files (*.h) for the native methods declared in
Java.
sh
Copy code
javah -jni NativeExample

3. Implement Native Code:

o Write C/C++ code that implements the native methods as declared in the generated header
file.

4. Compile Native Code:

o Compile the native code into a shared library compatible with your platform.

5. Load Native Library:

o Use System.loadLibrary("libraryName") to load the native library containing the native


methods.

6. Run Java Application:

o Execute your Java application, which will call the native methods as needed.

Advantages and Considerations

o Performance: JNI allows performance-critical operations to be implemented in native code,


leveraging the efficiency of compiled languages like C/C++.
o Integration: JNI facilitates integration with existing native libraries and system APIs.
o Complexity and Maintenance: JNI introduces complexity and potential for memory leaks or
crashes due to incorrect native code.
o Platform Dependence: Care must be taken to ensure compatibility across different platforms
when working with native code.

JNI is a powerful tool for extending Java applications to interact with native libraries and access platform-
specific capabilities. It is commonly used in areas such as multimedia processing, hardware interactions, and
performance optimization where direct native access is necessary. However, due to its complexity and potential
pitfalls, it requires careful design and testing to ensure robustness and stability in Java applications.

Example of Using JNI

Let's walk through a simple example to demonstrate how JNI works:

1. Java Code (NativeExample.java):

java
Copy code
public class NativeExample {
// Native method declaration
public native void nativeMethod();

// Load native library containing nativeMethod()


static {
System.loadLibrary("nativeLibraryName");
}
public static void main(String[] args) {
NativeExample example = new NativeExample();
example.nativeMethod(); // Call native method
}
}

o Explanation:
 native void nativeMethod(): This declares a native method that is implemented in
native code (C/C++).
 static { System.loadLibrary("nativeLibraryName"); }: This static block loads the
native library (nativeLibraryName) that contains the implementation of
nativeMethod().

2. Generate JNI Header:


o Use the javah tool to generate the JNI header file (NativeExample.h) from the
Java class NativeExample.

sh
Copy code
javah -jni NativeExample

o This generates a header file that declares the native method signature in C/C++.

3. Implement Native Code (NativeExample.c):


o Implement the native method (nativeMethod) in C/C++.

c
Copy code
#include <jni.h>
#include "NativeExample.h" // Generated JNI header

JNIEXPORT void JNICALL Java_NativeExample_nativeMethod(JNIEnv *env,


jobject obj) {
// Native method implementation
printf("Native method called\n");
}

o Explanation:
 JNIEXPORT void JNICALL
Java_NativeExample_nativeMethod(JNIEnv *env, jobject
obj): This is the C function that implements the native method declared in
NativeExample.java.
 printf("Native method called\n");: This prints a message
indicating that the native method has been called.

4. Compile Native Code:


o Compile the C code into a shared library (libnativeLibraryName.so on Unix-like
systems or nativeLibraryName.dll on Windows).

sh
Copy code
gcc -shared -o libnativeLibraryName.so -I$JAVA_HOME/include -
I$JAVA_HOME/include/linux NativeExample.c

o Replace $JAVA_HOME with your Java installation directory.


5. Run Java Application:
o Run the Java application (NativeExample) that calls the native method.

sh
Copy code
java NativeExample

o Output:

sql
Copy code
Native method called
Explanation of Steps:

 Step 1: In Java, NativeExample.java declares a native method nativeMethod() and loads


the native library (nativeLibraryName) that contains its implementation.
 Step 2: Using javah, a JNI header file (NativeExample.h) is generated from
NativeExample.java. This header file is used by the native code to implement the native method.
 Step 3: In NativeExample.c, the native method nativeMethod() is implemented. This C code
includes the necessary JNI headers (jni.h and NativeExample.h).
 Step 4: The C code (NativeExample.c) is compiled into a shared library
(libnativeLibraryName.so or nativeLibraryName.dll) using a C compiler (gcc).
 Step 5: Finally, the Java application (NativeExample) is executed, which loads the native library
and calls the native method nativeMethod(). The output shows the message printed by the native
method.

Advantages and Considerations:

 Performance: JNI allows Java applications to leverage the performance of native code for tasks
requiring low-level access or high computational intensity.
 Integration: JNI facilitates seamless integration with existing native libraries and system-level APIs.
 Complexity: JNI introduces complexity and requires careful management of memory and resource
handling to avoid leaks and crashes.

JNI provides Java developers with a powerful mechanism to extend their applications to interact with native
code, enabling them to integrate with platform-specific functionality and existing libraries written in languages
like C or C++. However, JNI should be used judiciously due to its complexity and potential pitfalls related to
memory management and platform dependencies.

1.6 Swings in Java

Java Swing tutorial is a part of Java Foundation Classes (JFC) that is used to create window-based
applications. It is built on the top of AWT (Abstract Windowing Toolkit) API and entirely written in java.

Unlike AWT, Java Swing provides platform-independent and lightweight components.

The javax.swing package provides classes for java swing API such as JButton, JTextField, JTextArea,
JRadioButton, JCheckbox, JMenu, JColorChooser etc.

Difference between AWT and Swing

There are many differences between java awt and swing that are given
below
No Java AWT Java Swing
.

1) AWT components are platform-dependent. Java swing components are platform-


independent.

2) AWT components are heavyweight. Swing components are lightweight.

3) AWT doesn't support pluggable look and feel. Swing supports pluggable look and feel.

4) AWT provides less components than Swing. Swing provides more powerful
components such as tables, lists, scrollpanes,
colorchooser, tabbedpane etc.

5) AWT doesn't follows MVC(Model View Controller) where model Swing follows MVC.
represents data, view represents presentation and controller acts as an
interface between model and view.

What is JFC

The Java Foundation Classes (JFC) are a set of GUI components which simplify the development of desktop
applications.

Hierarchy of Java Swing classes

The hierarchy of java swing API is given below.


Commonly used Methods of Component class

The methods of Component class are widely used in java swing that are
given below.

Method Description

public void add(Component c) add a component on another component.

public void setSize(int width,int sets size of the component.


height)

public void sets the layout manager for the component.


setLayout(LayoutManager m)

public void setVisible(boolean sets the visibility of the component. It is by


b) default false.

Java Swing Examples


There are two ways to create a frame:
o By creating the object of Frame class (association)
o By extending Frame class (inheritance)
We can write the code of swing inside the main(), constructor or any other method.
Simple Java Swing Example

Let's see a simple swing example where we are creating one button and adding it on the JFrame object inside the main()
method.

File: FirstSwingExample.java

import javax.swing.*;
public class FirstSwingExample {
public static void main(String[] args) {
JFrame f=new JFrame();//creating instance of JFrame

JButton b=new JButton("click");//creating instance of JButton


b.setBounds(130,100,100, 40);//x axis, y axis, width, height

f.add(b);//adding button in JFrame

f.setSize(400,500);//400 width and 500 height


f.setLayout(null);//using no layout managers
f.setVisible(true);//making the frame visible
}
}

Example of Swing by Association inside constructor


We can also write all the codes of creating JFrame, JButton and method call inside the java constructor.

File: Simple.java

import javax.swing.*;
public class Simple {
JFrame f;
Simple(){
f=new JFrame();//creating instance of JFrame

JButton b=new JButton("click");//creating instance of JButton


b.setBounds(130,100,100, 40);

f.add(b);//adding button in JFrame


f.setSize(400,500);//400 width and 500 height
f.setLayout(null);//using no layout managers
f.setVisible(true);//making the frame visible
}

public static void main(String[] args) {


new Simple();
}
}

The setBounds(int xaxis, int yaxis, int width, int height)is used in the above example that sets the position of the
button.

Simple example of Swing by inheritance

We can also inherit the JFrame class, so there is no need to create the instance of JFrame class explicitly.

File: Simple2.java

import javax.swing.*;
public class Simple2 extends JFrame{//inheriting JFrame
JFrame f;
Simple2(){
JButton b=new JButton("click");//create button
b.setBounds(130,100,100, 40);

add(b);//adding button on frame


setSize(400,500);
setLayout(null);
setVisible(true);
}
public static void main(String[] args) {
new Simple2();
}}

1.7 Introduction to Collection Framework

The Java Collection Framework provides a unified architecture for representing and manipulating collections of
objects. It includes interfaces, implementations, and algorithms that allow Java developers to work efficiently
with collections of objects. Here’s an introduction to the Java Collection Framework:

Key Components of the Collection Framework

1. Interfaces:
o Collection: Represents a group of objects, including lists, sets, and queues. Examples include List, Set,
and Queue.
o List: Ordered collection (sequence) that allows duplicate elements. Examples include ArrayList,
LinkedList, Vector.
o Set: Collection that does not allow duplicate elements. Examples include HashSet, LinkedHashSet,
TreeSet.
o Queue: Collection used for holding elements prior to processing. Examples include PriorityQueue,
LinkedList.

2. Implementations:
o Concrete implementations of collection interfaces that provide different behavior and performance
characteristics.
o Examples: ArrayList, LinkedList, HashSet, TreeMap, etc.
3. Algorithms:
o Utility methods provided by Collections class for performing operations on collections such as sorting,
searching, shuffling, etc.
o Examples: sort(), binarySearch(), shuffle(), reverse(), etc.

Benefits of Using the Collection Framework

o Uniformity: Provides a unified architecture for working with collections, making it easier to learn and
use different data structures.
o Efficiency: Offers optimized implementations of common data structures, reducing the need for custom
implementations.
o Flexibility: Supports a wide range of data structures and algorithms, allowing developers to choose the
most appropriate collection for their needs.

Example Usage of Collection Framework

Here’s a simple example demonstrating the use of ArrayList and HashSet from the Java Collection Framework:

java
Copy code
import java.util.*;

public class CollectionExample {


public static void main(String[] args) {
// Example of using ArrayList
List<String> list = new ArrayList<>();
list.add("Java");
list.add("Python");
list.add("C++");

System.out.println("Elements in ArrayList:");
for (String lang : list) {
System.out.println(lang);
}

// Example of using HashSet


Set<String> set = new HashSet<>();
set.add("Apple");
set.add("Banana");
set.add("Apple"); // Duplicate element

System.out.println("\nElements in HashSet:");
for (String fruit : set) {
System.out.println(fruit);
}
}
}
Explanation:

 ArrayList: Stores elements in an ordered sequence and allows duplicate elements.


 HashSet: Stores elements in an unordered collection and does not allow duplicate elements.

Common Operations

 Adding Elements: add()


 Removing Elements: remove()
 Iterating through Elements: Using enhanced for loop or iterators (Iterator)
 Sorting: Using Collections.sort() for lists
 Searching: Using contains() for sets
Considerations

 Thread Safety: Most collection implementations are not synchronized by default. Use
Collections.synchronizedXXX() methods or concurrent collections for thread-safe
operations.
 Generics: The use of generics ensures type safety and avoids casting issues (List<String>,
Set<Integer>, etc.).

1.8 Enumerations Stack and Queue


Enumerations
A Java enumeration is a class type. Although we don’t need to instantiate an enum using new, it has the same capabilities as
other classes. This fact makes Java enumeration a very powerful tool. Just like classes, you can give them constructors, add
instance variables and methods, and even implement interfaces.

Enum Example:

The 4 suits in a deck of playing cards may be 4 enumerators named Club, Diamond, Heart, and Spade, belonging to an
enumerated type named Suit. Other examples include natural enumerated types (like the planets, days of the week, colors,
directions, etc.).

One thing to keep in mind is that, unlike classes, enumerations neither inherit other classes nor can get extended(i.e become
superclass). We can also add variables, methods, and constructors to it. The main objective of an enum is to define our own
data types(Enumerated Data Types).

Declaration of enum in Java


Enum declaration can be done outside a Class or inside a Class but not inside a Method.

1. Declaration outside the class


Java

// A simple enum example where enum is declared


// outside any class (Note enum keyword instead of
// class keyword)

enum Color {
RED,
GREEN,
BLUE;
}

public class Test {


// Driver method
public static void main(String[] args)
{
Color c1 = Color.RED;
System.out.println(c1);
}
}
Output
RED

2. Declaration inside a class


Java

// enum declaration inside a class.

public class Test {


enum Color {
RED,
GREEN,
BLUE;
}

// Driver method
public static void main(String[] args)
{
Color c1 = Color.RED;
System.out.println(c1);
}
}
Output
RED

The first line inside the enum should be a list of constants and then other things like methods, variables, and constructors.

According to Java naming conventions, it is recommended that we name constant with all capital letters

Properties of Enum in Java


There are certain properties followed by Enum as mentioned below:

 Every enum is internally implemented by using Class.


 Every enum constant represents an object of type enum.
 Enum type can be passed as an argument to switch statements.
 Every enum constant is always implicitly public static final. Since it is static, we can access it by using the enum
Name. Since it is final, we can’t create child enums.
 We can declare the main() method inside the enum. Hence we can invoke the enum directly from the Command
Prompt.

Below is the implementation of the above properties:

Java

// A Java program to demonstrate working on enum


// in switch case (Filename Test. Java)

import java.util.Scanner;

// An Enum class
enum Day {
SUNDAY,
MONDAY,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY;
}

// Driver class that contains an object of "day" and


// main().
public class Test {
Day day;

// Constructor
public Test(Day day) { this.day = day; }

// Prints a line about Day using switch


public void dayIsLike()
{
switch (day) {
case MONDAY:
System.out.println("Mondays are bad.");
break;
case FRIDAY:
System.out.println("Fridays are better.");
break;
case SATURDAY:
case SUNDAY:
System.out.println("Weekends are best.");
break;
default:
System.out.println("Midweek days are so-so.");
break;
}
}
// Driver method
public static void main(String[] args)
{
String str = "MONDAY";
Test t1 = new Test(Day.valueOf(str));
t1.dayIsLike();
}
}
Output
Mondays are bad.

Java Enum Programs


1. Main Function Inside Enum
We can declare a main function inside an enum as we can invoke the enum directly from the Command Prompt.
Below is the implementation of the above property:

Java

// A Java program to demonstrate that we can have


// main() inside enum class.

enum Color {
RED,
GREEN,
BLUE;

// Driver method
public static void main(String[] args)
{
Color c1 = Color.RED;
System.out.println(c1);
}
}
Output
RED
2. Loop through Enum

We can iterate over the Enum using values( ) and loop. values() function returns an array of Enum values as constants using
which we can iterate over the values.

Below is the implementation of the loop through Enum:

Java

// Java Program to Print all the values


// inside the enum using for loop
import java.io.*;

// Enum Declared
enum Color {
RED,
GREEN,
BLUE;
}

// Driver Class
class GFG {

// Main Function
public static void main(String[] args)
{
// Iterating over all the values in
// enum using for loop
for (Color var_1 : Color.values()) {
System.out.println(var_1);
}
}
}
Output
RED
GREEN
BLUE

3. Enum in a Switch Statement


Java

// Java Program to implement


// Enum in a Switch Statement
import java.io.*;

// Driver Class
class GFG {
// Enum Declared
enum Color {
RED,
GREEN,
BLUE,
Yellow;
}

// Main Function
public static void main(String[] args)
{
Color var_1=Color.Yellow;

// Switch case with Enum


switch(var_1){
case RED:
System.out.println("Red color observed");
break;
case GREEN:
System.out.println("Green color observed");
break;
case BLUE:
System.out.println("Blue color observed");
default:
System.out.println("Other color observed");
}
}
}
Output
Other color observed

Enum and Inheritance

All enums implicitly extend java.lang.Enum class. As a class can only extend one parent in Java, an enum cannot extend
anything else.

toString() method is overridden in java.lang.Enum class, which returns enum constant name.

enum can implement many interfaces.

Enum and Constructor

Enum can contain a constructor and it is executed separately for each enum constant at the time of the enum class loading.

We can’t create enum objects explicitly and hence we can’t invoke the enum constructor directly.

Enum and Methods

Enum can contain both concrete methods and abstract methods. If an enum class has an abstract method, then each instance
of the enum class must implement it.

Java

// Java program to demonstrate that enums can have


// constructor and concrete methods.

// An enum (Note enum keyword inplace of class keyword)


enum Color {
RED,
GREEN,
BLUE;
// enum constructor called separately for each
// constant
private Color()
{
System.out.println("Constructor called for : "
+ this.toString());
}

public void colorInfo()


{
System.out.println("Universal Color");
}
}

public class Test {


// Driver method
public static void main(String[] args)
{
Color c1 = Color.RED;
System.out.println(c1);
c1.colorInfo();
}
}
Output
Constructor called for : RED
Constructor called for : GREEN
Constructor called for : BLUE
RED
Universal Color

Stack

In Java, Stack is a class that represents a last-in, first-out (LIFO) stack of objects. It extends the Vector class with additional
methods that allow a vector to be treated as a stack. Here's an overview of how Stack works and how you can use it in Java:

Key Characteristics of Stack

1. LIFO Principle:
o Stack follows the Last In, First Out principle, meaning the last element added to the stack is the first one
to be removed.

2. Extends Vector:
o Internally, Stack is implemented as a subclass of Vector, which means it inherits all the methods and
properties of Vector.

3. Methods:
o push(E item): Pushes an item onto the top of the stack.
o pop(): Removes the object at the top of the stack and returns it.
o peek(): Looks at the object at the top of the stack without removing it.
o empty(): Checks if the stack is empty.
o search(Object o): Searches for an object in the stack and returns its position relative to the top of the
stack (1-based index).

Example Usage of Stack

Here's a simple example demonstrating how to use Stack in Java:

java
Copy code
import java.util.Stack;

public class StackExample {


public static void main(String[] args) {
Stack<String> stack = new Stack<>();
// Pushing elements onto the stack
stack.push("Java");
stack.push("Python");
stack.push("C++");

// Popping elements from the stack


String top = stack.pop();
System.out.println("Popped element: " + top); // Outputs "Popped element: C++"

// Peeking at the top element without popping it


String topElement = stack.peek();
System.out.println("Top element: " + topElement); // Outputs "Top element: Python"

// Checking if the stack is empty


boolean isEmpty = stack.empty();
System.out.println("Is stack empty? " + isEmpty); // Outputs "Is stack empty? false"

// Searching for an element in the stack


int position = stack.search("Java");
if (position != -1) {
System.out.println("Position of Java in stack: " + position); // Outputs "Position
of Java in stack: 2"
}
}
}
Explanation:

o push(E item): Adds an element ("Java", "Python", "C++") to the top of the stack.
o pop(): Removes and returns the top element of the stack ("C++").
o peek(): Returns the top element without removing it ("Python").
o empty(): Checks if the stack is empty (false in this case).
o search(Object o): Finds the position of an object ("Java") in the stack (2 in this case).

Benefits of Using Stack

o Simple Interface: Provides straightforward methods (push(), pop(), peek()) for stack operations.
o Versatility: Can be used in various scenarios such as parsing expressions, backtracking algorithms, and
undo mechanisms.
o Extensibility: Being a subclass of Vector, it inherits all vector operations, making it suitable for
scenarios requiring dynamic resizing.

Considerations

o Performance: While Stack provides essential stack operations, for performance-critical


applications, consider using ArrayDeque or LinkedList with proper API constraints (push() for
addFirst(), pop() for removeFirst()).
o Concurrency: Stack is not synchronized. For concurrent access, consider using java.util.concurrent
data structures or synchronizing access externally.

In summary, Stack in Java provides a convenient way to manage elements in a last-in, first-out manner. It's
useful for various programming tasks where LIFO operations are required, offering simplicity and effectiveness
in managing collections of objects.

Queue

In Java, Queue is an interface that represents a collection designed for holding elements prior to processing. It
follows the First-In, First-Out (FIFO) principle, meaning that elements are inserted at the end of the queue and
removed from the beginning. The Queue interface is part of the Java Collections Framework and provides
several implementations for different use cases. Here’s an overview of Queue and its key implementations in
Java:
Queue Interface

1. Methods:

o boolean add(E e): Inserts the specified element into the queue if possible; throws an exception if the
element cannot be added.
o boolean offer(E e): Inserts the specified element into the queue if possible; returns true if successful,
false if the element cannot be added.
o E remove(): Retrieves and removes the head of the queue; throws an exception if the queue is empty.
o E poll(): Retrieves and removes the head of the queue, or returns null if the queue is empty.
o E element(): Retrieves, but does not remove, the head of the queue; throws an exception if the queue is
empty.
o E peek(): Retrieves, but does not remove, the head of the queue, or returns null if the queue is empty.

2. Implementations:

o LinkedList: Implements Queue interface and provides FIFO queue operations. It also allows null
elements and is not synchronized.
o PriorityQueue: Provides a priority queue based on the natural ordering or a Comparator. Elements are
ordered based on their natural ordering or the custom Comparator.

Example Usage of Queue

Here’s a simple example demonstrating how to use Queue implementations in Java:

Using LinkedList as a Queue:


java
Copy code
import java.util.LinkedList;
import java.util.Queue;

public class QueueExample {


public static void main(String[] args) {
Queue<String> queue = new LinkedList<>();

// Adding elements to the queue


queue.add("Java");
queue.add("Python");
queue.add("C++");

// Removing elements from the queue


String removedElement = queue.remove();
System.out.println("Removed element: " + removedElement); // Outputs "Removed element:
Java"

// Peeking at the first element without removing it


String peekedElement = queue.peek();
System.out.println("Peeked element: " + peekedElement); // Outputs "Peeked element:
Python"

// Iterating through the queue


System.out.println("\nElements in queue:");
for (String element : queue) {
System.out.println(element);
}
}
}
Using PriorityQueue:
java
Copy code
import java.util.PriorityQueue;
import java.util.Queue;

public class PriorityQueueExample {


public static void main(String[] args) {
Queue<Integer> priorityQueue = new PriorityQueue<>();
// Adding elements to the priority queue
priorityQueue.offer(10);
priorityQueue.offer(5);
priorityQueue.offer(20);

// Removing elements from the priority queue


int removedElement = priorityQueue.poll();
System.out.println("Removed element: " + removedElement); // Outputs "Removed element:
5"

// Peeking at the first element without removing it


int peekedElement = priorityQueue.peek();
System.out.println("Peeked element: " + peekedElement); // Outputs "Peeked element:
10"

// Iterating through the priority queue


System.out.println("\nElements in priority queue:");
while (!priorityQueue.isEmpty()) {
System.out.println(priorityQueue.poll());
}
}
}
Explanation:

o add(E e) and offer(E e): Both methods add an element to the queue. add() throws an exception if the
element cannot be added (e.g., if the capacity is full in a bounded queue), while offer() returns false if the
element cannot be added.
o remove() and poll(): Both methods remove and return the head of the queue. remove() throws an
exception if the queue is empty, while poll() returns null if the queue is empty.
o element() and peek(): Both methods retrieve the head of the queue without removing it. element()
throws an exception if the queue is empty, while peek() returns null if the queue is empty.

Benefits of Using Queue

o Efficiency: Provides efficient FIFO operations (add, remove, peek, etc.) for managing elements.
o Flexibility: Offers different implementations (LinkedList, PriorityQueue) for various requirements
(simple FIFO or priority-based processing).
o Thread Safety: Certain implementations (LinkedBlockingQueue from java.util.concurrent) provide
thread-safe operations for concurrent environments.

Considerations

o Null Elements: Some implementations (PriorityQueue) do not allow null elements.


o Concurrency: For concurrent applications, consider using thread-safe implementations like
LinkedBlockingQueue from java.util.concurrent.

In summary, Queue in Java provides a versatile way to manage collections of elements with FIFO or priority-
based processing requirements. It's widely used in applications requiring orderly processing of elements, such as
task scheduling, breadth-first search algorithms, and message queues.

1.9 Sets, Maps and Utility Classes


Sets

In Java, a Set is a collection that does not allow duplicate elements. It models the mathematical set abstraction
and provides methods for adding, removing, and checking the presence of elements. Java provides several
implementations of the Set interface in the Java Collections Framework, each with its own characteristics and
use cases. Here's an overview of sets in Java and some of the common implementations:
Set Interface

The Set interface extends the Collection interface and adds the following features:

1. No Duplicate Elements:

o A Set does not allow duplicate elements. If you attempt to add an element that already exists in the set,
the add operation will return false.

2. Unordered Collection:

o Unlike a List, which maintains the order of elements based on insertion sequence, a Set does not
guarantee any specific order of elements.

3. Common Methods:

o add(E e): Adds the specified element to the set if it is not already present.
o remove(Object o): Removes the specified element from the set if it is present.
o contains(Object o): Returns true if the set contains the specified element.
o size(): Returns the number of elements in the set.
o isEmpty(): Returns true if the set contains no elements.

Common Implementations of Set Interface

Java provides several implementations of the Set interface. Here are some of the commonly used ones:

1. HashSet:

o Implements Set interface using a hash table. It does not guarantee the order of elements and allows null
elements.

Example:
java
Copy code
Set<String> hashSet = new HashSet<>();
hashSet.add("Java");
hashSet.add("Python");
hashSet.add("C++");

2. TreeSet:
o Implements NavigableSet interface and stores elements in a sorted order (natural ordering or custom
Comparator).

Example:
java
Copy code
Set<String> treeSet = new TreeSet<>();
treeSet.add("Apple");
treeSet.add("Banana");
treeSet.add("Orange");

3. LinkedHashSet:
o Extends HashSet and maintains insertion order of elements. It provides predictable iteration order.
o Example:

java
Copy code
Set<String> linkedHashSet = new LinkedHashSet<>();
linkedHashSet.add("Red");
linkedHashSet.add("Green");
linkedHashSet.add("Blue");
Example Usage of Set Interface

Here's a simple example demonstrating the use of HashSet:

java
Copy code
import java.util.HashSet;
import java.util.Set;

public class SetExample {


public static void main(String[] args) {
Set<String> set = new HashSet<>();

// Adding elements to the set


set.add("Apple");
set.add("Banana");
set.add("Orange");

// Adding a duplicate element (will not be added)


boolean added = set.add("Apple");
System.out.println("Element 'Apple' added? " + added); // Outputs
"Element 'Apple' added? false"

// Removing an element from the set


set.remove("Banana");

// Checking if an element exists in the set


boolean contains = set.contains("Orange");
System.out.println("Set contains 'Orange'? " + contains); // Outputs
"Set contains 'Orange'? true"

// Iterating through the set


System.out.println("\nElements in set:");
for (String element : set) {
System.out.println(element);
}
}
}
Explanation:

 add(E e): Adds elements to the set. Returns true if the element was added, false if it already existed in the set.
 remove(Object o): Removes the specified element from the set if it exists.
 contains(Object o): Checks if the set contains the specified element.
 Iteration: Uses enhanced for loop to iterate through elements in the set.

Benefits of Using Sets

 No Duplicates: Ensures each element in the set is unique, which is useful for maintaining a collection of distinct
objects.
 Efficient Lookups: Provides efficient operations for adding, removing, and checking the presence of elements.
 Flexible Implementations: Offers different implementations (HashSet, TreeSet, LinkedHashSet) with varying
characteristics (ordering, uniqueness, etc.) to suit different use cases.

Considerations

 Ordering: Consider whether you need elements to be ordered (use TreeSet or LinkedHashSet) or not (use
HashSet).
 Null Elements: Some implementations (HashSet, LinkedHashSet) allow null elements, while others (TreeSet)
do not.

In summary, sets in Java are useful for managing collections of unique elements and provide efficient operations
for adding, removing, and checking the existence of elements. Understanding the characteristics and differences
between implementations (HashSet, TreeSet, LinkedHashSet) helps in choosing the appropriate set for
your specific application requirements.

Maps

In Java, Map is an interface in the Java Collections Framework that maps keys to values. It represents a
collection of key-value pairs where each key is unique and is used to retrieve the corresponding value. Maps are
commonly used to store and manipulate data in scenarios where quick access to values based on keys is
required. Here’s an overview of Map interface and its common implementations in Java:

Map Interface

The Map interface provides methods to manipulate key-value pairs:

1. Basic Operations:

o put(K key, V value): Associates the specified value with the specified key in the map. If the map
previously contained a mapping for the key, the old value is replaced.
o get(Object key): Returns the value to which the specified key is mapped, or null if the map contains no
mapping for the key.
o remove(Object key): Removes the mapping for the specified key from the map if present.
o containsKey(Object key): Returns true if the map contains a mapping for the specified key.
o containsValue(Object value): Returns true if the map maps one or more keys to the specified value.
o size(): Returns the number of key-value mappings in the map.
o isEmpty(): Returns true if the map contains no key-value mappings.

2. Iterating over Map:

o Use entrySet() to get a Set view of the mappings contained in the map. Each element in this set is a
Map.Entry<K, V>, which represents a key-value pair.

Common Implementations of Map Interface

Java provides several implementations of the Map interface, each with its own characteristics:

1. HashMap:

o Implements Map interface using a hash table. It provides constant-time performance for most operations
(average-case complexity).
o Example:

java
Copy code
import java.util.HashMap;
import java.util.Map;

public class MapExample {


public static void main(String[] args) {
Map<String, Integer> map = new HashMap<>();

// Adding key-value pairs to the map


map.put("Java", 8);
map.put("Python", 3);
map.put("C++", 11);

// Retrieving a value based on key


int javaVersion = map.get("Java");
System.out.println("Java version: " + javaVersion); // Outputs "Java
version: 8"

// Removing a key-value pair


map.remove("Python");

// Checking if a key exists in the map


boolean containsCPlusPlus = map.containsKey("C++");
System.out.println("Map contains C++? " + containsCPlusPlus); //
Outputs "Map contains C++? true"

// Iterating through the map


System.out.println("\nKey-Value pairs in map:");
for (Map.Entry<String, Integer> entry : map.entrySet()) {
System.out.println(entry.getKey() + " -> " + entry.getValue());
}
}
}

2. TreeMap:

o Implements NavigableMap interface and stores key-value pairs in sorted order (natural ordering or
custom Comparator).
o Example:

java
Copy code
import java.util.Map;
import java.util.TreeMap;

public class TreeMapExample {


public static void main(String[] args) {
TreeMap<String, Integer> treeMap = new TreeMap<>();

// Adding key-value pairs to the tree map


treeMap.put("Apple", 10);
treeMap.put("Banana", 5);
treeMap.put("Orange", 8);

// Retrieving a value based on key


int appleCount = treeMap.get("Apple");
System.out.println("Apple count: " + appleCount); // Outputs "Apple
count: 10"

// Removing a key-value pair


treeMap.remove("Banana");

// Iterating through the tree map


System.out.println("\nKey-Value pairs in tree map:");
for (Map.Entry<String, Integer> entry : treeMap.entrySet()) {
System.out.println(entry.getKey() + " -> " + entry.getValue());
}
}
}

3. LinkedHashMap:

o Extends HashMap and maintains insertion order of keys. Provides predictable iteration order.
o Example:

java
Copy code
import java.util.LinkedHashMap;
import java.util.Map;

public class LinkedHashMapExample {


public static void main(String[] args) {
Map<String, Integer> linkedHashMap = new LinkedHashMap<>();

// Adding key-value pairs to the linked hash map


linkedHashMap.put("Red", 1);
linkedHashMap.put("Green", 2);
linkedHashMap.put("Blue", 3);

// Retrieving a value based on key


int greenValue = linkedHashMap.get("Green");
System.out.println("Green value: " + greenValue); // Outputs "Green
value: 2"

// Removing a key-value pair


linkedHashMap.remove("Red");

// Iterating through the linked hash map


System.out.println("\nKey-Value pairs in linked hash map:");
for (Map.Entry<String, Integer> entry : linkedHashMap.entrySet()) {
System.out.println(entry.getKey() + " -> " + entry.getValue());
}
}
}
Benefits of Using Maps

o Key-Value Pairing: Provides a convenient way to associate keys with values, allowing efficient
retrieval and manipulation based on keys.
o Efficient Operations: Depending on the implementation (HashMap, TreeMap, LinkedHashMap), maps
offer efficient operations for adding, removing, and looking up entries.
o Flexibility: Offers different implementations (HashMap for fast access, TreeMap for sorted storage,
LinkedHashMap for predictable iteration order) to suit different use cases.

Considerations

o Null Keys and Values: Some map implementations (HashMap, LinkedHashMap, TreeMap) may
allow null keys and values depending on the specific implementation.
o Concurrency: If thread safety is required, consider using concurrent implementations
(ConcurrentHashMap from java.util.concurrent).

In summary, Map in Java is a powerful interface for managing key-value pairs and provides several
implementations to cater to different requirements such as fast access, sorted storage, or predictable iteration
order. Understanding the characteristics and differences between these implementations helps in choosing the
appropriate Map for your specific application needs.

Utility Classes

In Java, utility classes are classes that contain only static methods and constants, often providing utility
functionalities that are commonly used across different parts of an application. These utility classes are typically
used to group related utility methods together for ease of maintenance and reuse. Here's an overview of utility
classes in Java and their common characteristics:

Characteristics of Utility Classes

o Static Methods: Utility classes typically consist of static methods that can be invoked without creating
an instance of the class. This makes them convenient for providing stateless operations.
o Private Constructor: Utility classes often have a private constructor to prevent instantiation. This
ensures that the class cannot be instantiated accidentally or subclassed.
o Final Class: Utility classes are commonly declared as final to prevent subclassing, which aligns with
their static nature and avoids unintended inheritance.
o Commonly Used for Helper Methods: They are used to group related helper methods that perform
generic operations such as string manipulation, date formatting, mathematical calculations, file
operations, etc.
Example of a Utility Class

Here’s an example of a utility class that provides methods for basic mathematical operations:

java
Copy code
public final class MathUtils {

private MathUtils() {
// Private constructor to prevent instantiation
}

public static int add(int a, int b) {


return a + b;
}

public static int subtract(int a, int b) {


return a - b;
}

public static int multiply(int a, int b) {


return a * b;
}

public static double divide(double a, double b) {


if (b == 0) {
throw new IllegalArgumentException("Divisor cannot be zero");
}
return a / b;
}
}
Usage of Utility Class
java
Copy code
public class Main {
public static void main(String[] args) {
int sum = MathUtils.add(10, 5);
System.out.println("Sum: " + sum); // Outputs "Sum: 15"

int difference = MathUtils.subtract(10, 5);


System.out.println("Difference: " + difference); // Outputs
"Difference: 5"

int product = MathUtils.multiply(10, 5);


System.out.println("Product: " + product); // Outputs "Product: 50"

double quotient = MathUtils.divide(10.0, 5.0);


System.out.println("Quotient: " + quotient); // Outputs "Quotient: 2.0"
}
}
Benefits of Using Utility Classes

o Code Reusability: Utility classes promote code reuse by encapsulating common operations in a single
place, reducing redundancy and promoting consistent behavior across an application.
o Simplifies Code Organization: They help in organizing utility methods logically, making it easier for
developers to find and use them when needed.
o Promotes Static Typing: Static methods in utility classes promote compile-time type checking,
enhancing code reliability and maintainability.

Considerations

o Thread Safety: Since utility classes typically contain stateless methods, they are generally thread-
safe when used correctly.
o Avoid Overuse: Avoid overusing utility classes for unrelated methods. Ensure that methods
grouped in a utility class share a common purpose or theme.
In summary, utility classes in Java are valuable for encapsulating reusable static methods and constants. They
promote code organization, improve maintainability, and enhance code reuse across applications. When
designing utility classes, it's important to follow best practices such as making the class final with a private
constructor to ensure proper usage and prevent unintended instantiation.

You might also like