You are on page 1of 118

UNIT-I : Introduction to Java

Introduction of Java:
1. Java is a versatile, object-oriented programming language
developed by Sun Microsystems in 1995.
2. Known for its platform independence, Java uses the "Write
Once, Run Anywhere" (WORA) principle, allowing programs to
run on any device with a Java Virtual Machine (JVM).
3. Its syntax is derived from C and C++, fostering familiarity for
developers.
4. Key features include automatic memory management through
garbage collection, strong type-checking, and a rich set of
libraries.
5. Java's portability, security features, and widespread use in web
development, mobile apps (Android), and enterprise systems
contribute to its enduring popularity in the software
development landscape.

History of Java:
The following points are explain the history of java :
1. Inception (1991):
 Java's origins trace back to 1991 when James Gosling, Mike
Sheridan, and Patrick Naughton initiated the "Green Project" at
Sun Microsystems.
 The team aimed to develop a new programming language for
consumer electronics.
2. Oak Language (1992):
 Initially named "Oak," the language was designed for handheld
devices and set-top boxes.
 It incorporated features like strong typing and garbage
collection.
3. Evolution to Java (1995):

1
 In 1995, Oak was rebranded as Java to avoid trademark
conflicts with another company using the name "Oak."
 The official release of Java took place, accompanied by the
slogan "Write Once, Run Anywhere."
4. Java 1.0 (1996):
 The first official version, Java 1.0, was released, featuring
applets for web browsers and the HotJava browser.
 Java quickly gained attention for its portability and platform
independence.
5. Introduction of JDK (1997):
 The Java Development Kit (JDK) was introduced, providing tools
for Java development.
 This marked a significant step in establishing Java as a
comprehensive development platform.
6. Java 2 Platform (1998):
 The release of Java 2 (J2SE) introduced new features like the
Swing GUI toolkit and the Collections Framework.
 This version laid the foundation for Java's growth in enterprise
development.
7. Enterprise Edition (J2EE) and Micro Edition (J2ME) (1999):
 J2EE and J2ME were introduced, focusing on enterprise-level
and mobile application development, respectively.
 These editions expanded Java's capabilities into diverse
computing environments.
8. Open Sourcing (2006):
 Sun Microsystems open-sourced Java, making the source code
available to the public under the GNU General Public License
(GPL).
 This move aimed to foster community involvement and
innovation.
9. Oracle's Acquisition (2010):
 Oracle Corporation acquired Sun Microsystems, becoming the
steward of Java.
 Java continued to evolve under Oracle's management, with
regular updates and improvements.

2
10. Java 8 and Beyond (2014 Onward):
 Java 8 brought significant enhancements, including lambda
expressions and the Streams API.
 Subsequent versions, like Java 9, 10, and beyond, introduced
modularization and ongoing language and platform
improvements.

How Java is different from C++:

Java and C++ are both powerful programming languages, but they
differ in several key aspects:

1. Platform Independence:
 Java: Java follows the "Write Once, Run Anywhere" principle.
Java code is compiled into bytecode, which can run on any
device with a Java Virtual Machine (JVM), making it platform-
independent.
 C++: C++ code is compiled into machine-specific binaries, which
may not be portable across different platforms without
recompilation.
2. Memory Management:
 Java: Java uses automatic memory management through
garbage collection, which helps in handling memory allocation
and deallocation without explicit programmer intervention.
 C++: C++ allows manual memory management using features
like new and delete, providing more control but also requiring
careful handling to prevent memory leaks or errors.
3. Object-Oriented Features:
 Java: Java is purely object-oriented, and everything in Java is an
object. It enforces the use of classes and objects for program
structure.
 C++: C++ supports both procedural and object-oriented
programming. While it has classes and objects, it allows for
procedural-style coding as well.

3
4. Pointers:
 Java: Java does not have explicit pointers and does not support
pointer arithmetic, contributing to a more secure environment
with reduced risks of memory-related errors.
 C++: C++ supports pointers and allows for direct manipulation
of memory addresses, providing more flexibility but also
increasing the potential for bugs and security vulnerabilities.
5. Multiple Inheritance:
 Java: Java supports multiple inheritance through interfaces,
allowing a class to implement multiple interfaces. However, it
avoids the complications associated with multiple inheritance
in C++.
 C++: C++ supports direct multiple inheritance, allowing a class
to inherit from more than one class. While powerful, this can
lead to the "diamond problem" and increased complexity.
6. Exception Handling:
 Java: Java has a robust and consistent exception handling
mechanism, making it mandatory for developers to catch or
declare exceptions.
 C++: C++ supports exception handling but does not enforce its
usage, providing developers with more flexibility but potentially
leading to less consistent error-handling practices.

In summary, Java prioritizes platform independence, automatic


memory management, and a strict object-oriented approach, while
C++ offers more flexibility, direct memory manipulation, and support
for multiple programming paradigms. The choice between Java and
C++ often depends on factors like project requirements,
development preferences, and specific application needs.

JDK Tools:

The Java Development Kit (JDK) provides a set of tools that aid in
Java development. Some key JDK tools include:

4
1. javac (Java Compiler):
 Compiles Java source code (.java files) into bytecode (.class
files) that can be executed by the Java Virtual Machine (JVM).
2. java (Java Interpreter):
 Executes Java bytecode on the JVM. It takes the name of a class
as a parameter to run the main method of that class.
3. jar (Java Archive):
 Creates and manages Java Archive (JAR) files, which are used to
package multiple Java class files, resources, and metadata into
a single compressed file.
4. javadoc (Java Documentation Generator):
 Generates API documentation from Java source code
comments. It produces HTML pages documenting classes,
methods, and fields.
5. jdb (Java Debugger):
 A command-line debugger for Java applications. Developers use
it to find and fix bugs by stepping through code, setting
breakpoints, and inspecting variables.
6. jconsole (Java Monitoring and Management Console):
 A graphical tool for monitoring and managing Java applications.
It provides information about memory usage, thread activity,
and performance metrics.
7. jvisualvm (Java VisualVM):
 A visual tool that integrates several JDK monitoring,
troubleshooting, and profiling utilities. It allows developers to
analyze the performance of Java applications.
8. jps (Java Process Status Tool):
 Lists the instrumented HotSpot Java Virtual Machines on the
target system. It provides information about the Java processes
running, including their process IDs.
9. javap (Java Class File Disassembler):
 Disassembles Java class files, providing a human-readable
representation of the bytecode instructions.
10. keytool (Key and Certificate Management Tool):

5
 Manages keystores and certificates for secure communication
in Java applications. It is used to generate key pairs, create
certificates, and manage cryptographic keys.
11. javah (Java Header and Stub File Generator):
 Generates C header files and C source files from Java class files,
facilitating the integration of Java code with native code.

These tools collectively support the development, debugging,


documentation, and monitoring of Java applications, offering a
comprehensive environment for Java developers.

Class File:
A class file in Java is a binary file that contains bytecode, which is the
intermediate representation of a Java source code file.

When you compile a Java source code file (with a .java extension)
using the Java compiler (javac), it produces a class file (with a .class
extension).

The class file is not platform-specific and can be executed on any


device that has a Java Virtual Machine (JVM).

Key points about class files:

1. Bytecode:
 The class file contains bytecode, a set of instructions
understood by the JVM. It is a low-level, platform-independent
representation of the Java source code.
2. Compilation Process:
 After writing Java source code, you use the Java compiler
(javac) to compile it. This results in the creation of one or more
class files, each corresponding to a class defined in the source
code.
3. Platform Independence:

6
 The Java programming language follows the "Write Once, Run
Anywhere" principle. Since class files contain bytecode, they
can be executed on any device with a compatible JVM,
regardless of the underlying hardware or operating system.
4. Execution by JVM:
 When you run a Java program, the JVM loads the class files,
interprets the bytecode, and executes the instructions. This
process ensures that Java applications are portable across
different environments.
5. Structure:
 Class files have a specific structure defined by the Java Virtual
Machine Specification. They include constant pools, method
information, field information, access flags, and more.
6. Debugging and Disassembly:
 Tools like javap can be used to disassemble class files, providing
a human-readable representation of the bytecode instructions.
This can aid in understanding the structure and behavior of
Java programs.
7. Packaging in JAR Files:
 Class files are often bundled together into Java Archive (JAR)
files for distribution. JAR files simplify the deployment of Java
applications by packaging multiple class files along with
resources and metadata.

In summary, class files are an integral part of the Java compilation


process, containing bytecode that is executed by the JVM. Their
platform independence and standardized structure contribute to the
portability and versatility of Java applications.

Java Bytecode:
Java bytecode is an intermediate representation of a Java program
that is generated during the compilation process. It serves as an
abstraction layer between the Java source code and the native
machine code of the underlying hardware.

7
Here are key points about Java bytecode:

1. Generated by Compilation:
 After writing Java source code (.java files), you use the Java
compiler (javac) to compile it. The result is a set of class files
containing Java bytecode.
2. Platform-Independent:
 Java bytecode is designed to be platform-independent,
adhering to the "Write Once, Run Anywhere" (WORA) principle.
This means that the same bytecode can run on any device with
a Java Virtual Machine (JVM) without modification.
3. Intermediary Representation:
 Bytecode is neither high-level source code nor machine-specific
native code. It is an intermediary representation that retains
the structure and logic of the original Java source code while
being independent of the underlying hardware.
4. Java Virtual Machine (JVM):
 The JVM is responsible for executing Java bytecode. It
interprets the bytecode at runtime or, in some cases,
dynamically compiles it into native machine code for improved
performance.
5. Security:
 The use of bytecode adds a layer of security to Java
applications. Before executing, the JVM can perform bytecode
verification to ensure that the code adheres to Java's safety and
security requirements.
6. Portability:
 Since bytecode is platform-independent, Java applications can
be distributed in a single format and executed on various
devices and operating systems with a compatible JVM.
7. Debugging and Profiling:
 Java bytecode can be inspected using tools like javap or
decompiled for debugging and profiling purposes. This allows
developers to understand the behavior of their Java programs
at a lower level.

8
8. Optimizations:
 Just-in-time (JIT) compilers within the JVM can dynamically
translate bytecode into native machine code for improved
execution speed. This process allows the JVM to adapt to the
specific characteristics of the host system.
9. Structure:
 Bytecode instructions are typically low-level operations,
representing actions such as method invocation, variable
manipulation, and control flow. The Java Virtual Machine
Specification defines the detailed structure and semantics of
bytecode.

In summary, Java bytecode is a crucial component in the Java


programming language, serving as an intermediary representation
that enables platform independence, security, and adaptability
through the Java Virtual Machine.

JVM:
The Java Virtual Machine (JVM) is a key component of the Java
Runtime Environment (JRE) and Java Development Kit (JDK).

It plays a crucial role in executing Java programs by interpreting or


compiling Java bytecode.

Here are key points about the JVM:

1. Execution Environment:
 The JVM provides a runtime environment for Java applications
to run. It abstracts the underlying hardware and operating
system, allowing Java programs to be platform-independent.
2. Platform Independence:
 One of the primary features of the JVM is its ability to execute
Java bytecode on any device that has a JVM installed. This
follows the "Write Once, Run Anywhere" (WORA) principle.
3. Interpretation and Just-in-Time Compilation:
9
 The JVM can interpret Java bytecode on-the-fly, executing the
instructions one by one. Additionally, modern JVMs often
employ Just-in-Time (JIT) compilation, translating bytecode into
native machine code for improved performance during
runtime.
4. Memory Management:
 The JVM handles memory allocation and garbage collection,
automatically managing the allocation and deallocation of
memory, reducing the risk of memory-related errors.
5. Security:
 The JVM includes a security manager that enforces security
policies to protect against malicious activities. Bytecode
verification is performed before execution to ensure it adheres
to Java's safety requirements.
6. Class Loader:
 The Class Loader is responsible for loading classes into the JVM.
It loads classes from the local file system or network, allowing
Java applications to dynamically load classes at runtime.
7. Execution of Bytecode:
 The JVM executes Java bytecode, which is the compiled form of
Java source code. Bytecode is a set of instructions that the JVM
interprets or compiles into native machine code.
8. Java Native Interface (JNI):
 The JVM supports the Java Native Interface, allowing Java code
to interact with applications and libraries written in other
languages, such as C and C++.
9. Monitoring and Management:
 The JVM provides tools for monitoring and managing Java
applications. Tools like JConsole and VisualVM offer insights
into memory usage, thread activity, and overall performance.
10. HotSpot JVM:
 The HotSpot JVM, developed by Oracle, is a widely used and
optimized JVM implementation that incorporates features like
adaptive optimization and garbage collection.

10
In summary, the JVM serves as a virtualized execution environment
for Java applications, ensuring platform independence, memory
management, security, and adaptability. It is a fundamental
component that enables the widespread use of Java across diverse
computing environments.

Identifiers:
Identifiers in programming are names given to various program
elements such as variables, methods, classes, packages, and more.
Here are key points about identifiers:

1. Definition:
 An identifier is a sequence of characters in a program that is
used to name entities like variables, functions, classes, etc.
2. Rules for Naming:
 Identifiers must begin with a letter (A-Z or a-z), underscore (_),
or a dollar sign ($).
 After the first character, identifiers can also contain digits (0-9).
 Uppercase and lowercase letters are distinct in most
programming languages, meaning "example" and "Example"
would be different identifiers.
3. Examples:
 Valid identifiers: variable, _count, totalAmount,
calculateSum123.
 Invalid identifiers: 123total, @special, class, as they violate
naming rules.
4. Case Sensitivity:
 Most programming languages are case-sensitive, meaning
example and Example are considered different identifiers.
5. Reserved Words:
 Certain words are reserved by programming languages and
cannot be used as identifiers. Examples include keywords like if,
while, and class.
6. Descriptive Naming:
11
 Choosing meaningful and descriptive names for identifiers
enhances code readability and understanding.
7. CamelCase and SnakeCase:
 CamelCase and SnakeCase are conventions for naming
identifiers. CamelCase capitalizes the first letter of each word
except the first, while SnakeCase separates words with
underscores.
8. Consistency:
 Maintaining consistent naming conventions across a codebase
improves code clarity and makes it easier for developers to
collaborate.
9. Namespace:
 Identifiers within different namespaces (like classes or
packages) can share the same name without conflict.
10. Scope:
 The scope of an identifier refers to the region in the code
where it is valid and can be accessed. For example, local
variables have a limited scope within a specific block of code.

Data types:
Java provides a variety of data types to represent different kinds of
values. Here are the main data types in Java:

1. Primitive Data Types:


 Integral Types:

 byte: 8-bit signed integer.


 short: 16-bit signed integer.
 int: 32-bit signed integer.
 long: 64-bit signed integer.

 Floating-Point Types:

 float: 32-bit floating-point number.


 double: 64-bit floating-point number.

 Character Type:

 char: 16-bit Unicode character.

12
 Boolean Type:
 boolean: Represents true or false values.

2. Reference Data Types:


 Class Types:

 User-defined classes and standard library classes.

 Array Types:

 Arrays of any data type, including arrays of objects.


3. String Type:
 String: A sequence of characters. Although it is commonly used,
it is not a primitive data type but a class in Java.
4. Special Types:
 void: Represents the absence of a type. Used in method return
types to indicate that the method does not return a value.
5. Wrapper Classes:
 Java provides wrapper classes for each primitive data type to
convert them into objects. For example, Integer for int, Double
for double, etc.
6. Enumeration Types:
 enum: A special data type that defines a set of constants.

7. User-Defined Data Types:


 Classes and interfaces created by the programmer to model
complex data structures and behaviors.

Here's an example illustrating the usage of some primitive data types


in Java:

public class DataTypesExample {


public static void main(String[] args) {
// Integral Types
byte myByte = 127;
short myShort = 32000;
int myInt = 2147483647;

13
long myLong = 9223372036854775807L; // Note the 'L' suffix
for long literals

// Floating-Point Types
float myFloat = 3.14f; // Note the 'f' suffix for float literals
double myDouble = 2.71828;

// Character Type
char myChar = 'A';

// Boolean Type
boolean myBoolean = true;

System.out.println("Integral Types: " + myByte + ", " + myShort


+ ", " + myInt + ", " + myLong);
System.out.println("Floating-Point Types: " + myFloat + ", " +
myDouble);
System.out.println("Character Type: " + myChar);
System.out.println("Boolean Type: " + myBoolean);
}
}

Operators in Java:

14
Java provides various operators for performing operations on
variables and values. Here are some commonly used operators in
Java with examples:

1. Arithmetic Operators:
 Used for basic mathematical operations.

For ex:
int a = 10;
int b = 5;
int sum = a + b; // Addition
int difference = a - b; // Subtraction
int product = a * b; // Multiplication
int quotient = a / b; // Division
int remainder = a % b; // Modulus

2. Comparison Operators:
 Used to compare values and produce a boolean result.

int x = 10;
int y = 5;
boolean isEqual = (x == y); // Equal to
boolean isNotEqual = (x != y); // Not equal to
boolean greaterThan = (x > y); // Greater than
boolean lessThan = (x < y); // Less than
boolean greaterOrEqual = (x >= y); // Greater than or equal to
boolean lessOrEqual = (x <= y); // Less than or equal to

3. Logical Operators:
 Used for combining boolean expressions.
boolean condition1 = true;

15
boolean condition2 = false;
boolean logicalAnd = condition1 && condition2; // Logical AND
boolean logicalOr = condition1 || condition2; // Logical OR
boolean logicalNot = !condition1; // Logical NOT

4. Assignment Operators:
 Used to assign values to variables.

int num = 10; num += 5; // Equivalent to num = num + 5;

5. Increment and Decrement Operators:


 Used to increase or decrease the value of a variable.

int count = 5; count++; // Increment by 1 int newCount = count--; //


Decrease by 1 (post-decrement)

6. Conditional (Ternary) Operator:


 A shorthand way to write an if-else statement.

int age = 20; String result = (age >= 18) ? "Adult" : "Minor";

7. Bitwise Operators:
 Used to perform bit-level operations on integers.

int a = 5; // binary: 0101 int b = 3; // binary: 0011 int bitwiseAnd = a


& b; // Bitwise AND (result: 0001) int bitwiseOr = a | b; // Bitwise OR
(result: 0111)

8. Instanceof Operator:
 Used to test whether an object is an instance of a particular
class or interface.
Object obj = "Hello"; boolean isString = (obj instanceof String);

Control Statements:

16
Control statements in Java are used to control the flow of execution
in a program.

They enable decision-making, looping, and branching based on


certain conditions.

Here are the main control statements in Java:

1. if Statement:
 Used for decision-making. Executes a block of code if a given
condition is true.

Class If{
Public Static Void Main(string[ ]args) {
int x = 10;
if (x > 5) {
System.out.println("x is greater than 5");
}
}
}
2. if-else Statement:
 Provides an alternative block of code to be executed if the
condition in the if statement is false.

Class IfElse{
Public Static Void Main(string[ ]args) {
int y = 3;
if (y % 2 == 0) {
System.out.println("y is even");

17
} else {
System.out.println("y is odd");
}
}
}
3. if-else if-else Statement:
 Allows checking multiple conditions in sequence and executing
the block associated with the first true condition.
Class simple{
Public Static Void Main(string[ ]args) {
int grade = 75;
if (grade >= 90) {
System.out.println("A");
} else if (grade >= 80) {
System.out.println("B");
} else {
System.out.println("C");
}
}
}
4. Switch Statement:
 Evaluates a variable or expression against a list of possible
values and executes the corresponding block of code.
Class simple{
Public Static Void Main(string[ ]args) {
int day = 3;
18
switch (day) {
case 1:
System.out.println("Monday");
break;
case 2:
System.out.println("Tuesday");
break;
// ... other cases ...
default:
System.out.println("Invalid day");
}
}
}
5. while Loop:
 Repeats a block of code as long as a specified condition is true.
Class simple{
Public Static Void Main(string[ ]args) {
int i = 0;
while (i < 5) {
System.out.println(i);
i++;
}
}
}

19
6. do-while Loop:
 Similar to the while loop, but guarantees that the block of code
is executed at least once before checking the condition.

Class simple{
Public Static Void Main(string[ ]args) {
int j = 0;
do {
System.out.println(j);
j++;
} while (j < 5);
}
}
7. for Loop:
 Executes a block of code a specified number of times, using an
initialization, condition, and increment expression.
Class simple{
Public Static Void Main(string[ ]args) {

for (int k = 0; k < 5; k++) {


System.out.println(k);
}
}
}
8. break and continue Statements:

20
 Used to alter the flow of control in loops. break terminates the
loop, and continue skips the rest of the loop and proceeds to
the next iteration.
Class simple{
Public Static Void Main(string[ ]args) {
for (int m = 0; m < 10; m++) {
if (m == 5) {
break; // Terminate the loop when m is 5
}
if (m % 2 == 0) {
continue; // Skip even numbers
}
System.out.println(m);
}
}
}
loop in Java:

In Java, loops are used to repeatedly execute a block of code until a


specified condition is met. Here are the main types of loops in Java:

1. while Loop:
 The while loop repeatedly executes a block of code as long as
the specified condition is true.
Class simple{
Public Static Void Main(string[ ]args) {

21
int i = 0;
while (i < 5) {
System.out.println(i); i++;
}
}
}

2. do-while Loop:
 The do-while loop is similar to the while loop, but it guarantees
that the block of code is executed at least once before checking
the condition.

Class simple{
Public Static Void Main(string[ ]args) {
int j = 0;
do {
System.out.println(j);
j++;
}
while (j < 5);

}
}
3. for Loop:
 The for loop is used when you know the number of iterations in
advance. It has an initialization, condition, and
increment/decrement expression.
Class simple{

22
Public Static Void Main(string[ ]args) {
for (int k = 0; k < 5; k++) {
System.out.println(k);
}
}
}

4. Enhanced for Loop (for-each):


 Introduced in Java 5, the enhanced for loop simplifies iterating
over elements in an array or a collection.
Class simple{
Public Static Void Main(string[ ]args) {
int[] numbers = {1, 2, 3, 4, 5};
for (int num : numbers) {
System.out.println(num);
}
}
}

5. Nested Loops:
 You can use loops within other loops, creating nested loops for
more complex iteration patterns.
Class simple{
Public Static Void Main(string[ ]args) {
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 2; j++) {

23
System.out.println("i: " + i + ", j: " + j);
}
}
}
}

6. break Statement:
 The break statement is used to terminate a loop prematurely. It
is often used in combination with a conditional statement.
Class simple{
Public Static Void Main(string[ ]args) {
for (int m = 0; m < 10; m++) {
if (m == 5) {
break; // Terminate the loop when m is 5
}
System.out.println(m);
}
}
}

7. continue Statement:
 The continue statement is used to skip the rest of the loop's
code and proceed to the next iteration.
Class simple{
Public Static Void Main(string[ ]args) {
for (int n = 0; n < 10; n++) {

24
if (n % 2 == 0) {
continue; // Skip even numbers
}
System.out.println(n);
}
}
}

Loops are essential for repetitive tasks and allow for more efficient
and concise code in Java. The choice of loop depends on the specific
requirements of the program and the nature of the iteration.

Arrays in Java:
In Java, an array is a data structure that stores a fixed-size, ordered
collection of elements of the same type.

Here are the key aspects of arrays in Java:

1. Declaration and Initialization:


 Arrays can be declared and initialized using square brackets ([]).
The size of the array must be specified at the time of
declaration
// Declaration and Initialization of an integer array
int[] numbers = {1, 2, 3, 4, 5};

// Declaration of a string array


String[] names;
// Initialization with a specific size
names = new String[3];
25
Syntax:
// Declaration and Initialization
dataType[] arrayName = {element1, element2, ...,
elementN};

// Declaration
dataType[] arrayName;

// Initialization with a specific size


arrayName = new dataType[size];

Example: Let's create an array of integers, initialize it, and then


iterate through the elements:
public class ArrayExample {
public static void main(String[] args) {
// Declaration and Initialization
int[] numbers = {1, 2, 3, 4, 5};

// Accessing elements and printing them


System.out.print("Array Elements: ");
for (int i = 0; i < numbers.length; i++) {
System.out.print(numbers[i] + " ");
}
System.out.println();

26
// Declaration of a string array
String[] names;

// Initialization with a specific size


names = new String[3];

// Assigning values to elements


names[0] = "Alice";
names[1] = "Bob";
names[2] = "Charlie";

// Accessing and printing elements using enhanced for


loop (for-each)
System.out.print("Names: ");
for (String name : names) {
System.out.print(name + " ");
}
}
}

In this example:

 We declare and initialize an integer array named numbers with


values 1 to 5.
 We iterate through the array using a for loop to print its elements.
 We declare a string array names and initialize it with a size of 3.
27
 We assign values to the elements of the names array.
 We use an enhanced for-each loop to iterate through and print the
names.

This illustrates the basic syntax and usage of arrays in Java.


Remember that arrays in Java are zero-indexed, and the length
property is used to get the size of the array.

Inheritance in Java:

Inheritance is a fundamental concept in object-oriented


programming (OOP) that allows a class to inherit the properties and
behaviors of another class.
In Java, classes can be organized in a hierarchical structure using
inheritance.
Here's an overview of inheritance in Java:
Syntax:
class ChildClass extends ParentClass {
// Additional attributes and methods in the ChildClass
}

Example: Let's consider a simple example with a Vehicle class as the


parent class and Car as the child class that inherits from Vehicle:

// Parent class

class Vehicle {

String brand;

int year;

28
void start() {

System.out.println("The vehicle is starting.");

void stop() {

System.out.println("The vehicle is stopping.");

// Child class inheriting from Vehicle

class Car extends Vehicle {

int numberOfDoors;

void accelerate() {

System.out.println("The car is accelerating.");

public class InheritanceExample {

public static void main(String[] args) {

29
// Creating an instance of the child class (Car)

Car myCar = new Car();

myCar.brand = "Toyota";

myCar.year = 2022;

myCar.numberOfDoors = 4;

// Accessing methods from the parent class

myCar.start();

myCar.stop();

// Accessing methods from the child class

myCar.accelerate();

In this example:

 Vehicle is the parent class with attributes brand and year, as well as
methods start() and stop().
 Car is the child class that extends Vehicle. It inherits attributes and
methods from Vehicle and introduces its own attribute
numberOfDoors and method accelerate().
 In the main method, an instance of Car is created, and both parent
and child class methods are accessed.

Key Concepts:
30
1. Superclass (Parent Class): The class whose properties and behaviors
are inherited.
2. Subclass (Child Class): The class that inherits from another class.

Benefits of Inheritance:

 Code Reusability: Inherited features can be reused in the child class.


 Method Overriding: The child class can provide a specific
implementation for methods defined in the parent class.
 Polymorphism: Objects of the child class can be used wherever
objects of the parent class are expected.

Multilevel hierarchy:

In a multilevel hierarchy in Java, a class is derived from another class,


and then another class is derived from the derived class, forming a
chain of inheritance.
Let's illustrate this concept with an example:
// Grandparent class
class Animal {
void eat() {
System.out.println("Animals eat food.");
}
}

// Parent class inheriting from Animal


class Mammal extends Animal {
void breathe() {
System.out.println("Mammals breathe air.");
31
}
}

// Child class inheriting from Mammal


class Dog extends Mammal {
void bark() {
System.out.println("Dogs bark.");
}
}

public class MultilevelHierarchyExample {


public static void main(String[] args) {
// Creating an instance of the child class (Dog)
Dog myDog = new Dog();

// Accessing methods from the grandparent class (Animal)


myDog.eat();

// Accessing methods from the parent class (Mammal)


myDog.breathe();

// Accessing methods from the child class (Dog)


myDog.bark();
}

32
}

In this example:

 Animal is the grandparent class with a method eat.


 Mammal is the parent class that inherits from Animal and introduces
its own method breathe.
 Dog is the child class that inherits from Mammal and introduces its
own method bark.

Output:

Animals eat food.


Mammals breathe air.
Dogs bark.

method overriding in Java:

Method overriding is a concept in Java where a subclass provides a


specific implementation for a method that is already defined in its
superclass.
This allows a subclass to provide a more specialized version of a
method inherited from its parent class.
Here's an example with an explanation:
// Parent class
class Animal {
void makeSound() {
System.out.println("Generic animal sound");

33
}
}

// Child class inheriting from Animal


class Dog extends Animal {
// Overriding the makeSound method in the subclass
@Override
void makeSound() {
System.out.println("Bark, bark!");
}

// Additional method in the subclass


void fetch() {
System.out.println("Dog is fetching.");
}
}

public class MethodOverridingExample {


public static void main(String[] args) {
// Creating an instance of the subclass (Dog)
Dog myDog = new Dog();

// Accessing the overridden method

34
myDog.makeSound(); // Calls the overridden method in Dog
class

// Accessing the method from the parent class


Animal myAnimal = myDog; // Upcasting
myAnimal.makeSound(); // Calls the overridden method in Dog
class

// Accessing the additional method in the subclass


myDog.fetch();
}
}

Explanation:

 The Animal class has a method called makeSound.


 The Dog class extends Animal and overrides the makeSound method
with its own implementation.
 In the main method, an instance of the Dog class is created.
 The overridden makeSound method is called on both the Dog object
and the Animal reference pointing to the Dog object.
 The fetch method is specific to the Dog class and demonstrates
additional behavior.

Output:

Bark, bark!

Bark, bark!

Dog is fetching.

35
Abstract classes in Java:
Abstract classes in Java are classes that cannot be instantiated on
their own and may contain abstract methods, which are methods
without a body.

Abstract classes serve as blueprints for concrete classes, providing


common functionality and leaving certain details to be implemented
by their subclasses.

Here's an example with an explanation:

// Abstract class

abstract class Shape {

// Abstract method (to be implemented by subclasses)

abstract double calculateArea();

// Concrete method

void display() {

System.out.println("This is a shape.");

// Concrete subclass 1

class Circle extends Shape {

36
double radius;

// Constructor

Circle(double radius) {

this.radius = radius;

// Implementing the abstract method

@Override

double calculateArea() {

return Math.PI * radius * radius;

// Concrete subclass 2

class Rectangle extends Shape {

double length;

double width;

// Constructor

37
Rectangle(double length, double width) {

this.length = length;

this.width = width;

// Implementing the abstract method

@Override

double calculateArea() {

return length * width;

public class AbstractClassExample {

public static void main(String[] args) {

// Creating instances of concrete subclasses

Circle myCircle = new Circle(5.0);

Rectangle myRectangle = new Rectangle(4.0, 6.0);

// Calling methods on instances

myCircle.display();

38
System.out.println("Area of the circle: " +
myCircle.calculateArea());

myRectangle.display();

System.out.println("Area of the rectangle: " +


myRectangle.calculateArea());

Explanation:

 The Shape class is an abstract class with an abstract method


calculateArea() and a concrete method display().
 Two concrete subclasses, Circle and Rectangle, extend the Shape
abstract class and implement the calculateArea method.
 In the main method, instances of the concrete subclasses are
created, and methods are called on these instances.

Output:

This is a shape.

Area of the circle: 78.53981633974483

This is a shape.

Area of the rectangle: 24.0

Final classes in Java:


In Java, a final class is a class that cannot be subclassed.

39
Once a class is declared as final, it cannot be extended or inherited
by any other class.

Additionally, if a method is marked as final within a class, it cannot


be overridden by subclasses.

Here's an example to illustrate the concept of a final class:

// Final class

final class FinalClass {

// Final method

final void display() {

System.out.println("This is a final method.");

// This class cannot extend FinalClass due to the 'final' keyword

// class AnotherClass extends FinalClass {} // This would result in a


compilation error

public class FinalClassExample {

public static void main(String[] args) {

// Creating an instance of the final class

FinalClass finalObj = new FinalClass();

40
// Calling the final method

finalObj.display();

Explanation:

 FinalClass is declared as a final class, indicating that it cannot be


subclassed.
 The display method within FinalClass is marked as final, preventing it
from being overridden by any subclasses.
 The attempt to create a subclass (AnotherClass) of FinalClass results
in a compilation error.

Output:

This is a final method.

In this example, the FinalClass is marked as final, making it


impossible to create any subclasses. The display method, being a final
method, cannot be overridden by any subclass.

Use of final classes is often seen in situations where the developer


wants to ensure that a specific class remains unchanged and cannot be
extended further.

This can be useful for classes with critical implementations where


modification or extension might introduce unintended consequences.

Unit-II: Package and Exception in Java

Defining: 1. Packages in Java:

41
A package in Java is a way to organize related classes and interfaces.
It helps in avoiding naming conflicts and provides a modular
structure to the code.

2. Exceptions in Java:

Exceptions are events that occur during the execution of a program


that disrupt the normal flow of instructions. In Java, exceptions are
handled using a combination of try, catch, finally, and throw
keywords

Implementing and Applying Packages in Java:


Let's walk through an example of implementing and applying
packages in Java. In this example, we'll create a simple package
named geometry that contains classes for calculating the area of
geometric shapes.

1. Create Package and Classes:


Create a directory structure as follows:

geometry
|-- Shape.java
|-- Circle.java
|-- Rectangle.java
|-- MainApp.java

2.Define Classes in the Package:


Shape.java:

package geometry;

42
public abstract class Shape {
abstract double calculateArea();
}
Circle.java:
package geometry;

public class Circle extends Shape {


private double radius;

public Circle(double radius) {


this.radius = radius;
}

@Override
double calculateArea() {
return Math.PI * radius * radius;
}
}
Rectangle.java:
package geometry;

public class Rectangle extends Shape {


private double length;
private double width;

43
public Rectangle(double length, double width) {
this.length = length;
this.width = width;
}

@Override
double calculateArea() {
return length * width;
}
}
MainApp.java:
import geometry.Circle;
import geometry.Rectangle;

public class MainApp {


public static void main(String[] args) {
Circle myCircle = new Circle(5.0);
Rectangle myRectangle = new Rectangle(4.0, 6.0);

System.out.println("Area of the Circle: " +


myCircle.calculateArea());
System.out.println("Area of the Rectangle: " +
myRectangle.calculateArea());

44
}
}
3. Compileand Run:
Compile the classes using the following commands:

javac geometry/*.java MainApp.java


Run the application:
java MainApp

4. Output:
Area of the Circle: 78.53981633974483
Area of the Rectangle: 24.0
This structure illustrates the organization and encapsulation
provided by packages in Java. Each class is part of the geometry
package, and their functionality is encapsulated within this
namespace.

Importing Packages in Java:


In Java, importing packages is a way to make classes and interfaces
from one package available to another. This is done using the import
statement. Let's go through an example to illustrate how to import and
use classes from a different package:

1. Create Packages and Classes:


Create a directory structure as follows:

geometry
|-- Shape.java
|-- Circle.java

45
|-- Rectangle.java
|-- MainApp.java

app
|-- MainClass.java
2.Define Classes in the Packages:
geometry/Shape.java:

package geometry;

public abstract class Shape {


abstract double calculateArea();
}
geometry/Circle.java:
package geometry;

public class Circle extends Shape {


private double radius;

public Circle(double radius) {


this.radius = radius;
}

@Override
double calculateArea() {

46
return Math.PI * radius * radius;
}
}
geometry/Rectangle.java:
package geometry;

public class Rectangle extends Shape {


private double length;
private double width;

public Rectangle(double length, double width) {


this.length = length;
this.width = width;
}

@Override
double calculateArea() {
return length * width;
}
}
geometry/MainApp.java:
package geometry;

public class MainApp {

47
public static void main(String[] args) {
Circle myCircle = new Circle(5.0);
Rectangle myRectangle = new Rectangle(4.0, 6.0);

System.out.println("Area of the Circle: " +


myCircle.calculateArea());
System.out.println("Area of the Rectangle: " +
myRectangle.calculateArea());
}
}

app/MainClass.java:

package app;

import geometry.Circle;
import geometry.Rectangle;

public class MainClass {


public static void main(String[] args) {
Circle myCircle = new Circle(3.0);
Rectangle myRectangle = new Rectangle(5.0, 7.0);

System.out.println("Area of the Circle: " +


myCircle.calculateArea());

48
System.out.println("Area of the Rectangle: " +
myRectangle.calculateArea());
}
}
3. Compile
and Run:
Compile the classes using the following commands:

javac geometry/*.java app/*.java

Run the application:

java app.MainClass

4. Output:
Area of the Circle: 28.274333882308138
Area of the Rectangle: 35.0

In this example, the MainClass in the app package imports the Circle
and Rectangle classes from the geometry package using the import
statement. This allows it to create instances of these classes and use
their functionality.

Types of packages in Java:


In Java, packages are used to organize and categorize classes and
interfaces. There are two main types of packages:

1. Built-in Packages (Java API packages):


 These are the packages that are provided by Java and cover a
wide range of functionalities. They are also known as Java API
packages or standard packages.
Example: The java.util package is a built-in package that provides
utility classes for various purposes.
Here's a simple example using the ArrayList class from the java.util
package:

49
import java.util.ArrayList;

public class ArrayListExample {


public static void main(String[] args) {
// Creating an ArrayList of integers
ArrayList<Integer> numbers = new ArrayList<>();
numbers.add(1);
numbers.add(2);
numbers.add(3);

// Displaying the ArrayList


System.out.println("ArrayList: " + numbers);
}
}
2. User-defined Packages:
 These are packages created by the user to organize their own
classes and interfaces. User-defined packages help in avoiding
naming conflicts and provide a modular structure to the code.
Example: Let's create a simple user-defined package named math
with two classes, Calculator and AdvancedCalculator:
Directory Structure:
math
|-- Calculator.java
|-- AdvancedCalculator.java
|-- MathApp.java
Calculator.java:
package math;

public class Calculator {


public int add(int a, int b) {
return a + b;
50
}

public int subtract(int a, int b) {


return a - b;
}
}
AdvancedCalculator.java:
package math;

public class AdvancedCalculator extends Calculator {


public double squareRoot(int num) {
return Math.sqrt(num);
}
}
MathApp.java:
import math.Calculator;
import math.AdvancedCalculator;

public class MathApp {


public static void main(String[] args) {
Calculator basicCalc = new Calculator();
System.out.println("Addition: " + basicCalc.add(5, 3));

AdvancedCalculator advCalc = new AdvancedCalculator();

51
System.out.println("Square Root: " + advCalc.squareRoot(25));
}
}
Compile and Run:
javac math/*.java MathApp.java
java MathApp
Output:
Addition: 8
Square Root: 5.0

Exception handling in Java:


Exception handling in Java is a mechanism to handle runtime errors
or exceptional situations that may occur during the execution of a
program.
Java provides the try, catch, finally, throw, and throws keywords for
handling exceptions.
Let's go through a basic example to illustrate exception handling in
Java:
public class ExceptionHandlingExample {
public static void main(String[] args) {
try {
// Code that might cause an exception
int result = divide(10, 0);
System.out.println("Result: " + result); // This line won't be
reached if an exception occurs

52
} catch (ArithmeticException e) {
// Handling the exception
System.out.println("Error: Division by zero is not allowed.");
} finally {
// Code that will be executed regardless of whether an
exception occurs or not
System.out.println("This block will be executed regardless of
exceptions.");
}

// Rest of the program continues after exception handling


System.out.println("Program continues after exception
handling.");
}

// A method that might throw an exception


private static int divide(int numerator, int denominator) {
// Handling a potential divide-by-zero exception
if (denominator == 0) {
throw new ArithmeticException("Division by zero");
}
return numerator / denominator;
}
}

53
Output: Error: Division by zero is not allowed.
This block will be executed regardless of exceptions.
Program continues after exception handling.

Explanation:

 The ‘ try’ block contains the code that might throw an exception (in
this case, dividing by zero).
 The ‘catch’ block catches the specific type of exception
(‘ArithmeticException’) that may occur and provides a way to handle
it.
 The ‘finally’ block contains code that will be executed regardless of
whether an exception occurs or not.
 The ‘divide’ method throws an ‘ArithmeticException’ if the
denominator is zero.
 After exception handling, the program continues with the rest of the
code.

Exception handling helps prevent abrupt program termination and


allows developers to gracefully handle errors, ensuring that the
program can recover or terminate in a controlled manner.

Uncaught Exceptions:
Uncaught exceptions in Java are exceptions that are not caught and
handled within the code using a try-catch block.

When an uncaught exception occurs during the execution of a Java


program, it propagates up the call stack until it reaches the top-level
of the program (usually the main method) or until it is caught and
handled by an appropriate catch block.

Here's an example illustrating an uncaught exception:

54
public class UncaughtExceptionExample {
public static void main(String[] args) {
// Calling a method that throws an uncaught exception
uncaughtExceptionMethod();

// This line won't be reached if an uncaught exception occurs


System.out.println("This line won't be executed if an uncaught
exception occurs.");
}

private static void uncaughtExceptionMethod() {


// Throwing an uncaught exception (ArithmeticException)
int result = 10 / 0;
}
}

In this example, the ‘uncaughtExceptionMethod’ method intentionally


attempts to perform a division by zero, which results in an
‘ArithmeticException.’ Since there is no try-catch block to catch and
handle this exception within the ‘uncaughtExceptionMethod’ or the
main method, it becomes an uncaught exception.

Output:

Exception in thread "main" java.lang.ArithmeticException: / by zero

at
UncaughtExceptionExample.uncaughtExceptionMethod(UncaughtEx
ceptionExample.java:12)

55
at
UncaughtExceptionExample.main(UncaughtExceptionExample.java:
6)

The program terminates with a runtime exception, and the message


indicates that an ‘ArithmeticException’ occurred during the execution
of the ‘uncaughtExceptionMethod’ method.

To handle uncaught exceptions, you can use a general exception


handler at the top level of your program or implement more specific
exception handling strategies based on the nature of the exceptions
that may occur. Handling exceptions helps in providing a graceful
response to unexpected errors and prevents the program from
terminating abruptly.

Multiple catch:

In Java, you can use multiple ‘catch’ blocks to handle different types
of exceptions that may occur within a ‘try’ block.
This allows you to provide specific handling for different types of
exceptions.
The ‘catch’ blocks are evaluated in order, and the first matching
block is executed.
Here's an example illustrating multiple catch blocks:
public class MultipleCatchExample {
public static void main(String[] args) {
try {
// Code that might cause exceptions
int[] numbers = {1, 2, 3};
int result = divideByZero(numbers);
System.out.println("Result: " + result); // This line won't be
reached if an exception occurs
} catch (ArithmeticException e) {

56
// Handling ArithmeticException
System.out.println("Error: Division by zero is not allowed.");
} catch (ArrayIndexOutOfBoundsException e) {
// Handling ArrayIndexOutOfBoundsException
System.out.println("Error: Array index out of bounds.");
} catch (Exception e) {
// Generic exception handler (catches any other type of
exception)
System.out.println("An unexpected error occurred: " +
e.getMessage());
} finally {
// Code that will be executed regardless of whether an
exception occurs or not
System.out.println("This block will be executed regardless of
exceptions.");
}
}

private static int divideByZero(int[] numbers) {


// Causing an ArithmeticException by dividing by zero
int result = 10 / numbers[3];

// Causing an ArrayIndexOutOfBoundsException
int value = numbers[10];

return result;
}
}
Output:
57
Error: Array index out of bounds.
This block will be executed regardless of exceptions.
In this example, an attempt to divide by zero and access an array
element with an invalid index causes exceptions. The specific ‘catch’
blocks handle each type of exception separately, and the generic
‘Exception’ block catches any other unexpected exceptions. The
‘finally’ block ensures that its code is executed regardless of the
exception type.

Java's Built-in Exception:


Java provides a rich set of built-in exceptions that cover a wide range
of error scenarios. Here are some commonly used built-in exceptions
in Java:

1. ArithmeticException:
 Thrown when an exceptional arithmetic condition has
occurred, such as dividing by zero

int result = 10 / 0; // ArithmeticException

2. NullPointerException:
 Thrown when trying to access or invoke a method on an object
reference that is null.

String str = null;

int length = str.length(); // NullPointerException

3. ArrayIndexOutOfBoundsException:
 Thrown when attempting to access an array element at an
index that is outside the bounds of the array.

int[] numbers = {1, 2, 3};

58
int value = numbers[5]; // ArrayIndexOutOfBoundsException

4. NumberFormatException:
 Thrown when attempting to convert a string to a numeric
format, but the string does not have the appropriate format.

String str = "abc";

int num = Integer.parseInt(str); // NumberFormatException

5. FileNotFoundException:
 Thrown when attempting to access a file that cannot be found.

FileReader fileReader = new FileReader("nonexistentfile.txt"); //


FileNotFoundException

6. IOException:
 A general exception for I/O operations.

FileInputStream fileInputStream = new


FileInputStream("myfile.txt");

fileInputStream.read(); // IOException

7. nterruptedException:
 Thrown when a thread is waiting, sleeping, or otherwise
occupied and the thread is interrupted.

try { Thread.sleep(1000); } catch (InterruptedException e) {


e.printStackTrace(); }

8. ClassNotFoundException:

59
 Thrown when an application tries to load a class through its
string name using methods like Class.forName, but the
specified class cannot be found.
try { Class.forName("com.example.NonExistentClass"); //
ClassNotFoundException } catch (ClassNotFoundException e) {
e.printStackTrace(); }

These are just a few examples of the many built-in exceptions


provided by Java. It's important to understand the types of
exceptions that can be thrown in various scenarios to handle them
appropriately in your code.

Unit-III: Constructor, Wrapper, String and StringBuffer


Class in Java
Constructors in Java:

A constructor in Java is a special method used for initializing objects.

It has the same name as the class and is invoked when an object of
the class is created.

Constructors do not have a return type.

Example:

public class Car {

String model;

int year;

// Default constructor

public Car() {

model = "Unknown";
60
year = 2022;

// Parameterized constructor

public Car(String modelName, int releaseYear) {

model = modelName;

year = releaseYear;

public void displayDetails() {

System.out.println("Model: " + model);

System.out.println("Year: " + year);

public static void main(String[] args) {

// Creating objects using different constructors

Car defaultCar = new Car(); // Default constructor

Car customCar = new Car("Toyota", 2020); //


Parameterized constructor

// Displaying details of cars

System.out.println("Default Car Details:");

61
defaultCar.displayDetails();

System.out.println("\nCustom Car Details:");

customCar.displayDetails();

Output:

Default Car Details:

Model: Unknown

Year: 2022

Custom Car Details:

Model: Toyota

Year: 2020

Various Types of Constructor:


In Java, constructors are special methods used to initialize objects.
They are called when an object is created using the new keyword.
There are several types of constructors in Java:

1. Default Constructor:

 A constructor with no parameters.

62
 Automatically provided by Java if no constructors are explicitly
defined in a class.

Syntax:

public class MyClass {

// Default constructor (automatically provided if not explicitly


defined)

2. Parameterized Constructor:

 A constructor with one or more parameters.


 Used to initialize object properties with provided values.

Syntax:

public class Car {

String model;

int year;

// Parameterized constructor

public Car(String modelName, int releaseYear) {

model = modelName;

year = releaseYear;

3. Copy Constructor:

63
 A constructor that creates an object by copying the values of another
object.
 Helps in creating a new object with the same values as an existing
object.

Syntax:

public class Student {

String name;

int age;

// Copy constructor

public Student(Student otherStudent) {

name = otherStudent.name;

age = otherStudent.age;

4. Constructor Overloading:

 Defining multiple constructors in a class with different parameter


lists.
 Allows creating objects using different sets of parameters.

public class Book {

String title;

String author;

64
// Constructor with one parameter

public Book(String bookTitle) {

title = bookTitle;

author = "Unknown";

// Constructor with two parameters

public Book(String bookTitle, String bookAuthor) {

title = bookTitle;

author = bookAuthor;

5. Private Constructor:

 A constructor marked as private.


 Prevents the instantiation of a class from outside the class itself.

public class Singleton {

private static Singleton instance;

// Private constructor

private Singleton() {

65
// Initialization code

public static Singleton getInstance() {

if (instance == null) {

instance = new Singleton();

return instance;

These various types of constructors provide flexibility in creating and


initializing objects based on different scenarios and requirements in
Java

Role of Constructors in inheritance in Java:


In Java, constructors play a crucial role in inheritance, facilitating the
initialization of both the subclass and superclass components during
object creation. Let's explore this with an example:

// Base class (superclass)

class Vehicle {

String type;

// Constructor for Vehicle

66
public Vehicle(String vehicleType) {

type = vehicleType;

System.out.println("Vehicle Constructor called with type: " +


type);

// Derived class (subclass) inheriting from Vehicle

class Car extends Vehicle {

int numDoors;

// Constructor for Car

public Car(String carType, int doors) {

super(carType); // Calling the superclass constructor

numDoors = doors;

System.out.println("Car Constructor called with " + numDoors +


" doors");

public class InheritanceExample {

public static void main(String[] args) {

67
// Creating an object of the subclass Car

Car myCar = new Car("Sedan", 4);

// Accessing properties from both superclass and subclass

System.out.println("Vehicle Type: " + myCar.type);

System.out.println("Number of Doors: " + myCar.numDoors);

Output:

Vehicle Constructor called with type: Sedan

Car Constructor called with 4 doors

Vehicle Type: Sedan

Number of Doors: 4

Explanation:

 The Vehicle class has a constructor that takes a String parameter


representing the type of the vehicle.
 The Car class extends Vehicle and has an additional property
numDoors.
 The constructor of Car calls the constructor of the superclass
(Vehicle) using super(carType). This ensures that the initialization
logic in the superclass is executed before the subclass's own
initialization.

68
 When creating an object of Car (myCar), both the constructor of
Vehicle and Car are invoked, and the output shows the sequence of
constructor calls.

Constructors in inheritance help in achieving proper initialization of


both the subclass and superclass components, ensuring that the
entire object hierarchy is appropriately set up. They enable code
reuse and promote a clear and organized structure in object-oriented
design.

Introduction to Wrapper Classes:


Wrapper classes in Java provide a way to convert primitive data
types into objects.

In Java, everything is an object, and primitive data types (like int,


char, double, etc.) are not objects.

Wrapper classes encapsulate primitive data types in objects, allowing


them to be used in situations where objects are required.

There are eight wrapper classes in Java, each corresponding to a


primitive data type:

1. Byte: java.lang.Byte
2. Short: java.lang.Short
3. Integer: java.lang.Integer
4. Long: java.lang.Long
5. Float: java.lang.Float
6. Double: java.lang.Double
7. Character: java.lang.Character
8. Boolean: java.lang.Boolean

Example:

public class WrapperExample {

69
public static void main(String[] args) {
// Primitive data types
int primitiveInt = 42;
double primitiveDouble = 3.14;

// Wrapper classes
Integer wrappedInt = Integer.valueOf(primitiveInt);
Double wrappedDouble = Double.valueOf(primitiveDouble);

// Unwrapping (converting wrapper objects back to primitives)


int unwrappedInt = wrappedInt.intValue();
double unwrappedDouble = wrappedDouble.doubleValue();

System.out.println("Original primitive int: " + primitiveInt);


System.out.println("Wrapped Integer: " + wrappedInt);
System.out.println("Unwrapped int: " + unwrappedInt);

System.out.println("\nOriginal primitive double: " +


primitiveDouble);
System.out.println("Wrapped Double: " + wrappedDouble);
System.out.println("Unwrapped double: " + unwrappedDouble);
}
}
Output:

70
Original primitive int: 42
Wrapped Integer: 42
Unwrapped int: 42

Original primitive double: 3.14


Wrapped Double: 3.14
Unwrapped double: 3.14

String Operations is java:


String operations in Java involve manipulating and performing
various tasks with strings, which are sequences of characters. Java
provides a rich set of methods and operations for working with
strings. Here are some common string operations:

1. Concatenation:

Concatenation is the process of combining two strings.

public class StringExample {


public static void main(String[] args) {
String str1 = "Hello";
String str2 = " World";
String result = str1 + str2;
System.out.println(result);
}
}
Output:

71
Hello World

2. Length:

Getting the length of a string using the length() method.

public class StringExample {


public static void main(String[] args) {
String text = "Java Programming";
int length = text.length();
System.out.println("Length of the string: " + length);
}
}
Output:
Length of the string: 17
3. Substring:

Extracting a portion of a string using the substring() method.

public class StringExample {


public static void main(String[] args) {
String original = "Hello World";
String subString = original.substring(6); // Extracts from
index 6 to the end
System.out.println(subString);
}

72
}
Output:
World

4. Concatenating with Other Data Types:

Combining strings with other data types using the + operator

public class StringExample {


public static void main(String[] args) {
int number = 42;
String result = "The answer is: " + number;
System.out.println(result);
}
}
Output:
The answer is: 42

5. Replacing Characters:

Replacing characters in a string using replace().

public class StringExample {


public static void main(String[] args) {
String original = "I like Java";
String modified = original.replace("Java", "Python");
System.out.println(modified);
}

73
}
Output:
I like Python

6. Converting Case:

Changing the case of a string using toUpperCase() and


toLowerCase().

public class StringExample {


public static void main(String[] args) {

String original = "Hello World"; String upperCase =


original.toUpperCase(); String lowerCase =
original.toLowerCase(); System.out.println("Upper Case: " +
upperCase); System.out.println("Lower Case: " + lowerCase);
}
}

Output:

Upper Case: HELLO WORLD


Lower Case: hello world

7. Comparing Strings:

Comparing strings using equals() or equalsIgnoreCase().

public class StringExample {


public static void main(String[] args) {

String str1 = "Java"; String str2 = "java"; boolean isEqual =


str1.equals(str2); // Case-sensitive boolean isEqualIgnoreCase
= str1.equalsIgnoreCase(str2); // Case-insensitive
System.out.println("Equal: " + isEqual);
74
System.out.println("Equal (Ignore Case): " +
isEqualIgnoreCase);
}
}

Output:

Equal: false Equal (Ignore Case): true

Immutability:
In Java, strings are immutable, which means their values cannot be
changed after they are created.
Any operation that appears to modify a string actually creates a new
string.
This immutability has several benefits, such as thread safety,
simplicity, and ease of use.
Here's an example illustrating string immutability:
public class StringImmutabilityExample {
public static void main(String[] args) {
// Original string
String originalString = "Hello";

// Concatenating strings (creates a new string)


String concatenatedString = originalString + " World";

// Reassigning a value (creates a new string)


originalString = originalString + ", Java!";

75
System.out.println("Original String: " + originalString);
System.out.println("Concatenated String: " +
concatenatedString);

// Demonstrating immutability
System.out.println("Original String after operations: " +
originalString);
}
}
Output:
Original String: Hello, Java!
Concatenated String: Hello World
Original String after operations: Hello, Java!

Immutability ensures that once a string is created, its value cannot be


modified. This property is beneficial in scenarios where consistency
and predictability are essential, especially in a multi-threaded
environment. It also simplifies memory management and helps
prevent unintended side effects when working with strings.

Creating and Initializing Strings using methods of String and


StringBuffer Class :
Let's explore creating and initializing strings using methods of the
String and StringBuffer classes in Java.

1.. Creating and Initializing Strings using String Class:

76
public class StringExample {
public static void main(String[] args) {
// Using string literal
String str1 = "Hello, World!";

// Using constructor
String str2 = new String("Java Programming");

// Using char array


char[] charArray = {'H', 'e', 'l', 'l', 'o'};
String str3 = new String(charArray);

// Using concatenation
String str4 = "Hello" + ", " + "Java!";

// Using valueOf method


int number = 42;
String str5 = String.valueOf(number);

// Displaying strings
System.out.println(str1);
System.out.println(str2);
System.out.println(str3);
System.out.println(str4);

77
System.out.println(str5);
}
}
Output:
Hello, World!
Java Programming
Hello
Hello, Java!
42

2. Creating and Initializing Strings using StringBuffer Class:

public class StringBufferExample {


public static void main(String[] args) {
// Using constructor
StringBuffer buffer1 = new StringBuffer("Hello");

// Using append method


StringBuffer buffer2 = new StringBuffer();
buffer2.append("Java ").append("Programming");

// Using insert method


StringBuffer buffer3 = new StringBuffer("World");
buffer3.insert(0, "Hello, ");

78
// Using reverse method
StringBuffer buffer4 = new StringBuffer("olleH");

// Displaying string buffers


System.out.println(buffer1);
System.out.println(buffer2);
System.out.println(buffer3);
System.out.println(buffer4.reverse());
}
}
Output:
Hello
Java Programming
Hello, World
Hello

In the examples:

 For the String class, various ways to create and initialize strings are
demonstrated, including using literals, constructors, char arrays,
concatenation, and the valueOf method.
 For the StringBuffer class, initialization is shown through
constructors, appending, inserting, and reversing operations.

These examples illustrate different approaches to create and


initialize strings, whether using the immutable String class or the

79
mutable StringBuffer class, depending on the requirements of your
application.

Unit-IV: Interface and Threads in Java


Interfaces:-
An interface in Java is a collection of abstract methods.

It allows you to achieve abstraction and multiple inheritance.

Any class that implements an interface must provide implementations


for all its abstract methods.

Here's how you define an interface in Java:


// Interface definition
public interface MyInterface {
// Abstract method declaration (no method body)
void myMethod();

// Another abstract method


int calculate(int x, int y);

// Constant field (implicitly public, static, and final)


String INTERFACE_NAME = "MyInterface";

// Default method with a body (added in Java 8)


default void defaultMethod() {
System.out.println("Default method implementation");

80
}

// Static method (added in Java 8)


static void staticMethod() {
System.out.println("Static method implementation");
}
}

In this example:

 MyInterface is the interface name.


 myMethod and calculate are abstract methods without method
bodies. Any class implementing this interface must provide concrete
implementations for these methods.
 INTERFACE_NAME is a constant field, implicitly public, static, and
final. It can be accessed using the interface name
(MyInterface.INTERFACE_NAME).
 defaultMethod is a default method, which provides a default
implementation. Classes implementing the interface can use this
implementation or override it.
 staticMethod is a static method, which is part of the interface and
can be called using the interface name (MyInterface.staticMethod()).

When a class implements an interface, it must provide concrete


implementations for all the abstract methods declared in the
interface. Here's an example:

// Class implementing the interface

public class MyClass implements MyInterface {

// Implementation of abstract methods

@Override
81
public void myMethod() {

System.out.println("Implementation of myMethod");

@Override

public int calculate(int x, int y) {

return x + y;

// Optional: Override default method if needed

@Override

public void defaultMethod() {

System.out.println("Custom implementation of
defaultMethod");

In summary, interfaces in Java define a blueprint of methods and


constants that implementing classes must adhere to. They play a
crucial role in achieving abstraction, allowing for a level of flexibility
in designing and organizing code.

Abstract Methods in Interfaces:

82
In Java interfaces, abstract methods are methods that are declared
without a body. Any class implementing the interface must provide a
concrete implementation for these abstract methods. Abstract methods
in interfaces are implicitly public and abstract, and they do not have a
method body.

Here's an example of an interface with abstract methods:

// Interface with abstract methods


public interface MyInterface {
// Abstract method declaration (no method body)
void myMethod();

// Another abstract method


int calculate(int x, int y);
}

In this example, myMethod and calculate are abstract methods. Any


class that implements MyInterface must provide concrete
implementations for these methods. For instance:

// Class implementing the interface


public class MyClass implements MyInterface {
// Implementation of abstract methods
@Override
public void myMethod() {
System.out.println("Implementation of myMethod");
}

@Override
public int calculate(int x, int y) {
83
return x + y;
}
}

In the implementing class MyClass, both myMethod and calculate


are provided with concrete implementations.

Implementing Interface:
Implementing interfaces in Java involves providing concrete
implementations for all the abstract methods declared in the
interface.
Here's an example:
// Interface with abstract methods
interface Shape {
double calculateArea();

void display();
}

// Class implementing the interface


class Circle implements Shape {
private double radius;

// Constructor

84
public Circle(double radius) {
this.radius = radius;
}

// Implementation of abstract methods


@Override
public double calculateArea() {
return Math.PI * radius * radius;
}

@Override
public void display() {
System.out.println("Circle Area: " + calculateArea());
}
}

// Another class implementing the same interface


class Rectangle implements Shape {
private double length;
private double width;

// Constructor
public Rectangle(double length, double width) {
this.length = length;

85
this.width = width;
}

// Implementation of abstract methods


@Override
public double calculateArea() {
return length * width;
}

@Override
public void display() {
System.out.println("Rectangle Area: " + calculateArea());
}
}

public class InterfaceImplementationExample {


public static void main(String[] args) {
// Creating objects of classes implementing the interface
Circle circle = new Circle(5);
Rectangle rectangle = new Rectangle(4, 6);

// Invoking methods through the interface


circle.display();
rectangle.display();

86
}
}
Output:
Circle Area: 78.53981633974483
Rectangle Area: 24.0

In this example:

 The Shape interface declares two abstract methods: calculateArea


and display.
 The Circle class and Rectangle class both implement the Shape
interface.
 Each class provides concrete implementations for the abstract
methods declared in the interface.
 In the main method, objects of Circle and Rectangle are created, and
the display method is invoked through the interface.

Implementing interfaces allows for a consistent way to define and


enforce behavior across different classes. It promotes code
reusability, flexibility, and polymorphism, as objects of different
classes that implement the same interface can be treated uniformly.

Extending Interfaces:
In Java, interfaces can extend other interfaces, allowing the creation
of hierarchies of interfaces.

When one interface extends another, it inherits the abstract methods


(and constants) declared in the parent interface.

The extending interface can also declare additional abstract methods.

87
A class implementing an interface that extends another interface must
provide concrete implementations for all the abstract methods in the
entire hierarchy.

Here's an example of extending interfaces:

// Parent interface

interface Shape {

double calculateArea();

// Child interface extending the parent interface

interface ColoredShape extends Shape {

String getColor(); // Additional abstract method

// Class implementing the child interface

class ColoredCircle implements ColoredShape {

private double radius;

private String color;

// Constructor

public ColoredCircle(double radius, String color) {

this.radius = radius;

88
this.color = color;

// Implementation of abstract methods

@Override

public double calculateArea() {

return Math.PI * radius * radius;

@Override

public String getColor() {

return color;

public class InterfaceExtensionExample {

public static void main(String[] args) {

// Creating an object of the class implementing the child interface

ColoredCircle coloredCircle = new ColoredCircle(5, "Red");

// Invoking methods through the interfaces

89
System.out.println("Circle Area: " +
coloredCircle.calculateArea());

System.out.println("Circle Color: " + coloredCircle.getColor());

Output:

Circle Area: 78.53981633974483

Circle Color: Red

In conclusion :-
Extending interfaces helps in building a more organized and modular
design, allowing for the creation of specific interfaces with additional
functionality while maintaining a connection to a common set of
methods. Classes implementing these interfaces can then provide
specialized implementations.
Interface References:

In Java, interface references provide a way to achieve polymorphism,


allowing objects of different classes that implement the same interface
to be treated uniformly through the interface reference. This promotes
flexibility and code reuse.

Here's an example illustrating the use of interface references:

// Interface

interface Shape {

double calculateArea();

90
}

// Classes implementing the interface

class Circle implements Shape {

private double radius;

public Circle(double radius) {

this.radius = radius;

@Override

public double calculateArea() {

return Math.PI * radius * radius;

class Rectangle implements Shape {

private double length;

private double width;

public Rectangle(double length, double width) {

91
this.length = length;

this.width = width;

@Override

public double calculateArea() {

return length * width;

public class InterfaceReferenceExample {

public static void main(String[] args) {

// Using interface references

Shape circle = new Circle(5);

Shape rectangle = new Rectangle(4, 6);

// Invoking methods through the interface references

System.out.println("Circle Area: " + circle.calculateArea());

System.out.println("Rectangle Area: " +


rectangle.calculateArea());

// You can also have an array or a list of interface references

92
Shape[] shapes = {new Circle(3), new Rectangle(2, 4)};

// Iterating through the array and invoking methods

for (Shape shape : shapes) {

System.out.println("Shape Area: " +


shape.calculateArea());

Output:
Circle Area: 78.53981633974483
Rectangle Area: 24.0
Shape Area: 28.274333882308138
Shape Area: 8.0

Default Methods in Interfaces:


Default methods in interfaces were introduced in Java 8 to provide a
way to add new methods to interfaces without breaking existing
implementations. Default methods have a default implementation in
the interface itself, and classes that implement the interface can
choose to use the default implementation or override it.

Here's an example of default methods in interfaces:

// Interface with a default method

93
interface Greeting {

// Abstract method

void greet();

// Default method with a body

default void defaultGreeting() {

System.out.println("Default Greeting");

// Class implementing the interface

class EnglishGreeting implements Greeting {

// Implementation of abstract method

@Override

public void greet() {

System.out.println("Hello");

// The defaultGreeting method is not overridden

94
public class DefaultMethodExample {

public static void main(String[] args) {

// Creating an object of the implementing class

EnglishGreeting englishGreeting = new EnglishGreeting();

// Invoking methods

englishGreeting.greet(); // Invokes the implemented greet


method

englishGreeting.defaultGreeting(); // Invokes the defaultGreeting


method

Output:

Hello

Default Greeting

Static Methods in Interfaces:


Static methods in interfaces were introduced in Java 8 as a way to
provide utility methods associated with an interface. Unlike default
methods, static methods cannot be overridden by implementing
classes. Instead, they are called using the interface name.

Here's an example of static methods in interfaces:

// Interface with a static method

95
interface MathOperation {

// Abstract method

int operate(int a, int b);

// Static method with a body

static void description() {

System.out.println("Performing a mathematical operation");

// Class implementing the interface

class Addition implements MathOperation {

// Implementation of abstract method

@Override

public int operate(int a, int b) {

return a + b;

// Another class implementing the interface

class Subtraction implements MathOperation {

96
// Implementation of abstract method

@Override

public int operate(int a, int b) {

return a - b;

public class StaticMethodExample {

public static void main(String[] args) {

// Calling the static method using the interface name

MathOperation.description();

// Creating objects of implementing classes

Addition addition = new Addition();

Subtraction subtraction = new Subtraction();

// Invoking the operate method

int sum = addition.operate(5, 3);

int difference = subtraction.operate(8, 4);

System.out.println("Sum: " + sum);

97
System.out.println("Difference: " + difference);

Output:

Performing a mathematical operation

Sum: 8

Difference: 4

Static methods in interfaces are useful for providing utility methods


related to the interface's functionality. They cannot be overridden by
implementing classes and are called using the interface name,
making them associated with the interface itself rather than
instances of the interface.

Constants in Interfaces Thread: Thread life cycle:


It seems there might be a slight confusion in your request. Let me
clarify two separate topics: Constants in Interfaces and the Thread
Life Cycle in Java.

Constants in Interfaces:

In Java, interface constants are implicitly public, static, and final.


They can be used to declare constant values associated with an
interface.

Here's an example:

// Interface with constants

98
interface Constants {
// Constant fields (implicitly public, static, and final)
int MAX_VALUE = 100;
String DEFAULT_NAME = "Default";
}

// Class using the constants


class MyClass implements Constants {
public void displayConstants() {
System.out.println("Max Value: " + MAX_VALUE);
System.out.println("Default Name: " + DEFAULT_NAME);
}
}

public class ConstantsExample {


public static void main(String[] args) {
// Creating an object of the class
MyClass myObject = new MyClass();

// Displaying constants
myObject.displayConstants();
}
}
Output:

99
Max Value: 100
Default Name: Default

In this example, the Constants interface declares two constants


(MAX_VALUE and DEFAULT_NAME), and the MyClass class
implements this interface, inheriting the constants.

Thread Life Cycle in Java:

The life cycle of a thread in Java consists of several states: New,


Runnable, Blocked, Waiting, Timed Waiting, and Terminated. Here's
a brief overview:

1. New: A thread is in this state before it is started.


2. Runnable: The thread is ready to run and is waiting for the processor
to allocate time.
3. Blocked: The thread is blocked waiting for a monitor lock to enter a
synchronized block/method.
4. Waiting: The thread is waiting indefinitely for another thread to
perform a particular action.
5. Timed Waiting: The thread is waiting for another thread to perform a
particular action, but with a specified waiting time.
6. Terminated: The thread has completed its execution.

Here's a simple example demonstrating the life cycle of a thread:

class MyThread extends Thread {


public void run() {
// Code to be executed by the thread
System.out.println("Thread is running...");
}

100
}

public class ThreadLifeCycleExample {


public static void main(String[] args) {
// Creating a new thread
MyThread myThread = new MyThread();

// Starting the thread (moves it from New to Runnable state)


myThread.start();

// Main thread continues its execution


System.out.println("Main thread is running...");
}
}
Output:
Main thread is running...
Thread is running...

Creating and implementing thread:


n Java, you can create and implement threads by extending the Thread
class or by implementing the Runnable interface. Below are examples
of both approaches:

1. Extending the Thread class:

// Class extending Thread

101
class MyThread extends Thread {

public void run() {

// Code to be executed by the thread

for (int i = 1; i <= 5; i++) {

System.out.println("Thread: " + i);

try {

Thread.sleep(500); // Simulating some work

} catch (InterruptedException e) {

e.printStackTrace();

public class ThreadExample {

public static void main(String[] args) {

// Creating an instance of the thread

MyThread myThread = new MyThread();

// Starting the thread

myThread.start();

102
// Code in the main thread

for (int i = 1; i <= 5; i++) {

System.out.println("Main: " + i);

try {

Thread.sleep(500);

} catch (InterruptedException e) {

e.printStackTrace();

OPutput:
Main: 1
Thread: 1
Thread: 2
Main: 2
Thread: 3
Main: 3
Thread: 4
Main: 4
Thread: 5

103
Main: 5

2. Implementing the Runnable interface:

// Class implementing Runnable


class MyRunnable implements Runnable {
public void run() {
// Code to be executed by the thread
for (int i = 1; i <= 5; i++) {
System.out.println("Thread: " + i);
try {
Thread.sleep(500); // Simulating some work
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

public class RunnableExample {


public static void main(String[] args) {
// Creating an instance of the runnable
MyRunnable myRunnable = new MyRunnable();

104
// Creating a thread using the runnable
Thread thread = new Thread(myRunnable);

// Starting the thread


thread.start();

// Code in the main thread


for (int i = 1; i <= 5; i++) {
System.out.println("Main: " + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
Output:
Main: 1
Thread: 1
Thread: 2
Main: 2
Thread: 3
Main: 3

105
Thread: 4
Main: 4
Thread: 5
Main: 5

In both examples, a separate thread is created and runs concurrently


with the main thread. The sleep method is used to simulate some work
in each thread. Extending Thread or implementing Runnable provides
a way to define the behavior of the thread. Choose the approach based
on your specific requirements and design considerations.

multi-threaded programming:
Multi-threaded programming in Java allows multiple threads to run
concurrently, enabling parallel execution of tasks.
Below is an example of a simple multi-threaded program where
two threads run concurrently:
class MyRunnable implements Runnable {
public void run() {
for (int i = 1; i <= 5; i++) {
System.out.println(Thread.currentThread().getId() + " - Count:
" + i);
try {
Thread.sleep(500); // Simulating some work
} catch (InterruptedException e) {
e.printStackTrace();
}

106
}
}
}

public class MultiThreadExample {


public static void main(String[] args) {
// Creating instances of the runnable
MyRunnable myRunnable1 = new MyRunnable();
MyRunnable myRunnable2 = new MyRunnable();

// Creating threads using the runnables


Thread thread1 = new Thread(myRunnable1);
Thread thread2 = new Thread(myRunnable2);

// Starting the threads


thread1.start();
thread2.start();

// Code in the main thread


for (int i = 1; i <= 5; i++) {
System.out.println("Main Thread - Count: " + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {

107
e.printStackTrace();
}
}
}
}
Output:
11 - Count: 1
12 - Count: 1
Main Thread - Count: 1
12 - Count: 2
11 - Count: 2
Main Thread - Count: 2
12 - Count: 3
11 - Count: 3
Main Thread - Count: 3
11 - Count: 4
12 - Count: 4
Main Thread - Count: 4
11 - Count: 5
12 - Count: 5
Main Thread - Count: 5

In this example:

108
 The MyRunnable class implements the Runnable interface and
defines the behavior of the threads in the run method.
 Two instances of MyRunnable are created, and two threads (thread1
and thread2) are created using these instances.
 The start method is called on each thread to begin their execution
concurrently.
 The main thread also performs its own tasks concurrently with the
other threads.

You can observe that the output shows interleaved execution of


threads, demonstrating the concurrent nature of multi-threaded
programming.

Remember to handle synchronization if multiple threads access


shared resources to avoid potential issues like data inconsistency or
race conditions. The synchronized keyword and other
synchronization mechanisms in Java can be used for this purpose.
Thread priorities:-
In Java, thread priorities are used to influence the scheduling of
threads by the thread scheduler.

Threads with higher priority are more likely to be scheduled for


execution than threads with lower priority.

However, thread priorities do not guarantee the exact order of


execution and can vary depending on the operating system and the
thread scheduler's implementation.

Thread priorities in Java are represented by integers ranging from


Thread.MIN_PRIORITY (1) to Thread.MAX_PRIORITY (10), with
the default priority being Thread.NORM_PRIORITY (5). You can set
the priority of a thread using the setPriority(int priority) method.

Here's a simple example to illustrate thread priorities:

class MyThread extends Thread {

109
public void run() {

for (int i = 1; i <= 3; i++) {

System.out.println(Thread.currentThread().getName() + "
Count: " + i);

public class ThreadPriorityExample {

public static void main(String[] args) {

// Creating threads with different priorities

Thread thread1 = new MyThread();

Thread thread2 = new MyThread();

Thread thread3 = new MyThread();

// Setting thread priorities

thread1.setPriority(Thread.MIN_PRIORITY); // Priority 1

thread2.setPriority(Thread.NORM_PRIORITY); // Priority 5
(default)

thread3.setPriority(Thread.MAX_PRIORITY); // Priority 10

// Starting the threads

110
thread1.start();

thread2.start();

thread3.start();

The output can vary on different systems, but you might


observe that the higher-priority thread tends to get more CPU
time:
Thread-0 Count: 1
Thread-1 Count: 1
Thread-2 Count: 1
Thread-0 Count: 2
Thread-0 Count: 3
Thread-1 Count: 2
Thread-2 Count: 2
Thread-1 Count: 3
Thread-2 Count: 3
It's important to note that thread priorities should be used
judiciously, and relying too much on thread priorities may lead to
platform-dependent behavior. In general, it's recommended to
design your concurrent applications without relying heavily on

111
thread priorities and consider other synchronization mechanisms if
precise control over execution order is required.

synchronization of thread:
In Java, synchronization is a mechanism used to control the access of multiple
threads to shared resources. This ensures that only one thread at a
time can execute a critical section of code, preventing data corruption
or race conditions. The synchronized keyword is used to achieve
synchronization.

Here's a simple example demonstrating synchronization using


a shared resource:
class SharedResource {

private int counter = 0;

// Synchronized method

public synchronized void increment() {

for (int i = 1; i <= 5; i++) {

counter++;

System.out.println(Thread.currentThread().getName() + "
Counter: " + counter);

try {

Thread.sleep(100); // Simulating some work

} catch (InterruptedException e) {

e.printStackTrace();

112
}

class MyThread extends Thread {

private SharedResource sharedResource;

// Constructor to receive the shared resource

public MyThread(SharedResource sharedResource) {

this.sharedResource = sharedResource;

public void run() {

// Calling the synchronized method

sharedResource.increment();

public class SynchronizationExample {

public static void main(String[] args) {

113
// Creating an instance of the shared resource

SharedResource sharedResource = new SharedResource();

// Creating threads with the same shared resource

Thread thread1 = new MyThread(sharedResource);

Thread thread2 = new MyThread(sharedResource);

// Starting the threads

thread1.start();

thread2.start();

The output will demonstrate that only one thread at a time can
execute the increment method, preventing interleaved access
and ensuring the integrity of the shared resource:

Thread-0 Counter: 1
Thread-0 Counter: 2
Thread-0 Counter: 3
Thread-0 Counter: 4
Thread-0 Counter: 5
Thread-1 Counter: 6

114
Thread-1 Counter: 7
Thread-1 Counter: 8
Thread-1 Counter: 9
Thread-1 Counter: 10

Synchronization can also be achieved using synchronized blocks.


The key is to ensure that critical sections of code are protected to
avoid conflicts when multiple threads access shared resources.

resuming and stopping Threads:


In Java, the suspend() and resume() methods were deprecated due
to potential deadlocks and other issues.
Instead, the java.util.concurrent package provides mechanisms for
thread suspension and resumption using CountDownLatch or
Semaphore.
Below is an example using CountDownLatch:
import java.util.concurrent.CountDownLatch;

class MyThread extends Thread {


private CountDownLatch latch;

public MyThread(CountDownLatch latch) {


this.latch = latch;
}

115
public void run() {
System.out.println(Thread.currentThread().getName() + "
started");
try {
// Simulating some work
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "
finished");
latch.countDown(); // Decrements the latch count
}
}

public class ThreadSuspendResumeExample {


public static void main(String[] args) {
CountDownLatch latch = new CountDownLatch(2);

MyThread thread1 = new MyThread(latch);


MyThread thread2 = new MyThread(latch);

thread1.start();
thread2.start();

116
// Main thread waits for both threads to finish
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}

System.out.println("All threads finished");


}
}

In this example:

 MyThread simulates some work in its run method.


 The CountDownLatch is used to synchronize the main thread with
the completion of both worker threads.
 The main thread waits for both worker threads to finish using
latch.await().

Output (may vary):

Thread-0 started

Thread-1 started

Thread-0 finished

Thread-1 finished

All threads finished

117
Keep in mind that direct suspend() and resume() methods are
deprecated due to the issues they can cause, such as potential
deadlocks.
Using modern concurrency utilities like CountDownLatch or
other mechanisms provided by java.util.concurrent is
recommended for synchronization and coordination between
threads.

118

You might also like