[go: up one dir, main page]

0% found this document useful (0 votes)
36 views88 pages

DSA Notes

The document provides an overview of Java programming concepts including access modifiers, constructors, inheritance, and polymorphism. It explains the types of access modifiers, the use of getters and setters, and the different types of constructors such as default and parameterized constructors. Additionally, it covers inheritance types and polymorphism through method overloading and overriding, along with examples illustrating each concept.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
36 views88 pages

DSA Notes

The document provides an overview of Java programming concepts including access modifiers, constructors, inheritance, and polymorphism. It explains the types of access modifiers, the use of getters and setters, and the different types of constructors such as default and parameterized constructors. Additionally, it covers inheritance types and polymorphism through method overloading and overriding, along with examples illustrating each concept.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 88

D

DATA STRUCTURES
&
ALGORITHMS

P HARSHAVARDHAN
2341008007

1
2
Access Modifiers
Access Modifiers specify where a property/method is accessible. There are four types of access
modifiers in java :
• private • protected
• default • public

From the below table, notice


Access Modifier Within Class Within Package Outside Class by Outside Package
sub-class only
Public YES YES YES YES
Protected YES YES YES NO
Default YES YES NO NO
Private YES NO NO NO

that the private access modifier can only be accessed within the class. So, let's try to access private
modifiers outside the class :

class Employee {
private int id;
private String name;
}
public class DSA {
public static void main(String[] args) {
Employee emp1 = new Employee();
emp1.id = 3;
emp1.name = "YSG";
}
}

OUTPUT: java: id has private access in Employee

You can see that the above code produces an error that we're trying to access a private variable outside
the class. So, is there any way by which we can access the private access modifiers outside the class?
The answer is Yes! We can access the private access modifiers outside the class with the help of getters
and setters.

Getters and Setters :

Getter: Returns the value [accessors]


Setter: Sets / updates the value [mutators]

In the below code, we've created total 4 methods:


setName(): The argument passed to this method is assigned to the private variable name.
getName(): The method returns the value set by the setName() method.
setId(): The integer argument passed to this method is assigned to the private variable id.
getId): This method returns the value set by the setId() method.

1
class Employee {
private int id;
private String name;
public String getName(){
return name;
}
public void setName(String n){
name = n;
}
public void setId(int i){
id = i;
}
public int getId(){
return id;
}
}

public class DSA {


public static void main(String[] args) {
Employee emp1 = new Employee();
emp1.setName("YSG");
System.out.println(emp1.getName());
emp1.setId(1);
System.out.println(emp1.getId());
}
}
Output :
YSG
1

As you can see that we've got our expected output. So, that's how we use the getters and setters method
to get and set the values of private access modifiers outside the class.

Constructors in Java :

• Constructors are similar to methods,, but they are used to initialize an object.
• Constructors do not have any return type(not even void).
• Every time we create an object by using the new() keyword, a constructor is called.
• If we do not create a constructor by ourself, then the default constructor(created by Java compiler) is
called.
• Rules for creating a Constructor :
• The class name and constructor name should be the same.
• It must have no explicit return type.
• It cannot be abstract, static, final, and synchronized.

Types of Constructors in Java :


There are two types of constructors in Java :
1. Default constructor : A constructor with 0 parameters is known as default constructor.
Syntax :
<class_name>(){
//code to be executed on the execution of the constructor
}

2
Example :
class DSA {
DSA(){
System.out.println("This is the default constructor of DSA
class.");
}
}
public class DSA_constructors {
public static void main(String[] args) {
DSA obj1 = new DSA();
}
}

Output :
This is the default constructor of DSA class.

In the above code, DSA() is the constructor of class DSA The DSA () constructor is invoked
automatically with the creation of object ob1.

2. Parameterized constructor : A constructor with some specified number of parameters is known as a


parameterized constructor.

Syntax :
<class-name>(<data-type> param1, <data-type> param2,......){
//code to be executed on the invocation of the constructor
}

Example:
class DSA{
DSA (String s, int b){
System.out.println("This is the " +b+ "nd week of "+ " "+ s);
}
}
public class DSA_constructors {
public static void main(String[] args) {
DSA obj1 = new DSA("Coding with Java",2);
}
}

Output :
This is the 2nd week of Coding with Java
In the above example, DSA() constructor accepts two parameters i.e.,
string s and int b.

Constructor Overloading in Java :


Just like methods, constructors can also be overloaded in Java. We can overload the Employee
constructor like below:

public Employee (String n)


name = n;
}

3
Note:
• Constructors can take parameters without being overloaded
• There can be more than two overloaded constructors
• Let's take an example to understand the concept of constructor overloading.

In the below example, the class Employee has a constructor named Employee(). It
takes two argument, i.e., string s & int i. The same constructor is overloaded and
then it accepts three arguments i.e., string s, int i & int salary.

class Employee {
// First constructor
Employee(String s, int i){
System.out.println("The name of the first employee is : " +
s);
System.out.println("The id of the first employee is : " + i);
}
// Constructor overloaded
Employee(String s, int i, int salary){
System.out.println("The name of the second employee is : " +
s);
System.out.println("The id of the second employee is : " + i);
System.out.println("The salary of second employee is : " +
salary);
}
}
public class DSA_constructors {
public static void main(String[] args) {
Employee Ysg = new Employee("ysg",1);
Employee Mr = new Employee("Mr",2,70000);
}
}
Output :
The name of the first employee is : Ysg
The id of the first employee is : 1
The name of the second employee is : Mr
The id of the second employee is : 2
The salary of second employee is : 70000

Create a class cylinder and use getter and setters to set its radius and height.
Use [1] to calculate surface and volume of the cylinder
Use a constructor and repeat [1].
Overload a constructor used to initialize a rectangle of length and breath 5 for using
custom parameters
Repeat [1] for a sphere.

class Cylinder{
private int radius;
private int height;
public Cylinder(int radius, int height) {
this.radius = radius;
this.height = height;
}
4
public int getRadius() {
return radius;
}
public void setRadius(int radius) {
this.radius = radius;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
public double surfaceArea(){
return 2* Math.PI* radius * radius + 2*Math.PI*radius*height;
}
public double volume(){
return Math.PI * radius * radius * height;
}
}
class Rectangle{
private int length;
private int breadth;
public Rectangle() {
this.length = 4;
this.breadth = 5;
}
public Rectangle(int length, int breadth) {
this.length = length;
this.breadth = breadth;
}
public int getLength() {
return length;
}
public int getBreadth() {
return breadth;
}
}
public class dsa_4 {
Rectangle r = new Rectangle(12, 56);
System.out.println(r.getLength());
System.out.println(r.getBreadth());
}
}

Inheritance in Java

Inheritance in Java is a mechanism in which one object acquires all the properties and behaviors of a
parent object. It is an important part of Object Oriented programming system).
The idea behind inheritance in Java is that you can create new classes that are built upon existing classes.
When you inherit from an existing class, you can reuse methods and fields of the parent class. Moreover,
you can add new methods and fields in your current class also.
Inheritance represents the IS-A relationship which is also known as a parent-child relationship.

5
Need For Inheritance:
• For Method Overriding (so runtime polymorphism can be achieved).
• For Code Reusability.

Terms used in Inheritance


Class: A class is a group of objects which have common properties. It is a template or blueprint from
which objects are created.
Sub Class/Child Class: Subclass is a class which inherits the other class. It is also called a derived
class, extended class, or child class.
Super Class/Parent Class: Superclass is the class from where a subclass inherits the features. It is also
called a base class or a parent class.
Reusability: As the name specifies, reusability is a mechanism which facilitates you to reuse the fields
and methods of the existing class when you create a new class. You can use the same fields and methods
already defined in the previous class.

The syntax of Java Inheritance:

class Subclass-name extends Superclass-name


{
//methods and fields
}

The extends keyword indicates that you are making a new class that derives from an existing class. The
meaning of "extends" is to increase the functionality. In the terminology of Java, a class which is
inherited is called a parent or superclass, and the new class is called child or subclass.

Example:
import java.io.*;
// Base or Super Class
class Employee {
int salary = 60000;
}
// Inherited or Sub Class
class Engineer extends Employee {
int benefits = 10000;
}
// Driver Class
class SOA {
public static void main(String args[])
{
Engineer E1 = new Engineer();
System.out.println("Salary : " + E1.salary
+ "\nBenefits : " + E1.benefits);
}
}
Output
Salary : 60000
Benefits : 10000

Types of inheritance in java

On the basis of class, there can be three types of inheritance in java: single, multilevel and hierarchical.
6
In java programming, multiple and hybrid inheritance is supported through interface only.

1. Single Inheritance
In single inheritance, subclasses inherit the features of one superclass. In the image below, class A
serves as a base class for the derived class B.

Example:
class Animal{
void eat(){System.out.println("eating...");}
}
class Dog extends Animal{
void bark(){System.out.println("barking...");}
}
class TestInheritance{
public static void main(String args[]){
Dog d=new Dog();
d.bark();
d.eat();
}
}
Output:
barking...
eating...

2. Multilevel Inheritance
In Multilevel Inheritance, a derived class will be inheriting a base class, and as well as the derived
class also acts as the base class for other classes. In the below image, class A serves as a base class for
the derived class B, which in turn serves as a base class for the derived class C. In Java, a class cannot
directly access the grandparent’s members.

Example:
class Subject{
void study(){System.out.println("study DSA");}
}

7
class Section extends Subject{ void learn()
{System.out.println("learn concept");}
}
class Student extends Section{
void write(){System.out.println("write in exam");}
}
class Multilevel{
public static void main(String args[]){
Student s=new Student();
s.study();
s.learn();
s.write();
}}

Output:
study DSA
learn concept
write in exam

3. Hierarchical Inheritance
In Hierarchical Inheritance, one class serves as a superclass (base class) for more than one subclass.
Here, class A serves as a base class for the derived classes B, C, and D.

Example:
class DSA {
public void print_DSA() { System.out.println("Subject DSA"); }
}
class SectionN2 extends DSA {
public void print_N2() { System.out.println("Section N2"); }
}
class SectionG1 extends DSA {
public void print_G1() { System.out.println("Section G1"); }
}
// Driver Class
public class Hierarchical {
public static void main(String[] args)
{
SectionN2 obj_N2 = new SectionN2();
obj_N2.print_DSA();
obj_N2.print_N2();
SectionG1 obj_G1 = new SectionG1();
obj_G1.print_DSA();
obj_G1.print_G1();
}
}

Output:
Subject DSA
Section N2
Subject DSA
Section G1

Polymorphism: Polymorphism refers to the concept of having many forms. Simply put, it allows a
message to be displayed in multiple forms.
8
Two types:
1. Compile Time Polymorphism: achieved by Method Overloading
2. Run Time Polymorphism: achieved by Method Overriding

Method Overloading & Compile Time Polymorphism


• Compile-Time Polymorphism occurs when an object's functionality is determined at compile-time.
In Java, this is achieved through method overloading. Method overloading allows multiple methods
with the same name but different parameter lists within a single class. During compilation, Java
distinguishes which method to call based on the method signatures. This process is also known as
static or early binding. In essence, compile-time polymorphism ensures that the appropriate method
is bound to an object at compile-time, based on its signature.
• Compile-time polymorphism offers flexibility and clarity in code organization. By defining multiple
methods with the same name but different parameter lists, developers can provide various ways to
interact with objects, enhancing code readability and maintainability. However, it's essential to note
that while method overloading is a form of polymorphism, not all programming languages support it
or rely on it for achieving polymorphic behaviour. Java, however, adopts function overloading at
compile-time, making it a key aspect of polymorphism in Java programming.

Java program to demonstrate compile-time polymorphism


public class DSA {
// First addition function
public static int add(int a, int b)
{
return a + b;
}
// Second addition function
public static double add(double a, double b)
{
return a + b;
}
// Driver code
public static void main(String args[])
{
// Here, the first addition function is called
System.out.println(add(6, 4));
// Here, the second addition function is called
System.out.println(add(6.0, 4.0));
}
}
Output:
10
10.0

Method Overriding & Run-Time Polymorphism:


• In Java, Overriding is a feature that allows a subclass or child class to provide a specific
implementation of a method that is already provided by one of its super-classes or parent classes.
When a method in a subclass has the same name, the same parameters or signature, and the same
return type(or sub-type) as a method in its super-class, then the method in the subclass is said
to override the method in the super-class.
• Method overriding is one of the ways by which Java achieves Run Time Polymorphism (Dynamic
Dispatch Method). The version of a method that is executed will be determined by the object that is
used to invoke it. If an object of a parent class is used to invoke the method, then the version in the

9
parent class will be executed, but if an object of the subclass is used to invoke the method, then the
version in the child class will be executed. In other words, it is the type of the object being referred
to (not the type of the reference variable) that determines which version of an overridden method
will be executed
// Base Class
class A {
void show() {
System.out.println("This is class A");
}
}
// Inherited class
class B extends A {
void show()
{
System.out.println("This is class B");
}
}
// Driver class
class Main {
public static void main(String[] args)
{
// If a A type reference refers to a A object, then A's show is
called
A obj1 = new A();
obj1.show();
// If a A type reference refers to a B object B's show() is
called. This is called RUN TIME POLYMORPHISM.
A obj2 = new B();
obj2.show();
}
}

Output:
This is class A
This is class B

class Animal {
public void animalSound() {
System.out.println("The animal makes a sound");
}
}
class Pig extends Animal {
public void animalSound() {
System.out.println("The pig says: wee wee");
}
}
class Dog extends Animal {
public void animalSound() {
System.out.println("The dog says: bow wow");
}
}
class Main {
public static void main(String[] args) {

10
Animal myAnimal = new Animal();
Animal myPig = new Pig();
Animal myDog = new Dog();
myAnimal.animalSound();
myPig.animalSound();
myDog.animalSound();
}
}

Output:
The animal makes a sound
The pig says: wee wee
The dog says: bow wow

Rules for Java Method Overriding


1. Overriding and Access Modifiers
When overriding a method in Java, the access modifier in the subclass can be the same as or more
permissive than the access modifier in the superclass. However, it cannot be more restrictive. For
instance, if a method in the superclass is declared as `protected`, the overriding method in the subclass
can be made `public` or `protected`, but not `private`. Attempting to make it `private` will result in a
compilation error.

A Simple Java program to demonstrate Overriding and Access-Modifiers

class Parent {
// private methods are not overridden
private void p1()
{
System.out.println("Its Parent p1");
}
protected void p2()
{
System.out.println("Its Parent p2”);
}
}
class Child extends Parent {
// new p1() method unique to Child class
private void p1()
{
System.out.println("Its child p1");
}
// overriding method with more accessibility
@Override public void p2()
{
System.out.println(“Its child p2");
}
}
class Main {
public static void main(String[] args)
{
Parent obj1 = new Parent();
obj1.p2();
Parent obj2 = new Child();
obj2.p2();
11
}
}

Output:
Its Parent p2
Its child p2

2. Final methods cannot be overridden


If we don’t want a method to be overridden, we declare it as final.

A Java program to demonstrate that final methods cannot be overridden

class A {
// Can't be overridden
final void display() {}
}
class B extends A {
// This would produce error
void display() {}
}
Output:
13: error: display() in B cannot override display() in A
void display() { }
^
overridden method is final

3. Static methods cannot be overridden(Method Overriding vs Method Hiding):


When you define a static method in a subclass with the same signature as a static method in the
superclass, it's termed as method hiding. The following table outlines the behavior when you define a
method with the same signature as a method in the superclass:

SuperClass Subclass
Result
Method Method
Method hiding. The subclass method hides the superclass
Static Static
method.
No hiding occurs. The subclass method is unrelated to the
Static Instance
superclass method.
No hiding occurs. The subclass method is unrelated to the
Instance Static
superclass method.
Method overriding. The subclass method overrides the superclass
Instance Instance
method.

Java program to show that if the static method is redefined by a derived class, then it is
not overriding, it is hiding

class Parent {
// Static method in base class
// which will be hidden in subclass
static void p1()
{
System.out.println("Its parent static p1");

12
}
// Non-static method which will be overridden in derived class
void p2()
{
System.out.println(
" Its parent static non - static(instance) p2 ");
}
}
class Child extends Parent {
// This method hides p1() in Parent
static void p1()
{
System.out.println("Its child static p1");
}
// This method overrides p2() in Parent
@Override public void p2()
{
System.out.println(
"Its child non - static(instance) p2 ");
}
}
// Driver class
class Main {
public static void main(String[] args)
{
Parent obj1 = new Child();
// As per overriding rules this should call to class Child
static
// overridden method. Since static method cannot be overridden,
it
// calls Parent's p1()
obj1.p1();
// Here overriding works and Child's p2() is called
obj1.p2();
}
}
Output:
Its parent static p1
Its child non- static p2

4. Private methods cannot be overridden


Private methods cannot be overridden in Java because they are bound during compile time. Therefore,
it's not possible to override private methods in a subclass.

Example:
class A {
private void privateMethod()
{
System.out.println(
"This is a private method in A");
}

public void publicMethod()


{
13
System.out.println(
"This is a public method in A");
privateMethod();
}
}

class B extends A {
// This is a new method with the same name as the
// private method in A
private void privateMethod()
{
System.out.println(
"This is a private method in B");
}

// This method overrides the public method in A


public void publicMethod()
{
System.out.println(
"This is a public method in B");
privateMethod(); // calls the private method in B, not A
}
}

public class Main {


public static void main(String[] args)
{
A obj1 = new A();
obj1.publicMethod(); // calls the public method in A
B obj2 = new B();
obj2.publicMethod(); // calls the overridden public method in B
}
}
Output:
This is a public method in A
This is a private method in A
This is a public method in B
This is a private method in B

5. The overriding method must have the same return type (or subtype)
From Java 5.0 onwards it is possible to have different return types for an overriding method in the
child class, but the child’s return type should be a sub-type of the parent’s return type.

Java Program to Demonstrate Different Return Types if Return Type in


Overridden method is Sub-type

// Class 1
class A {
}
// Class 2
class B extends A {
}
// Class 3

14
// Helper class (Base class)
class Base {
// Method of this class of class1 return type
A fun()
{
// Display message only
System.out.println("Base fun()");
return new A();
}
}
// Class 4
// Helper class extending above class
class Derived extends Base {
// Method of this class of class1 return type
B fun()
{
// Display message only
System.out.println("Derived fun()");

return new B();


}
}
// Class 5
// Main class
public class GFG {
// Main driver method
public static void main(String args[])
{
// Creating object of class3 type
Base base = new Base();
// Calling method fun() over this object
// inside main() method
base.fun();
// Creating object of class4 type
Derived derived = new Derived();
// Again calling method fun() over this object
// inside main() method
derived.fun();
}
}

Output:
Base fun()
Derived fun()

6. Invoking overridden method from sub-class


We can call the parent class method in the overriding method using the super keyword.

// A Java program to demonstrate that overridden method can be called


from sub-class
// Base Class
class Parent {
void show() { System.out.println("Parent's show()"); }
}
15
// Inherited class
class Child extends Parent {
// This method overrides show() of Parent
@Override void show()
{
super.show();
System.out.println("Child's show()");
}
}

// Driver class
class Main {
public static void main(String[] args)
{
Parent obj = new Child();
obj.show();
}
}
Output:
Parent's show()
Child's show()

Write a Java program to create a base class Shape with a method called
calculateArea(). Create three subclasses: Circle, Rectangle, and Triangle. Override
the calculateArea() method in each subclass to calculate and return the shape's area.

// Shape.java
// Base class Shape
public class Shape {
public double calculateArea() {
return 0; // Default implementation returns 0
}
}

// Circle.java
// Subclass Circle
public class Circle extends Shape {
private double radius;
public Circle(double radius) {
this.radius = radius;
}
@Override
public double calculateArea() {
return Math.PI * radius * radius; // Calculate area of circle
}
}
// Rectangle.java
// Subclass Rectangle
public class Rectangle extends Shape {
private double width;
private double height;
public Rectangle(double width, double height) {
this.width = width;

16
this.height = height;
}
@Override
public double calculateArea() {
return width * height; // Calculate area of rectangle
}
}
// Triangle.java
// Subclass Triangle
public class Triangle extends Shape {
private double base;
private double height;
public Triangle(double base, double height) {
this.base = base;
this.height = height;
}
@Override
public double calculateArea() {
return 0.5 * base * height; // Calculate area of triangle
}
}
// Main.java
// Main class
public class Main {
public static void main(String[] args) {
Circle circle = new Circle(4);
System.out.println("Area of Circle: " +
circle.calculateArea());
Rectangle rectangle = new Rectangle(12, 34);
System.out.println("\nArea of Rectangle: " +
rectangle.calculateArea());
Triangle triangle = new Triangle(5, 9);
System.out.println("\nArea of Triangle: " +
triangle.calculateArea());
}
}
Output:
Area of Circle: 50.26548245743669
Area of Rectangle: 408.0
Area of Triangle: 22.5

Write a Java program to create a class Employee with a method called


calculateSalary(). Create two subclasses Manager and Programmer. In each
subclass, override the calculateSalary() method to calculate and return the salary
based on their specific roles.

// Employee.java
// Base class Employee
class Employee {
private String name;
private String role;
public Employee(String name, String role) {
this.name = name;

17
this.role = role;
}
public String getName() {
return name;
}
public String getRole() {
return role;
}
public double calculateSalary() {
return 0.0;
}
}
// Manager.java
// Subclass Manager
class Manager extends Employee {
private double baseSalary;
private double bonus;
public Manager(String name, double baseSalary, double bonus) {
super(name, "Manager");
this.baseSalary = baseSalary;
this.bonus = bonus;
}
@Override
public double calculateSalary() {
return baseSalary + bonus;
}
}
// Programmer.java
// Subclass Programmer
class Programmer extends Employee {
private double baseSalary;
private double overtimePay;
public Programmer(String name, double baseSalary, double
overtimePay) {
super(name, "Programmer");
this.baseSalary = baseSalary;
this.overtimePay = overtimePay;
}
@Override
public double calculateSalary() {
return baseSalary + overtimePay;
}
}
// Main.java
// Main class
public class Main {
public static void main(String[] args) {
Employee emp1 = new Manager("Lilo Heidi", 7500.0, 1500.0);
Employee emp2 = new Programmer("Margrit Cathrin", 5000.0,
600.0);
System.out.println("Manager: " + emp1.getName() + "\nRole: "
+ emp1.getRole() + "\nSalary: $" + emp1.calculateSalary());
System.out.println("\nProgrammer: " + emp2.getName() +
"\nRole: " + emp2.getRole() + "\nSalary: $" +
emp2.calculateSalary());
18
}
}
Output:
Manager: Lilo Heidi
Role: Manager
Salary: $9000.0
Programmer: Margrit Cathrin
Role: Programmer
Salary: $5600.0

Write a Java program to create a base class Sports with a method called play().
Create three subclasses: Football, Basketball, and Rugby. Override the play()
method in each subclass to play a specific statement for each sport.

// Sports.java
// Base class Sports
class Sports {
public void play() {
System.out.println("Playing a sport...\n");
}
}
// Football.java
// Subclass Football
class Football extends Sports {
@Override
public void play() {
System.out.println("Playing football...");
}
}
// Basketball.java
// Subclass Basketball
class Basketball extends Sports {
@Override
public void play() {
System.out.println("Playing basketball...");
}
}
// Rugby.java
// Subclass Rugby
class Rugby extends Sports {
@Override
public void play() {
System.out.println("Playing rugby...");
}
}
// Main.java
// Main class
public class Main {
public static void main(String[] args) {
Sports sports = new Sports();
Football football = new Football();
Basketball basketball = new Basketball();
Rugby rugby = new Rugby();
sports.play();

19
football.play();
basketball.play();
rugby.play();
}
}
Sample Output:
Playing a sport...
Playing football...
Playing basketball...
Playing rugby...

JAVA ABSTRACTION
• Abstraction is a process of hiding the implementation details and showing only functionality to the
user.
• Another way, it shows only essential things to the user and hides the internal details, for example,
sending SMS where you type the text and send the message. You don't know the internal processing
about the message delivery.
• Abstract Classes and Methods:
• An abstract class is a class that is declared abstract—it may or may not include abstract methods.
Abstract classes cannot be instantiated, but they can be subclassed.
• An abstract method is a method that is declared without an implementation (without braces, and
followed by a semicolon), like this:
• abstract void moveTo(double deltaX, double deltaY);
If a class includes abstract methods, then the class itself must be declared abstract, as in:

public abstract class GraphicObject {


// declare fields
// declare nonabstract methods
abstract void draw();
}

Example of Abstract Class that has Abstract method

// Abstract class
abstract class Student {
abstract void printInfo();
}
// Abstraction performed using extends
class Employee extends Student {
voidprintInfo()
{
String name = "Rohan";
int age = 25;
float salary = 3000.5F;
System.out.println(name);
System.out.println(age);
System.out.println(salary);
}
}
// Base class
class Base {
public static void main(String args[])
{
20
Student s = new Employee();
s.printInfo();
}
}
Output:
Rohan
25
3000.5

Abstract Class having constructor, data member, and methods

Java Program to implement Abstract Class having constructor, data member, and
methods

import java.io.*;
abstract class Subject {
Subject() {
System.out.println("Learning Subject");
}

abstract void topics();


void Learn(){
System.out.println("Understanding syllabus");
}
}
class DSAextends Subject {
void topics(){
System.out.println(" Java , OOPS, Stack");
}
}
class Main {
public static void main(String[] args) {
Subject x=new DSA();
x.topics();
x.Learn();
}
}
Output:
Learning Subject
Java , OOPS, Stack
Understanding syllabus

Java Program to Illustrate Abstract Class containing Constructors

abstract class Parent {


// Constructor of class Parent
Parent()
{
System.out.println("Parent Constructor Called");
}
abstract void disp();// Abstract method
}
class Child extends Parent {

21
Child() // Constructor of child
{
System.out.println("Child Constructor Called");
}
voiddisp()
{
System.out.println("Child disp() called");
}
}
// Main class
class DSA {
public static void main(String args[])
{
Child d = new Child();
d.disp();
}
}

Output:
Parent Constructor Called
Child Constructor Called
Child disp() called

Java Program to illustrate Abstract class without any abstract method

abstract class B {
void print() // Regular method, not an abstract method.
{
System.out.println("B class is called");
}
}
class D extends B {
}
class Main {
public static void main(String args[])
{
D d = new D();
d.print();
}
}
Output:
B class is called

Java Program to Illustrate Abstract classes can also have final methods

abstract class B {
final void print()
{
System.out.println("Base print() called");
}
}

class D extends B {
22
}
Class DSA {
public static void main(String args[])
{
{
B b = new D();
b.print();
}
}
}
Output:
Base print() called

If the Child class cannot implement all the abstract methods of the Parent class, it's advisable to mark
that Child class as abstract. This ensures that any subsequent Child class can then implement the
remaining abstract method(s).

import java.io.*;
abstract class D {
abstract void dsa1();
abstract void dsa2();
abstract void dsa3();
}

abstract class Child1 extends D {


public void dsa1() {
System.out.println("Inside dsa1");
}
}

class Child2 extends Child1 {


public void dsa2() {
System.out.println("Inside dsa2");
}
public void dsa3() {
System.out.println("Inside dsa3");
}
}

class DSA {
public static void main(String[] args)
{
// Child1 c1=new Child1();
// c1.dsa1(); // will throw error since it is abstract

Child2 c2 = new Child2();


c2.dsa1();
c2.dsa2();
c2.dsa3();
}
}
Output:
Inside dsa1
Inside dsa2
23
Inside dsa3

[Abstraction lets you focus on what the object does instead of how it does it.]
Ways to achieve Abstraction
There are two ways to achieve abstraction in java
1. Abstract class (0 to 100%)
2. Interface (100%)
Important points:
✓ An abstract class must be declared with an abstract keyword.
✓ It can have abstract and non-abstract methods.
✓ It cannot be instantiated.
✓ It can have constructors and static methods also.
✓ It can have final methods which will force the subclass not to change the body of the method.

Interface in Java:
An interface is a fully abstract class. It includes a group of abstract methods (methods without a body).
In Java, an interface serves as a means to achieve abstraction by allowing only abstract methods without
method bodies. It facilitates abstraction and multiple inheritances by using interfaces. In essence,
interfaces can contain abstract methods and variables but not method bodies. They also establish an IS-A
relationship. When we classify an entity based on its behavior rather than its attributes, defining it as an
interface is appropriate.

Syntax:
interface {
// declare constant fields
// declare methods that abstract by default.
}

To declare an interface, you use the interface keyword. This establishes total abstraction, meaning all
methods within the interface are declared without bodies, and they are automatically public.
Additionally, all fields within an interface are public, static, and final by default. When a class
implements an interface, it must provide implementations for all the methods declared in that interface.
This implementation is achieved using the implements keyword.
1. Interfaces indeed facilitate total abstraction in Java.
2. While Java doesn't support multiple inheritances with classes, it does so through interfaces, allowing a
class to implement multiple interfaces.
3. Unlike class inheritance, where a class can only extend one superclass, a class can implement any
number of interfaces. This feature promotes loose coupling between classes, enhancing flexibility and
maintainability in software design.
4. Interfaces are a fundamental tool for implementing abstraction in Java, enabling developers to define
contracts without specifying implementation details.

Difference between Abstract class& Interface


An abstract class is a class that cannot be instantiated and may contain both abstract and non-abstract
methods. It serves as a blueprint for other classes to extend. Abstract methods within an abstract class are
those without implementation, which must be overridden by concrete subclasses.
In contrast, an interface is a contract specifying a set of methods that a class must implement. All
methods within an interface are implicitly abstract, lacking implementation details. Classes that
implement an interface must provide concrete implementations for all the methods declared in that
interface.
In summary, the key differences are:
24
- Instantiation: Abstract classes cannot be instantiated directly, while interfaces cannot be instantiated at
all. They serve as templates or blueprints for other classes.
- Method Implementation: Abstract classes can have both abstract and non-abstract methods. Abstract
methods within abstract classes require implementation in subclasses, while non-abstract methods can
have implementations. Interfaces, on the other hand, contain only abstract methods, all of which must be
implemented by any class that implements the interface.
-Inheritance: A class is limited to inheriting from just one abstract class, whereas it can implement
multiple interfaces. This distinction arises from the fact that an abstract class signifies a specific type of
object, while an interface delineates a collection of behaviors.
-Access Modifiers: Abstract classes are capable of employing access modifiers like public, protected,
and private for their methods and properties. Conversely, interfaces are restricted to public access for
their methods and properties.
The query emerges: why opt for interfaces when abstract classes are available?
The rationale lies in the fact that abstract classes can accommodate non-final variables, whereas interface
variables are inherently final, public, and static.

Java program to demonstrate working of interface

import java.io.*;
interface intr1 {

final int b = 20; // public, static and final


// public and abstract
void display();
}
// A class that implements the interface.
class Demo implements intr1 {
public void display(){
System.out.println("DSA");
}
public static void main(String[] args)
{
Demo t = new Demo();
t.display();
System.out.println(b);
}
}
Output:
DSA
20

Java program to demonstrate the real-world example of Interfaces

import java.io.*;
interface Vehicle {
void changeGear(int a);
void speedUp(int a);
void applyBrakes(int a);
}

class Bicycle implements Vehicle{


int speed;
25
int gear;
@Override
public void changeGear(int newGear){
gear = newGear;
}
@Override
public void speedUp(int increment){
speed = speed + increment;
}
@Override
public void applyBrakes(int decrement){
speed = speed - decrement;
}
public void printStates() {
System.out.println("speed: " + speed
+ " gear: " + gear);
}
}
class Bike implements Vehicle {
int speed;
int gear;
@Override
public void changeGear(int newGear){
gear = newGear;
}
@Override
public void speedUp(int increment){
speed = speed + increment;
}
@Override
public void applyBrakes(int decrement){
speed = speed - decrement;
}
public void printStates() {
System.out.println("speed: " + speed
+ " gear: " + gear);
}
}
class DSA {
public static void main (String[] args) {
Bicycle bicycle = new Bicycle();
bicycle.changeGear(2);
bicycle.speedUp(3);
bicycle.applyBrakes(1);
System.out.println("Bicycle present state :");
bicycle.printStates();
Bike bike = new Bike();
bike.changeGear(1);
bike.speedUp(4);
bike.applyBrakes(3);
System.out.println("Bike present state :");
bike.printStates();
}
}
Output:
26
Bicycle present state :
speed: 2 gear: 2
Bike present state :
speed: 1 gear: 1

Multiple Inheritance in Java Using Interface

Multiple Inheritance is a concept in Object-Oriented Programming (OOP) that cannot be directly


implemented in Java using classes. However, Java allows for multiple inheritances through interfaces.
Let's verify this statement with an example.

interface P{
void print();
}
interface S{
void show();
}
class Main implements P,S{

public void print(){


System.out.println("Study");
}
public void show(){
System.out.println("DSA");
}

public static void main(String args[]){


Main obj = new Main();
obj.print();
obj.show();
}
}
Output:
Study
DSA
Multiple inheritance is not supported through classes in Java due to the potential for ambiguity in
resolving conflicting method implementations from multiple parent classes. However, it is possible
through interfaces because there is no such ambiguity. In the case of interfaces, the implementation is
provided by the implementing class, thus avoiding conflicts.

interface Print{
void disp();
}
interface Show {
void disp();
}

class Demo implements Print,Show{

public void print(){

System.out.println("Java");
}
public static void main(String args[]){
27
Demo obj = new Demo();

obj.disp();

}
}

Output: Java

Explanation: In the above example, Print and Show interface have same methods but its implementation
is provided by class Demo, so there is no ambiguity.
Interface inheritance
A class implements an interface, but one interface extends another interface.

interface Print{
void print();
}
interface Show extends Print{
void show();
}
class Demo implements Show{
public void print(){
System.out.println("Hi");
}
public void show(){
System.out.println("DSA");
}
public static void main(String args[]){
Demo obj = new Demo();
obj.print();
obj.show();
}
}

Output:
Hi
DSA

• Java 8 Default Method in Interface


• Since Java 8, we can have method body in interface. But we need to make it default method. Let's see
an example:

interface Draw{
void draw();
default void msg(){
System.out.println("default method");
}
}
class Rectangle implements Draw{
public void draw(){
System.out.println("drawing rectangle");
}
}
28
class DemoInterfaceDefault{
public static void main(String args[]){
Draw d=new Rectangle();
d.draw();
d.msg();
}
}

Output:
drawing rectangle
default method

Java 8 Static Method in Interface


Since Java 8, we can have static method in interface. Let's see an example:

interface Draw{
void draw();
static int cube(int a){
return a*a*a;
}
}

class Rectangle implements Draw{


public void draw(){
System.out.println("drawing rectangle");
}
}

class DemoInterfaceStatic{
public static void main(String args[]){
Draw d=new Rectangle();
d.draw();
System.out.println(Draw.cube(3));
}
}

Output:
drawing rectangle
27

Hybrid Inheritance using interfaces


A hybrid inheritance is a combination of more than one types of inheritance. For example when class A
and B extends class C & another class D extends class A then this is a hybrid inheritance, because it is a
combination of single and hierarchical inheritance.

class C
{
public void disp()
{
System.out.println("C");
}
}
class A extends C
{
29
public void disp()
{
System.out.println("A");
}
}
class B extends C
{
public void disp()
{
System.out.println("B");
}
}
class D extends A
{
public void disp()
{
System.out.println("D");
}
public static void main(String args[]){
D obj = new D();
obj.disp();
}
}
Output:
D

Super keyword in java


In Java, the super keyword is a reference variable that is used to refer to the immediate parent class
object. When you create an instance of a subclass, an instance of the parent class is created implicitly,
and it is referred to by the super reference variable.

Use of super keyword:


1. Accessing Parent Class Instance Variables: You can use super to refer to immediate parent class
instance variables.
class Parent {
int parentVar = 10;
}

class Child extends Parent {


int childVar = 20;

void display() {
System.out.println("Parent variable: " + super.parentVar);
System.out.println("Child variable: " + childVar);
}
}

public class Main {


public static void main(String[] args) {
Child obj = new Child();
obj.display();
}
}

30
Output:
Parent variable: 10
Child variable: 20

Invoking Parent Class Methods: You can use super to invoke immediate parent class
methods.

class Parent {
void parentMethod() {
System.out.println("Parent method");
}
}

class Child extends Parent {


void childMethod() {
super.parentMethod();
System.out.println("Child method");
}
}

public class Main {


public static void main(String[] args) {
Child obj = new Child();
obj.childMethod();
}
}

Output:
Parent method
Child method

Invoking Parent Class Constructor: You can use super() to invoke the immediate
parent class constructor.

class Parent {
Parent() {
System.out.println("Parent constructor");
}
}

class Child extends Parent {


Child() {
super(); // Calls the parent class constructor
System.out.println("Child constructor");
}
}

public class Main {


public static void main(String[] args) {
Child obj = new Child();
}
}

31
Output:
Parent constructor
Child constructor

Example to show super keyword where super() is provided by the compiler


implicitly.

class Animal{
Animal(){System.out.println("animal is created");}
}
class Dog extends Animal{
Dog(){
System.out.println("dog is created");
}
}
class Main{
public static void main(String args[]){
Dog d=new Dog();
}
}

Output:
animal is created
dog is created

class Person{
int id;
String name;
Person(int id,String name){
this.id=id;
this.name=name;
}
}
class Emp extends Person{
float salary;
Emp(int id,String name,float salary){
super(id,name);//reusing parent constructor
this.salary=salary;
}
void display(){System.out.println(id+" "+name+" "+salary);}
}
class Main{
public static void main(String[] args){
Emp e1=new Emp(1,"ysg",50000f);
e1.display();
}
}

Output

1 ysg 50000.0

32
this keyword in Java:
The ‘this’ keyword in Java refers to the current objectin a method or constructor. It's particularly useful
in scenarios where you need to differentiate between instance variables and local variables or
parameters with the same name.

Following are the ways to use the ‘this’ keyword in Java mentioned below:
• Using the ‘this’ keyword to refer to current class instance variables.
• Using this() to invoke the current class constructor
• Using ‘this’ keyword to return the current class instance
• Using ‘this’ keyword as the method parameter
• Using ‘this’ keyword to invoke the current class method
• Using ‘this’ keyword as an argument in the constructor call

Using ‘this’ keyword to refer to current class instance variables

class A {
int a;
int b;

// Parameterized constructor
A(int a, int b)
{
this.a = a;
this.b = b;
}

void display()
{
// Displaying value of variables a and b
System.out.println("a = " + a + " b = " + b);
}

public static void main(String[] args)


{
A obj = new A(10, 20);
obj.display();
}
}
Output:
a = 10 b = 20

Using this() to invoke current class constructor

class A {
int a;
int b;
A()
{
this(10, 20);
System.out.println(
"Inside default constructor \n");

33
}
A(int a, int b)
{
this.a = a;
this.b = b;
System.out.println(
"Inside parameterized constructor");
}
public static void main(String[] args)
{
A obj = new A();
}
}
Output:
Inside parameterized constructor
Inside default constructor

Using ‘this’ keyword to return the current class instance

class A {
int a;
int b;
A()
{
a = 10;
b = 20;
}

A get() { return this; }


void display()
{
System.out.println("a = " + a + " b = " + b);
}
public static void main(String[] args)
{
A obj = new A();
obj.get().display();
}
}

Output:
a = 10 b = 20

Using ‘this’ keyword as a method parameter

class A {
int a;
int b;
A()
{
a = 10;
b = 20;

34
}
void display(A obj)
{
System.out.println("a = " + obj.a
+ " b = " + obj.b);
}
void get() {
display(this);
}
public static void main(String[] args)
{
A obj = new A();
obj.get();
}
}
Output:
a = 10 b = 20

Using ‘this’ keyword to invoke the current class method

class A {
void display()
{
this.show();
System.out.println("Inside display function");
}
void show()
{
System.out.println("Inside show function");
}
public static void main(String args[])
{
A obj1 = new A();
obj1.display();
}
}
Output:
Inside show function
Inside display function

Using ‘this’ keyword as an argument in the constructor call

class A {
B obj;
A(B obj)
{
this.obj = obj;
obj.display();
}
}
class B {
int x = 5;
B() {

35
A obj = new A(this);
}
void display()
{
System.out.println("Value of x in Class B : " + x);
}
public static void main(String[] args)
{
B obj = new B();
}
}

Output:
Value of x in Class B : 5
Advantages of using the `this` reference:

Distinguishing between instance variables and local variables with the same name:
• The `this` reference helps to differentiate between instance variables and local variables that share
the same name, reducing ambiguity and ensuring correct assignment.
• Passing the current object as an argument to another method: By using `this`, the current object can
be passed as an argument to another method, enabling operations that require the object's state or
behavior.
• Returning the current object from a method: `this` can be returned from a method, allowing methods
to return the current object itself, which can be useful for method chaining or maintaining fluent
interfaces.
• Invoking a constructor from another overloaded constructor in the same class: The `this` reference
can be used to invoke another constructor within the same class, allowing code reuse and reducing
redundancy in overloaded constructors.

Disadvantages of using the `this` reference:


• Overuse can make the code harder to read and understand: Excessive use of `this` can clutter the code
and make it less readable, especially in situations where it is not necessary for disambiguation.
• Adding unnecessary overhead to the program: Using `this` unnecessarily may introduce unnecessary
overhead to the program, impacting performance and increasing complexity without adding significant
benefits.
• Using `this` in a static context results in a compile-time error: Since `this` refers to the current object
instance, attempting to use it in a static context, such as a static method or initializer, will result in a
compile-time error. This limitation restricts its usage in certain scenarios.

Packages In java
In Java, a package serves as a container for organizing related classes, interfaces, and sub-packages.
Packages in Java can be classified into two main categories: built-in packages and user-defined
packages. Built-in packages encompass a variety of functionalities and are provided by the Java
platform. Examples of built-in packages include java, lang, awt, javax, swing, net, io, util, and sql. These
packages offer a wide range of pre-implemented classes and interfaces to facilitate common
programming tasks and operations.

Advantage of Java Package


1) Java package is used to categorize the classes and interfaces so that they can be easily maintained.
2) Java package provides access protection.
3) Java package removes naming collision.

36
The package keyword is used to create a package in java.

//save as Simple.java
package mypack;
public class Simple{
public static void main(String args[]){
System.out.println("Welcome to package");
}
}

How to access package from another package?


There are three ways to access the package from outside the package.

import package.*;
import package.classname;
fully qualified name.

1) Using packagename.
If you use package.* then all the classes and interfaces of this package will be accessible but not
subpackages.
The import keyword is used to make the classes and interface of another package accessible to the
current package.

Example of package that import the packagename.*


/save by A.java
package pack;
public class A{
public void msg(){System.out.println("Hello");}
}
//save by B.java
package mypack;
import pack.*;

class B{
public static void main(String args[]){
A obj = new A();
obj.msg();
}
}

37
Output: Hello

2) Using packagename.classname
If you import package.classname then only declared class of this package will be accessible.

Example of package by import package.classname


//save by A.java

package pack;
public class A{
public void msg(){System.out.println("Hello");}
}
//save by B.java
package mypack;
import pack.A;
class B{
public static void main(String args[]){
A obj = new A();
}
}

Output:
Output: Hello

3) Using fully qualified name


If you use fully qualified name then only declared class of this package will be accessible. Now there is
no need to import. But you need to use fully qualified name every time when you are accessing the class
or interface. It is generally used when two packages have same class name e.g. java.util and java.sql
packages contain Date class.

Example of package by import fully qualified name

//save by A.java
package pack;
public class A{
public void msg(){System.out.println("Hello");}
}
//save by B.java
package mypack;
class B{
public static void main(String args[]){
pack.A obj = new pack.A();//using fully qualified name
obj.msg();
}
}
Output: Hello

Note: If you import a package, all the classes and interface of that package will be imported excluding
the classes and interfaces of the subpackages. Hence, you need to import the subpackage as well.
Sequence of the program must be package then import then class.
Subpackage in java
Package inside the package is called the subpackage. It should be created to categorize the package
further.

38
Example of Subpackage

package com.dsa.core;
class Demo{
public static void main(String args[]){
System.out.println("Hello subpackage");
}
}

Output: Hello subpackage

Using Static Import


Static import is a feature introduced in Java programming language ( versions 5 and above ) that
allows members ( fields and methods ) defined in a class as public static to be used in Java code
without specifying the class in which the field is defined.

Following program demonstrates static import :

import static java.lang.System.*;

class Demo
{
public static void main(String args[])
{
// We don't need to use 'System.out' as imported using static.
out.println("JavaforDSA");
}
}
Output:
JavafortDSA

Exception handling in java


Exception handling in Java allows you to gracefully handle unexpected situations or errors that occur
during the execution of a program. When executing Java code, different errors can occur: coding errors
made by the programmer, errors due to wrong input, or other unforeseeable things. When an error
occurs, Java will normally stop and generate an error message. The technical term for this is: Java will
throw an exception (throw an error).

Exception Hierarchy:
All exception and error types are subclasses of the class Throwable, which is the base class of the
hierarchy.
One branch is headed by Exception. This class is used for exceptional conditions that user programs
should catch.
NullPointerException is an example of such an exception. Another branch, Error is used by the Java
run-time system(JVM) to indicate errors having to do with the run-time environment itself(JRE).
StackOverflowError is an example of such an error.

Default Exception Handling by Java:

39
When an exception occurs within a method, the Java Virtual Machine (JVM) handles it through a
process known as default exception handling. Here's how the JVM handles an exception:
1. Exception Object Creation:
• When an exception occurs, the method creates an object known as an Exception Object. This object
contains information about the exception, including its name, description, and the current state of the
program.
• The exception object is then handed off to the runtime system (JVM). This process of creating the
exception object and passing it to the runtime system is called throwing an exception.

2. Call Stack and Exception Handling:


• The runtime system searches the call stack to find a method that contains a block of code capable of
handling the occurred exception. This block of code is referred to as an Exception Handler.
• It starts searching from the method where the exception occurred and proceeds through the call stack
in reverse order of method calls.

3. Finding an Appropriate Handler:


• If the runtime system finds an appropriate handler for the exception, it passes the exception object to it.
• An appropriate handler is one whose type of exception object matches the type of exception object it
can handle.

4. Default Exception Handler:


• If the runtime system searches all methods on the call stack and cannot find an appropriate handler, it
hands over the exception object to the default exception handler, which is part of the runtime system.
• The default exception handler prints the exception information, including the exception type,
description, and stack trace, and terminates the program abnormally.

class DSA {

// Main driver method


public static void main(String args[])
{
// Taking an empty string
String str = null;
// Getting length of a string
System.out.println(str.length());
}
}

Output:

Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.length()" because


"str" is null
at DSA.main(DSA.java:13)

Example to show how a run-time system searches for appropriate exception


handling code on the call stack.

// Java Program to Demonstrate Exception is Thrown


// How the runTime System Searches Call-Stack
// to Find Appropriate Exception Handler Class ExceptionThrown
class DSA {
40
// Method 1
// It throws the Exception(ArithmeticException).
// Appropriate Exception handler is not found
// within this method.
static int divideByZero(int a, int b)
{
// this statement will cause ArithmeticException
// (/by zero)
int i = a / b;
return i;
}
// The runTime System searches the appropriate
// Exception handler in method also but couldn't have
// found. So looking forward on the call stack
static int computeDivision(int a, int b)
{
int res = 0;
// Try block to check for exceptions
try {
res = divideByZero(a, b);
}
// Catch block to handle NumberFormatException
// exception Doesn't matches with
// ArithmeticException
catch (NumberFormatException ex) {
// Display message when exception occurs
System.out.println(
"NumberFormatException is occurred");
}
return res;
}
// Method 2
// Found appropriate Exception handler. i.e. matching catch
block.
public static void main(String args[])
{
int a = 1;
int b = 0;
// Try block to check for exceptions
try {
int i = computeDivision(a, b);
}
// Catch block to handle ArithmeticException
// exceptions
catch (ArithmeticException ex) {
// getMessage() will print description
// of exception(here / by zero)
System.out.println(ex.getMessage());
}
}
}

Output
/ by zero

41
How Programmer Handle an Exception?

Customized Exception Handling:

Java exception handling is managed via five keywords: try, catch, throw, throws, and finally. Briefly,
here is how they work. Program statements that you think can raise exceptions are contained within a
try block. If an exception occurs within the try block, it is thrown. This exception is caught using catch
block and handled it in some rational manner. System-generated exceptions are automatically thrown
by the Java run-time system. To manually throw an exception, use the keyword throw. Any exception
that is thrown out of a method must be specified as such by a throws clause. Any code that absolutely
must be executed after a try block completes is put in a finally block.

Syntax:
try{

// code you think can raise an exception (also known as guardedBody)


}
catch(ExceptionType1 variable1){
// exception handler for ExceptionType1 (remedyBody1)
}
catch(ExceptionType2 variable2){
// exception handler for ExceptionType2 (remedyBody2)
}
// optional
finally{
// block of code to be executed after try block ends
}

Key Points for Exception Handling:

• Each method may contain multiple statements that could potentially throw exceptions. To handle these
situations, enclose each potentially risky statement within its own `try` block and provide a
corresponding `catch` block to handle the exception.
• When an exception occurs within a `try` block, it is caught by the associated `catch` block. Multiple
`catch` blocks can be used, each handling a specific type of exception indicated by its argument, which
must be a class inheriting from the `Throwable` class.
• A `try` block may have zero or more `catch` blocks, but only one `finally` block.
• The `finally` block is optional but highly useful. It always executes, regardless of whether an exception
occurred in the `try` block or not. If an exception occurred, the `finally` block executes after the `try`
and `catch` blocks. If no exception occurred, it executes after the `try` block. It's commonly used for
critical tasks such as resource cleanup (e.g., closing files or connections).
• Note that if `System.exit` is invoked within a `try` block, the `finally` block will not be executed. This
is because `System.exit` immediately terminates the program.

In Java, there are two types of exceptions:


1. Built-in Exceptions: Checked exceptions and Unchecked exceptions
2. User Defined Exceptions

• Checked exceptions are those that the compiler checks for at compile time. When a piece of code
within a method may throw a checked exception, the method is required to either handle the exception
or declare it using the throws keyword. Checked exceptions can be categorized into two types: fully
checked and partially checked exceptions.
42
• A fully checked exception is one in which all of its subclasses are also checked exceptions. Examples
include IOException and InterruptedException. On the other hand, a partially checked exception is a
checked exception where some of its subclasses are unchecked. An example of this is Exception.
• Example: Consider this Java program that attempts to open and read the file located at “C:\test\a.txt”
and print its first three lines. The program encounters compilation errors due to the use of
`FileReader()`, which throws the checked exception `FileNotFoundException`, and also because of the
`readLine()` and `close()` methods, which can throw the checked exception `IOException`.

// Java Program to Illustrate Checked Exceptions Where


FileNotFoundException occurred
// Importing I/O classes
import java.io.*;
// Main class
class Main {
// Main driver method
public static void main(String[] args)
{
// Reading file from path in local directory
FileReader file = new FileReader("C:\\dsa\\string.txt");
// Creating object as one of ways of taking input
BufferedReader fileInput = new BufferedReader(file);
// Printing first 3 lines of file "C:\dsa\string.txt"
for (int counter = 0; counter < 3; counter++)
System.out.println(fileInput.readLine());
// Closing file connections using close() method
fileInput.close();
}
}

Unchecked exceptions
Exceptions that aren't checked at compile time are referred to as unchecked exceptions. In Java,
exceptions falling under the Error and RuntimeException classes are considered unchecked exceptions,
while all other exceptions under Throwable are checked.

Ex: Consider the following Java program. It compiles fine, but it


throws ArithmeticException when run. The compiler allows it to compile
because ArithmeticException is an unchecked exception.

// Java Program to Illustrate Un-checked Exceptions

class Main {
public static void main(String args[])
{
int x = 0;
int y = 10;
int z = y / x;
}
}

Output;
Exception in thread "main" java.lang.ArithmeticException: / by zero
at Main.main(Main.java:5)

43
Java Result: 1

User-defined Exceptions: Sometimes, the built-in exceptions in Java are not able to describe a certain
situation. In such cases, users can also create exceptions, which are called ‘user-defined Exceptions’.

The advantages of Exception Handling in Java are as follows:


1. Provision to Complete Program Execution
2. Easy Identification of Program Code and Error-Handling Code
3. Propagation of Errors
4. Meaningful Error Reporting
5. Identifying Error Types

class MarksOutOfBoundException extends Exception {

public MarksOutOfBoundException(String message) {


super(message);
}
}

class Student1 {
private String name;
private int marks;

public Student1(String name, int marks) throws


MarksOutOfBoundException {
if (marks < 0 || marks > 100) {
throw new MarksOutOfBoundException("Marks should be
between 0 and 100.");
}
this.name = name;
this.marks = marks;
}

public void displayInfo() {


System.out.println("Name: " + name);
System.out.println("Marks: " + marks);
}
}

public class Main {


public static void main(String[] args) {
try {
// Creating a student object with marks more than 100
Student1 student1 = new Student1("John", 105); // This
will throw MarksOutOfBoundException
student1.displayInfo(); // This won't be executed due to
the exception
} catch (MarksOutOfBoundException e) {
System.out.println("Exception caught: " + e.getMessage());
}

try {
// Creating a student object with valid marks
44
Student1 student2 = new Student1("Alice", 90);
student2.displayInfo(); // This will be executed
} catch (MarksOutOfBoundException e) {
System.out.println("Exception caught: " + e.getMessage());
// This won't be executed
}
}
}

// A Class that represents use-defined exception

class MyException extends Exception {


public MyException(String s)
{
// Call constructor of parent Exception
super(s);
}
}

// A Class that uses above MyException


public class Main {
// Driver Program
public static void main(String args[])
{
try {
// Throw an object of user defined exception
throw new MyException("GeeksGeeks");
}
catch (MyException ex) {
System.out.println("Caught");

// Print the message from MyException object


System.out.println(ex.getMessage());
}
}
}

// A Class that represents use-defined exception

class MyException extends Exception {


}

// A Class that uses above MyException


public class setText {
// Driver Program
public static void main(String args[])
{
try {
// Throw an object of user defined exception
throw new MyException();
}
catch (MyException ex) {
System.out.println("Caught");
System.out.println(ex.getMessage());
45
}
}
}

Methods to print the Exception information:

1. printStackTrace()
This method prints exception information in the format of the Name of the exception: description of
the exception, stack trace.

Example:

import java.io.*;
class DSA {
public static void main (String[] args) {
int a=5;
int b=0;
try{
System.out.println(a/b);
}
catch(ArithmeticException e){
e.printStackTrace();
}
}
}

Output:

java.lang.ArithmeticException: / by zero
at DSA.main(File.java:10)

2. toString()
The toString() method prints exception information in the format of the Name of the exception:
description of the exception.

Example:
import java.io.*;
class DSA {
public static void main (String[] args) {
int a=5;
int b=0;
try{
System.out.println(a/b);
}
catch(ArithmeticException e){
System.out.println(e.toString());
}
}
}

Output:

46
java.lang.ArithmeticException: / by zero

3. getMessage()
The getMessage() method prints only the description of the exception.

Example: Program to print the exception information using getMessage() method

import java.io.*;
class DSA {
public static void main (String[] args) {
int a=5;
int b=0;
try{
System.out.println(a/b);
}
catch(ArithmeticException e){
System.out.println(e.getMessage());
}
}
}
Output:

/ by zero

import java.util.Scanner;

public class LuckyNumberReader {


public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);

try {
System.out.print("Enter your lucky number: ");
int luckyNumber = Integer.parseInt(scanner.nextLine());

if (luckyNumber < 0) {
throw new NumberFormatException("Negative numbers are
not allowed.");
}

System.out.println("Your lucky number is: " +


luckyNumber);
} catch (NumberFormatException e) {
System.out.println("Error: " + e.getMessage());
// You may handle this differently, like asking for input
again.
} finally {
scanner.close();
}
}
}

import java.util.Scanner;

47
public class LuckyNumberReader {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);

while (true) {
try {
System.out.print("Enter your lucky number: ");
int luckyNumber =
Integer.parseInt(scanner.nextLine());

if (luckyNumber < 0) {
throw new NumberFormatException("Negative numbers
are not allowed.");
}

System.out.println("Your lucky number is: " +


luckyNumber);
break; // Exit the loop if input is valid
} catch (NumberFormatException e) {
System.out.println("Error: " + e.getMessage());
System.out.println("Please enter a positive number.");
}
}

scanner.close();
}
}

import java.util.Scanner;

public class AgeReader {


public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);

int age = 0;
boolean validInput = false;

while (!validInput) {
try {
System.out.print("Enter your age: ");
age = Integer.parseInt(scanner.nextLine());

if (age < 0) {
throw new IllegalArgumentException("Age cannot be
negative.");
} else if (age < 18) {
throw new IllegalArgumentException("You must be at
least 18 years old.");
} else if (age > 120) {
throw new IllegalArgumentException("Are you sure?
Please enter a valid age.");
}
validInput = true;
} catch (NumberFormatException e) {

48
System.out.println("Error: Invalid input. Please enter
a valid age.");
} catch (IllegalArgumentException e) {
System.out.println("Error: " + e.getMessage());
}
}
System.out.println("Your age is: " + age);
scanner.close();
}
}

Recursion
In Java, Recursion is a process in which a function calls itself directly or indirectly is called recursion
and the corresponding function is called a recursive function. Using a recursive algorithm, certain
problems can be solved quite easily.

Factorial using loop


class Factorial1{
public static void main(String args[]){
int i,fact=1;
int number=5;//It is the number to calculate factorial
for(i=1;i<=number;i++){
fact=fact*i;
}
System.out.println("Factorial of "+number+" is: "+fact);
}
}

Base Condition in Recursion

In the recursive program, the solution to the base case is provided and the solution to the bigger
problem is expressed in terms of smaller problems.

Factorial Program using recursion in java


class Factorial2{
static int factorial(int n){
if (n == 0)
return 1;

else
return(n * factorial(n-1));
}
public static void main(String args[]){
int i,fact=1;
int number=4;//It is the number to calculate factorial
fact = factorial(number);
System.out.println("Factorial of "+number+" is: "+fact);
49
}
}

In the above example, the base case for n == 0 is defined and the larger value of a number can be
solved by converting it to a smaller one till the base case is reached.

The idea is to represent a problem in terms of one or more smaller sub-problems and add base
conditions that stop the recursion. For example, we compute factorial n if we know the factorial of
(n-1). The base case for factorial would be n = 0. We return 1 when n = 0.

Stack Overflow error


If the base case is not reached or not defined, then the stack overflow problem may arise. Let us take an
example to understand this.

int fact(int n)
{
// wrong base case (it may cause
// stack overflow).
if (n == 100)
return 1;
else
return n*fact(n-1);
}

If fact(10) is called, it will call fact(9), fact(8), fact(7) and so on but the number will never reach 100.
So, the base case is not reached. If the memory is exhausted by these functions on the stack, it will
cause a stack overflow error.

How is memory allocated to different function calls in recursion?


When any function is called from main(), the memory is allocated to it on the stack. A recursive
function calls itself, the memory for the called function is allocated on top of memory allocated to the
calling function and a different copy of local variables is created for each function call. When the base
case is reached, the function returns its value to the function by whom it is called and memory is de-
allocated and the process continues.

Binary Search using recursion

Recursive algorithms are used in binary search. The broad strategy is to look at the middle item on the
list. The procedure is either terminated (key found), the left half of the list is searched recursively, or the
right half of the list is searched recursively, depending on the value of the middle element.

Example: Input: arr[] = {1, 4, 3, 5, 6, 8, 11, 10, 14, 17}


Target value = 8
OUTPUT: Element 8 is present at index 6.

Algorithm:
1. Find the element at arr [size/2], which will be the array's midpoint. The array is split halfway, with
the lower half consisting of items 0 to midpoint -1 and the top half consisting of elements midpoint
to size -1.
2. Compare the key to arr [midpoint] by calling the user function.

50
3. If the key is a match, return arr [midpoint];
4. Otherwise return NULL, indicating that there is no match if the array has only one element.
5. Search the lower half of the array by repeatedly executing search if the key is less than the value
taken from arr [midpoint].
6. Call search recursively to search the upper half of the array.

import java.util.Scanner;
class Mid
{
public static int binarySearch(int[] arr, int left, int right, int
number)
{
if (left > right)
return -1;

int mid = (left + right) / 2;

if (number == arr[mid])
return mid;
else if (number <arr[mid])
return binarySearch(arr, left, mid - 1, number);
else
return binarySearch(arr, mid + 1, right, number);
}

public static void main(String[] args)


{
int n;
Scanner sc = newScanner(System.in);
System.out.print("Enter the value of arr size ");
n = sc.nextInt();
51
int[] arr = newint[n];
for(int i=0; i<n; i++)
{
System.out.print("Enter array value ");
arr[i]=sc.nextInt();
}
int number;
System.out.print("Enter the target value: ");
number = sc.nextInt();

int left = 0;
int right = arr.length - 1;

int index = binarySearch(arr, left, right, number);

if (index != -1)
{
System.out.println("Element found at index " + index);
}
else
{
System.out.println("Element not found in the array");
}
}
}

Tower of Hanoi

Tower of Hanoi is a mathematical puzzle where we have three rods and n disks. The objective of the
puzzle is to move the entire stack to another rod, obeying the following simple rules: 1) Onlyone disk
can be moved at a time. 2) Each move consists of taking the upper disk from one of the stacks and
placing it on top of another stack i.e. a disk can only be moved if it is the uppermost disk on a stack. 3)
No disk may be placed on top of a smaller disk.

52
class Main {

static void towerOfHanoi(int n, char from_rod, char to_rod, char


aux_rod)
{
if (n == 1) {
System.out.println("Move disk 1 from rod " + from_rod + " to rod " +
to_rod);
return;
}
towerOfHanoi(n - 1, from_rod, aux_rod, to_rod);

System.out.println("Move disk " + n + " from rod " + from_rod + " to


rod "+ to_rod);
towerOfHanoi(n - 1, aux_rod, to_rod, from_rod);
}

public static void main(String args[])


{
int n = 4;
towerOfHanoi(n, 'A', 'B', 'C');
}
}

Write a recursive java program to print the sum of n natural numbers.


public class RecursiveSum {

// Recursive method to calculate the sum of the first n natural numbers

public static intsumOfNaturalNumbers(int n) {

// Base case: If n is 1, the sum is just 1

if (n == 1) {

return 1;

// Recursive case: The sum of the first n natural numbers is n + sum


of the first (n-1) natural numbers

return n + sumOfNaturalNumbers(n - 1);

public static void main(String[] args) {

int n = 10; // You can change this value to test with different numbers

int result = sumOfNaturalNumbers(n);

System.out.println("The sum of the first " + n + " natural numbers is: " +
result);
}

Big O Notation in Data Structures


53
Asymptotic analysis is the study of how the algorithm's performance changes when the order of the input
size changes. We employ big-notation to asymptotically confine the expansion of a running time to
within constant factors above and below. The amount of time, storage, and other resources required to
perform an algorithm determine its efficiency. Asymptotic notations are used to determine the efficiency.
For different types of inputs, an algorithm's performance may vary. The performance will fluctuate as the
input size grows larger.

When the input tends towards a certain value or a limiting value, asymptotic notations are used to
represent how long an algorithm takes to execute. When the input array is already sorted, for example,
the time spent by the method is linear, which is the best scenario.

However, when the input array is in reverse order, the method takes the longest (quadratic) time to sort
the items, which is the worst-case scenario. It takes average time when the input array is not sorted or in
reverse order. Asymptotic notations are used to represent these durations.

Big O notation classifies functions based on their growth rates: several functions with the same growth
rate can be written using the same O notation. The symbol O is utilized since a function's development
rate is also known as the order of the function. A large O notation description of a function generally
only offers an upper constraint on the function's development rate.

Examples

Now let us have a deeper look at the Big O notation of various examples:

O(1)- Constant Time


This describes an algorithm that runs in constant time, regardless of the size of the input. An example is
accessing an element in an array by index.

void constantTimeComplexity(int arr[])

printf("First element of array = %d",arr[0]);

This function runs in O(1) time (or "constant time") relative to its input. The input array could be 1 item
or 1,000 items, but this function would still just require one step.

Example:

int[] array = {1, 2, 3, 4, 5};

int element = array[2]; // Constant time

Example:

int a = 4;

O(n)-(Linear Time): An algorithm that runs in linear time has a running time proportional to the input
size. A common example is iterating through a list.

Example:

void linearTimeComplexity(int arr [], int size)


{
for (int i = 0; i < size; i++)
{

54
printf("%d\n", arr[i]);
}
}
This function runs in O(n) time (or "linear time"), where n is the number of items in the array. If the
array has 10 items, we have to print 10 times. If it has 1000 items, we have to print 1000 times.

Example:
int sum = 0;
for (int i = 0; i <array.length; i++) {
sum += array[i];
}
Time Complexity- O(n)

public class LinearSearch


{
// Linear search method
public static int linearSearch(int[] arr, int target)
{
for (int i = 0; i < arr.length; i++)
{
if (arr[i] == target)
return i;
} // Return -1 if the target is not found
return -1;
}
public static void main(String[] args) {
int[] array = {5, 3, 8, 1, 9, 2};
int target = 8;
int result = linearSearch(array, target);
if (result != -1)
System.out.println("Element found at index: " + result);
else
System.out.println("Element not found.");
}
}

Time Complexity- O(n) O(n2):Quadratic Time:

Quadratic time complexity occurs when nested loops are involved, and the inner loop's operations
depend on the outer loop's iterations.

void quadraticTimeComplexity(int arr[], int size)


{
for (int i = 0; i < size; i++)
{
for (int j = 0; j < size; j++)
{
printf("%d = %d\n", arr[i], arr[j]);
}
}
}

Here we're nesting two loops. If our array has n items, our outer loop runs n times, and our inner loop
runs n times for each iteration of the outer loop, giving us n^2 total prints. If the array has 10 items, we

55
have to print 100 times. If it has 1000 items, we have to print 1000000 times. Thus this function runs in
O(n^2) time (or "quadratic time").

O(log n) - Logarithmic Time:


An algorithm with logarithmic complexity has a running time that grows logarithmically with the input
size. A common example is a binary search.

int low = 0;
int high = array.length - 1;
while (low <= high) {
int mid = (low + high) / 2;
if (array[mid] < target) {
low = mid + 1;
} else if (array[mid] > target) {
high = mid - 1;
} else {
break;
}
}

The time complexity for the loop with elementary operations: Assuming these operations take
unit time for execution. This unit time can be denoted by O(1). If the loop runs for N
times without any comparison.

class DSA
{
public static void main(String[] args)
{
int a = 0, b = 0;
int N = 4, M = 4;

// This loop runs for N time


for (int i = 0; i < N; i++)
{
a = a + 10;
}

// This loop runs for M time


for (int i = 0; i < M; i++)
{
b = b + 40;
}
System.out.print(a + " " + b);
}
}
Output
40 160

Explanation: The Time complexity here will be O(N + M). Loop one is a single for-loop that runs N
times and calculation inside it takes O(1) time. Similarly, another loop takes M times by combining

56
both the different loops takes by adding them
is O( N + M + 1) = O( N + M).

To find the time complexity for nested loops, assume that two loops with a different number of
iterations. It can be seen that, if the outer loop runs once, the inner will run M times, giving us a
series as M + M + M + M + M……….N times, this can be written as N * M.

import java.io.*;
class DSA{
public static void main (String[] args)
{
int a = 0;
int b = 0;
int N = 4;
int M = 5;
// Nested loops
for(int i = 0; i < N; i++)
{
for(int j = 0; j < M; j++)
{
a = a + j;
// Print the current
// value of a
System.out.print(a + " ");
}
System.out.println();
}
}
}

Output
0 1 3 6 10
10 11 13 16 20
20 21 23 26 30
30 31 33 36 40

O(n log n): Logarithmic time

s=0;
for(k=1;k<=n;k++)
for(m=2;m<=n;m=m*2){
sum=sum+m;
}
System.out.println(s);

public void Algorithm(int n) {

for (int i = 0; i < n; i++) {

//Outer loop runs n times

for (int j = 1; j < n; j *= 2) {


// Middle loop runs log(n) times

for (int k = 1; k < n; k *= 2) {

57
//Inner loop runs log(n) times
// Some operation here
}
}
}
}

Time Complexity: O(n log(n) log(n)) or O(n log2n)

Explanation:
• The outer loop runs n times.
• The middle loop and inner loop each run approximately lo 2( )log (n) times.
• The total complexity is therefore ( ⋅log( )⋅log( ))=O(n log2n).

public int treeHeight(int n) {


if (n <= 1) {
return 1; // Base case
}
return 1 + treeHeight(n / 2);
}
public void recursiveTreeTraversal(int n) {
for (int i = 0; i < n; i++) {
int height = treeHeight(n);
for (int j = 0; j < height; j++) {
}
}
}

Time Complexity- O(n log2n)

public void nestedLogLoops(int n, int k) {


for (int i = 0; i < n; i++) {
nestedLogHelper(n, k);
}
}
private void nestedLogHelper(int n, int k) {
if (k <= 0) {
return;
}
int j = 1;
while (j < n) {
// Perform some operation
j *= 2; // Logarithmic increment

58
𝑛
𝑛
𝑛
g⁡
𝑛
2​
}
nestedLogHelper(n, k - 1);
}

Time Complexity- O( log )

Explanation: In this example,


• The outer loop runs linearly, iterating over n elements.
• The nestedLogHelper method creates a series of nested loops, where each inner loop grows
logarithmically (doubling at each step).
• The recursion depth is governed by the parameter k, leading to k levels of nested loops, each
with logarithmic increments.
• The overall complexity results in ( log ), where n comes from the outer loop, and log
represents the series of recursive or nested logarithmic loops.

public int treeHeight(int n) {


if (n <= 1) {
return 1; // Base case
}
return 1 + treeHeight(n / 2);
}
public void recursiveTreeTraversal(int n) {
for (int i = 0; i < n; i++) {
int height = treeHeight(n);
for (int j = 0; j < height; j++) {
}

Search, Insert, and Delete in an Unsorted Array | Array Operations

Search Operation:
In an unsorted array, the search operation can be performed by linear traversal from the first element
to the last element.

class Main {
static int findElement(int arr[], int n, int key)
{
for (int i = 0; i < n; i++)
59
𝑘
𝑘
𝑘
𝑛
𝑛
𝑛
𝑛
𝑛
if (arr[i] == key)
return i;
return -1;
}
public static void main(String args[])
{
int arr[] = { 12, 34, 10, 6, 40 };
int n = arr.length;
int key = 6;
int position = findElement(arr, n, key);
if (position == -1)
System.out.println("Element not found");
else
System.out.println("Element Found at Position: "
+ (position + 1));
}
}
Output
Element Found at Position: 4
Time Complexity: O(N)
Auxiliary Space: O(1)

Insert Operation:
Insert at the end:
In an unsorted array, the insert operation is faster as compared to a sorted array because we don’t have
to care about the position at which the element is to be placed.

class Main {
static int insertSorted(int arr[], int n, int key,int capacity)
{
if (n >= capacity)
return n;

arr[n] = key;

60
return (n + 1);
}
public static void main(String[] args)
{
int[] arr = new int[15];
arr[0] = 13;
arr[1] = 17;
arr[2] = 21;
arr[3] = 41;
arr[4] = 51;
arr[5] = 71;
int capacity = 15;
int n = 6;
int i, key = 25;
System.out.print("Before Insertion: ");
for (i = 0; i < n; i++)
System.out.print(arr[i] + " ");
n = insertSorted(arr, n, key, capacity);
System.out.print("\n After Insertion: ");
for (i = 0; i < n; i++)
System.out.print(arr[i] + " ");
}
}

Output
Before Insertion: 13 17 21 41 51 71

After Insertion: 13 17 21 41 51 71 25

Time Complexity: O(1)


Auxiliary Space: O(1)

2. Insert at any position


Insert operation in an array at any position can be performed by shifting elements to the right, which
are on the right side of the required position

61
import java.io.*;
class Main {
static void insertElement(int arr[], int n, int x, int pos)
{
for (int i = n - 1; i >= pos; i--)
arr[i + 1] = arr[i];
arr[pos] = x;
}
public static void main(String[] args)
{
int arr[] = new int[15];
arr[0] = 2;
arr[1] = 4;
arr[2] = 1;
arr[3] = 8;
arr[4] = 5;
int n = 5;
int x = 10, pos = 2;

System.out.print("Before Insertion: ");


for (int i = 0; i < n; i++)
System.out.print(arr[i] + " ");
insertElement(arr, n, x, pos);
n += 1;
System.out.print("\n\nAfter Insertion: ");
for (int i = 0; i < n; i++)
System.out.print(arr[i] + " ");
}
}
Output
Before insertion : 2 4 1 8 5

After insertion : 2 4 10 1 8 5

Time complexity: O(N)


Auxiliary Space: O(1)

Delete Operation:
In the delete operation, the element to be deleted is searched using the linear search, and then the
delete operation is performed followed by shifting the elements.

62
class Main {
static int findElement(int arr[], int n, int key)
{
int i;
for (i = 0; i < n; i++)
if (arr[i] == key)
return i;

return -1;
}
staticintdeleteElement(intarr[], int n, int key)
{
intpos = findElement(arr, n, key);

if (pos == -1) {
System.out.println("Element not found");
return n;
}
int i;
for (i = pos; i < n - 1; i++)
arr[i] = arr[i + 1];

return n - 1;
}
public static void main(String args[])
{
int i;
intarr[] = { 10, 50, 30, 40, 20 };

int n = arr.length;
int key = 30;
System.out.println("Array before deletion");
for (i = 0; i < n; i++)
System.out.print(arr[i] + " ");

// Function call
n = deleteElement(arr, n, key);

System.out.println("\n\nArray after deletion");


for (i = 0; i < n; i++)
System.out.print(arr[i] + " ");
}
}
Output:
Array before deletion
10 50 30 40 20
Array after deletion

63
10 50 40 20

Time Complexity: O(N)


Auxiliary Space: O(1)

Extra Info:
Lists are stored sequentially in memory. The elements are stored one after the other. They are faster to
access, but slower in addition or deletion of elements. Linked lists are not stored sequentially in memory.
each element holds the address of the next element. They are slower to access but faster in addition or
deletion of elements.

LinkedList

Linked List is a linear data structure, in which elements are not stored at a contiguous location, rather
they are linked using pointers. Linked List forms a series of connected nodes, where each node stores
the data and the address of the next node.

Need For Linked List:

➢ Dynamic Data structure: The size of memory can be allocated or de-allocated at run time based on
the operation insertion or deletion.
➢ Ease of Insertion/Deletion: The insertion and deletion of elements are simpler than arrays since no
elements need to be shifted after insertion and deletion, Just the address needed to be updated.
➢ Efficient Memory Utilization: As we know Linked List is a dynamic data structure the size
increases or decreases as per the requirement so this avoids the wastage of memory.
➢ Implementation: Various advanced data structures can be implemented using a linked list like a
stack, queue, graph, hash maps, etc.

Nodes in a Linked List


Nodes are the building block of the linked list. After all, a linked list is a collection of nodes.

A node in a linked list consists of two parts:

• data which denotes the value of the node.


• next which is a reference to the succeeding node.

64
Head and Tail in a Linked List

The first node of the linked list is called the head node. It is the starting point of a linked list.

The last node is called the tail node. As there is no node after the last node, the last node always points to
the null.
A null pointer does not point to any memory location.

Operations on Linkedlist:

• Create node. • Insert nodes.


• Connect nodes. • Delete nodes.
• Append nodes.

Creating a node

class Node {
int data;
Node next;
Node(int data) {
this.data = data;
this.next = null;
}
}
// create nodes
Node node1 = new Node(11);
Node node2 = new Node(18);
Node node3 = new Node(24);

65
Link the Nodes in a Linked List
After creating the nodes, you must connect them to form a linked list.

To do this you first need to create a linked list with a head node.

classLinkedList {
Node head;
LinkedList() {
this.head = null;
}
}

Initially the head node is set to null because there are no nodes in the linked list yet.
Now to connect the nodes together in a Linked List, you can start by setting the head node to the first
node in the list, in this case node1.

head = node1;
Then make the next of node1 point to node2, and the next of node2 point to node3. That is:

node1.next = node2;

node2.next = node3;

Append a Node to Linked List


Appending a node means adding a node to the end of a linked list. There are two cases to consider when
appending a node:

• Appending to an empty linked list.


• Appending to a non-empty linked list.

How to Append a Node to an Empty Linked List

If there are no nodes in a linked list, it is an empty linked list. To append a node to an empty linked list,
you must first make sure the linked list is empty. You can do this by checking if the head node is null.

If the head node is null then you can simply set head to the new node:

if (head == null) {

head = newNode;

66
}

Append a Node to Non-Empty Linked List


If there are one or more nodes in a linked list, it is a non-empty linked list.

To append a node to a non-empty linked list, make the last node link to the new node.

Unlike arrays, we cannot access any elements in a linked list directly. We must traverse from
the head node to the last node.
To do that, create a temporary pointer (you can call the pointer current) that points to the head node.

Next, make current point to its next node, till the next of the current node points to null.

67
When the next node of current is null, you can then make the next of the current node point to the new
node. That is:

while (current.next != null) {

current = current.next;

current.next = newNode;

Insert a Node in a Linked List


Inserting a node means adding a node to a given index. There are two cases to consider when inserting a
node:

• Inserting a node at the first index.


• Inserting a node at a given index.

How to Insert a node at the First Index


To insert a node at the first index:

• make next of the new node point to the head node


• set the head to the new node.

if (index == 0) {
newNode.next = head;
head = newNode;
}

68
Insert a Node at Any Position

Let’s suppose you want to add a node at index 2 in the linked list above.

To insert a node at index 2, you must traverse the node that comes before index 2.

Next, create a new node and make the next of the new node point to the next of the current node.

Make the next of current point to the new node.

69
for (int i = 0; i < index - 1 && current != null; i++) {
current = current.next;
}
if (current != null) {
newNode.next = current.next;
current.next = newNode;
}

Delete a Node in a Linked List


There are two ways to delete nodes in a linked list:

• Deleting the head node.


• Deleting a node at a given position.

Delete the Head Node


Deleting the head node of a linked list is simple. You can store the data of the head node in a temporary
variable if it needs to be accessed later. Then set the head pointer to point to the next node after
the head node.

if (index == 0) {
deletedValue = head.data;
head = head.next;
}
Delete a Node at a Given Position
Suppose you want to delete the node at index 2 in the diagram below:

70
You can delete the node at index 2 by making the node at index 1 point to the node at index 3.

To delete a node you must access the node you want to delete and the node before it. Take two temporary
pointers (you can call the pointers previous and current). Let previous point to null and current point to
the head node.

Now, move current one step forward and move previous to current till you reach index 2.

Make the next of previous point to the next of the current node.

Then store the data of current in a variable for future use.


After removing the link to the node at index 2, it is no longer accessible through any reference in the
linked list.

71
It's important to note that when removing a node from a linked list, you don't need to explicitly delete the
node itself at the given index. This is because the removed node will be automatically handled by the
garbage collector when it is no longer reachable through any references.

However, in languages like C or C++, which do not have automatic garbage collection, you need to
manually delete the node when it is no longer needed to avoid memory leaks and wasted memory
resources.

for (int i = 0; i < index && current != null; i++) {

previous = current;

current = current.next;

if (current != null) {

deletedValue = current.data;

previous.next = current.next;

Complete Code:

class Node {

int data;

Node next;

Node(int data) {

this.data = data;

this.next = null;

classLinkedList {

Node head;

LinkedList() {

this.head = null;

public void createLinkedList() {

Node node1 = new Node(11);

this.head = node1;

72
Node node2 = new Node(18);

node1.next = node2;

Node node3 = new Node(24);

node2.next = node3;

public void append(Node newNode) {

Node current = this.head;

if (current == null) {

this.head = newNode;

} else {

while (current.next != null) {

current = current.next;

current.next = newNode;

public void insert(Node newNode, int index) {

Node current = this.head;

if (index == 0) {

newNode.next = current;

this.head = newNode;

} else {

for (int i = 0; i < index - 1 && current != null; I++)

current = current.next;

if (current != null) {

newNode.next = current.next;

current.next = newNode;

public int delete(int index) {

73
Node current = this.head;

Node previous = null;

int deletedValue = -1;

if (index == 0) {

deletedValue = this.head.data;

this.head = this.head.next;

return deletedValue;

else {

for (int i = 0; i < index && current != null; i++) {

previous = current;

current = current.next;

if (current != null) {

deletedValue = current.data;

previous.next = current.next;

returndeletedValue;

public void displayLinkedList() {

Node current = this.head;

while (current != null) {

System.out.println(current.data);

current = current.next;

class Main {

public static void main(String[] args) {

LinkedList l1 = new LinkedList();

74
Node newNode1 = new Node(22);

Node newNode2 = new Node(43);

Node newNode3 = new Node(5);

l1.createLinkedList();

l1.append(newNode1);

l1.insert(newNode2, 0);

l1.insert(newNode3, 2);

l1.delete(2);

l1.displayLinkedList();

Type casting in Java


Typecasting in Java is the process of converting one data type to another data type using the casting
operator. When you assign a value from one primitive data type to another type, this is known as type
casting. To enable the use of a variable in a specific manner, this method requires explicitly instructing
the Java compiler to treat a variable of one data type as a variable of another data type.
Syntax: <datatype> variableName = (<datatype>) value;

• Type casting of primitive data types


• Type casting of reference variables

Type casting of primitive data types


• Widening Type Casting • Narrowing Type Casting
• Widening Type Casting
Converting a lower data type into a higher one is called widening type casting. It is also known
as implicit conversion or casting down. It is done automatically. It is safe because there is no chance to
lose data. It takes place when:
• Both data types must be compatible with each other.
• The target type must be larger than the source type.
1. byte -> short -> char -> int -> long -> float -> double

For example, the conversion between numeric data type to char or Boolean is not done automatically.
Also, the char and Boolean data types are not compatible with each other. Let's see an example.

WideningTypeCastingExample.java
public class WideningTypeCastingExample
{
public static void main(String[] args)
{
int x = 7;

75
//automatically converts the integer type into long type
long y = x;
//automatically converts the long type into float type
float z = y;
System.out.println("Before conversion, int value "+x);
System.out.println("After conversion, long value "+y);
System.out.println("After conversion, float value "+z);
}
}
Output
Before conversion, the value is: 7
After conversion, the long value is: 7
After conversion, the float value is: 7.0

In the above example, we have taken a variable x and converted it into a long type. After that, the long
type is converted into the float type.
Narrowing Type Casting
Converting a higher data type into a lower one is called narrowing type casting. It is also known
as explicit conversion or casting up. It is done manually by the programmer. If we do not perform
casting then the compiler reports a compile-time error.
double -> float -> long -> int -> char -> short -> byte

Let's see an example of narrowing type casting.


In the following example, we have performed the narrowing type casting two times. First, we have
converted the double type into long data type after that long data type is converted into int type.

NarrowingTypeCastingExample.java
public class NarrowingTypeCastingExample
{
public static void main(String args[])
{
double d = 166.66;
//converting double data type into long data type
long l = (long)d;
//converting long data type into int data type
int i = (int)l;
System.out.println("Before conversion: "+d);
//fractional part lost
System.out.println("After conversion into long type: "+l);
//fractional part lost
System.out.println("After conversion into int type: "+i);
}
}
Output
Before conversion: 166.66
After conversion into long type: 166
76
After conversion into int type: 166

Type casting of reference variables


A process of converting one data type to another is known as Typecasting and Upcasting and Down
casting is the type of object typecasting. In Java, the object can also be typecasted like the
datatypes. Parent and Child objects are two types of objects. So, there are two types of typecasting
possible for an object, i.e., Parent to Child and Child to Parent or can say Upcasting and Down
casting.
In Java, the object can also be typecasted like the datatypes. Parent and Child objects are two types of
objects. So, there are two types of typecasting possible for an object, i.e., Parent to Child and Child to
Parent or can say Upcasting and Down casting.
Typecasting is used to ensure whether variables are correctly processed by a function or not.
In Upcasting and Down casting, we typecast a child object to a parent object and a parent object to
a child object simultaneously. We can perform Upcasting implicitly or explicitly, but down casting
cannot be implicitly possible.

Upcasting is a type of object typecasting in which a child object is typecasted to a parent class object.
By using the Upcasting, we can easily access the variables and methods of the parent class to the child
class. Here, we don't access all the variables and the method. We access only some specified variables
and methods of the child class. Upcasting is also known as Generalization and Widening.

UpcastingExample.java
class Parent{
void PrintData() {
System.out.println("method of parent class");
}
}

class Child extends Parent {


void PrintData() {
System.out.println("method of child class");
}
}
class UpcastingExample{
public static void main(String args[]) {
Parent obj1 = (Parent) new Child();
Parent obj2 = (Parent) new Child();
obj1.PrintData();
obj2.PrintData();
}
77
}
Output:
method of child class
method of child class

Upcasting is another type of object typecasting. In Upcasting, we assign a parent class reference object
to the child class. In Java, we cannot assign a parent class reference object to the child class, but if we
perform down casting, we will not get any compile-time error. However, when we run it, it throws
the "ClassCastException". Now the point is if down casting is not possible in Java, then why is it
allowed by the compiler? In Java, some scenarios allow us to perform down casting. Here, the subclass
object is referred by the parent class.
Below is an example of down casting in which both the valid and the invalid scenarios are explained:

//Parent class
class Parent {
String name;

// A method which prints the data of the parent class


void showMessage()
{
System.out.println("Parent method is called");
}
}
// Child class
class Child extends Parent {
int age;
// Performing overriding
@Override
void showMessage()
{
System.out.println("Child method is called");
}
}
public class Downcasting{
public static void main(String[] args)
{
Parent p = new Child();
p.name = "Harsha";

// Performing Downcasting Implicitly


//Child c = new Parent(); // it gives compile-time error

// Performing Downcasting Explicitly


Child c = (Child)p;

78
c.age = 18;
System.out.println(c.name);
System.out.println(c.age);
c.showMessage();
}
}
Output:
Harsha
18
Child method is called

Need For Upcasting and Down casting


In Java, we rarely use Upcasting. We use it when we need to develop a code that deals with only the
parent class. Down casting is used when we need to develop a code that accesses behaviors of the child
class.

Upcasting Downcasting
A child object is typecast to a The reference of the parent class object is
parent object. cast to the child class.
Upcasting can be performed Implicit downcasting is not possible;
implicitly or explicitly. it must be done explicitly.
In the child class, we can access The methods and variables of both
the methods and variables of the parent and child classes can
the parent class. be accessed.
All the methods and variables of
We can access some specified
both classes can be accessed
methods of the child class.
by performing downcasting.
Example: Parent p = new Child();
Example: Parent p = new Parent();
Child c = (Child)p;

Wrapper classes in Java


The wrapper class in Java provides the mechanism to convert primitive into object and object into
primitive.
Since J2SE 5.0, auto boxing and unboxing feature convert primitives into objects and objects into
primitives automatically. The automatic conversion of primitive into an object is known as auto boxing
and vice-versa unboxing.
Use of Wrapper classes in Java

79
Java is an object-oriented programming language, so we need to deal with objects many times like in
Collections, Serialization, Synchronization, etc. Let us see the different scenarios, where we need to use
the wrapper classes.
• Change the value in Method: Java supports only call by value. So, if we pass a primitive value, it
will not change the original value. But, if we convert the primitive value in an object, it will
change the original value.
• Serialization: We need to convert the objects into streams to perform the serialization. If we have
a primitive value, we can convert it in objects through the wrapper classes.
• Synchronization: Java synchronization works with objects in Multithreading.
• java.util package: The java.util package provides the utility classes to deal with objects.
• Collection Framework: Java collection framework works with objects only. All classes of the
collection framework (ArrayList, LinkedList, Vector, HashSet, LinkedHashSet, TreeSet,
PriorityQueue, ArrayQueue, etc.) deal with objects only.
The eight classes of the java.lang package are known as wrapper classes in Java. The list of eight
wrapper classes are given below:

Auto boxing
The automatic conversion of primitive data type into its corresponding wrapper class is known as auto
boxing, for example, byte to Byte, char to Character, int to Integer, long to Long, float to Float, boolean
to Boolean, double to Double, and short to Short.
Since Java 5, we do not need to use the valueOf() method of wrapper classes to convert the primitive
into objects.

Wrapper class Example: Primitive to Wrapper


//Java program to convert primitive into objects
//Auto boxing example of int to Integer
public class WrapperExample1{
public static void main(String args[]){
//Converting int into Integer
int a=20;
Integer I=Integer.valueOf(a);
//converting int into Integer explicitly
Integer j=a;
//auto boxing, now compiler will write Integer.valueOf(a) internally
System.out.println(a+" "+i+" "+j);
}
}
Output:
20 20 20

Unboxing

80
The automatic conversion of wrapper type into its corresponding primitive type is known as unboxing. It
is the reverse process of auto boxing. Since Java 5, we do not need to use the intValue() method of
wrapper classes to convert the wrapper type into primitives.

Wrapper class Example: Wrapper to Primitive


//Java program to convert object into primitives
//Unboxing example of Integer to int
public class WrapperExample2{
public static void main(String args[]){
//Converting Integer to int
Integer a=new Integer(3);
int i=a.intValue();//converting Integer to int explicitly
int j=a; unboxing, now compiler will write a.intValue() internally
System.out.println(a+" "+i+" "+j);
}
}
Output:
3 3 3

Generics in Java
Generics means parameterized types. The idea is to allow type (Integer, String, … etc., and user-
defined types) to be a parameter to methods, classes, and interfaces. Using Generics, it is possible to
create classes that work with different data types. An entity such as class, interface, or method that
operates on a parameterized type is a generic entity.

Why Generics?
The Object is the superclass of all other classes, and Object reference can refer to any object. These
features lack type safety. Generics add that type of safety feature. We will discuss that type of safety
feature in later examples.
Generics in Java are similar to templates in C++. For example, classes like HashSet, ArrayList,
HashMap, etc., use generics very well. There are some fundamental differences between the two
approaches to generic types.
Types of Java Generics
Generic Method: Generic Java method takes a parameter and returns some value after performing a
task. It is exactly like a normal function, however, a generic method has type parameters that are cited
by actual type. This allows the generic method to be used in a more general way. The compiler takes
care of the type of safety which enables programmers to code easily since they do not have to perform
long, individual type castings.
Generic Classes: A generic class is implemented exactly like a non-generic class. The only difference
is that it contains a type parameter section. There can be more than one type of parameter, separated
by a comma. The classes, which accept one or more parameters, ?are known as parameterized classes
or parameterized types.

Generic Class
Like C++, we use < > to specify parameter types in generic class creation. To create objects of a
generic class, we use the following syntax.

81
// To create an instance of generic class
BaseType <Type> obj = new BaseType <Type>()
Note: In Parameter type we can not use primitives like ‘int’,’char’ or ‘double’.

class Test<T> {
// An object of type T is declared
T obj;
Test(T obj) {
this.obj = obj;
} // constructor
public T getObject() {
return this.obj;
}
}
class Main {
public static void main(String[] args)
{
// instance of Integer type
Test<Integer> iObj = new Test<Integer>(15);
System.out.println(iObj.getObject());

// instance of String type


Test<String> sObj
= new Test<String>("DSA");
System.out.println(sObj.getObject());
}
}
Output
15
DSA

Primitive Type Wrapper class Primitive Type Wrapper class


boolean Boolean Int Integer
char Character Long Long
byte Byte Float Float
short Short Double Double
Java program to show multiple type parameters in Java Generics
class Test<T, U>

82
{
T obj1; // An object of type T
U obj2; // An object of type U

// constructor
Test(T obj1, U obj2)
{
this.obj1 = obj1;
this.obj2 = obj2;
}

// To print objects of T and U


public void print()
{
System.out.println(obj1);
System.out.println(obj2);
}
}
class Main
{
public static void main (String[] args)
{
Test <String, Integer> obj =
new Test<String, Integer>("DSA", 15);

obj.print();
}
}
Output:
DSA
15

Generic Functions/Method:
We can also write generic functions that can be called with different types of arguments based on the
type of arguments passed to the generic method. The compiler handles each method.
class Test {
static <T> void genericDisplay(T element)
{
System.out.println(element.getClass().getName()
+ " = " + element);
}
public static void main(String[] args)
{
genericDisplay(11);
genericDisplay("DSA");
genericDisplay(1.0);

83
}
}

Output
java.lang.Integer = 11
java.lang.String = DSA
java.lang.Double = 1.0

class Box1<T> {
private T value;

public void setValue(T value) {


this.value = value;
}

public T getValue() {
return value;
}
}

public class AS3Q4 {


public static void main(String[] args) {
// 1. Create a boxed String object and two variables that
refer to that box.
Box1<String> stringBox = new Box1<>();
stringBox.setValue("Hello");

Box1<String> anotherStringBox = stringBox;

// Change the contents of one and determine the effect on the


other.
anotherStringBox.setValue("World");

System.out.println("String in stringBox: " +


stringBox.getValue()); // Output: World

// 2. Create a boxed Integer object and two variables that


refer to that box.
Box1<Integer> integerBox = new Box1<>();
integerBox.setValue(10);

Box1<Integer> anotherIntegerBox = integerBox;

// Change the contents of one and determine the effect on the


other.
anotherIntegerBox.setValue(20);

System.out.println("Integer in integerBox: " +


integerBox.getValue()); // Output: 20

// 3. Create a boxed Object object and two variables that


refer to that box.

84
Box1<Object> objectBox = new Box1<>();

// Put a string in the box.


objectBox.setValue("String value");
System.out.println("Object box containing string: " +
objectBox.getValue()); // Output: String value

// Put an integer in the box.


objectBox.setValue(30);
System.out.println("Object box containing integer: " +
objectBox.getValue()); // Output: 30
}
}

public class AS3Q5 {

public static void main(String[] args) {


// Example usage of printArray method with different types of
arrays
Integer[] intArray = { 1, 2, 3, 4, 5 };
Double[] doubleArray = { 1.1, 2.2, 3.3, 4.4, 5.5 };
Character[] charArray = { 'H', 'E', 'L', 'L', 'O' };
String[] strArray = { "Hello", "World", "JAVA"};

System.out.println("Array of Integers:");
printArray(intArray);

System.out.println("\nArray of Doubles:");
printArray(doubleArray);

System.out.println("\nArray of Characters:");
printArray(charArray);

System.out.println("\nArray of Strings:");
printArray(strArray);
}

// Generic method to print any type of array


public static <E> void printArray(E[] inputArray) {
for (int i = 0; i < inputArray.length; i++) {
System.out.print(inputArray[i] + " ");
}
System.out.println();
}
}

public class AS3Q6 {


public static void main(String[] args) {
// Example usage of count method with different types of
arrays
Integer[] intArray = {1, 2, 3, 4, 5, 1, 2, 3};
Double[] doubleArray = {1.1, 2.2, 3.3, 4.4, 5.5, 1.1, 2.2};
85
String[] stringArray = {"apple", "banana", "apple", "orange",
"apple"};

// Count occurrences of elements in arrays


System.out.println("Occurrences of 3 in intArray: " +
count(intArray, 3));
System.out.println("Occurrences of 1.1 in doubleArray: " +
count(doubleArray, 2.2));
System.out.println("Occurrences of 'apple' in stringArray: " +
count(stringArray, "apple"));
}

// Generic method to count occurrences of an element in an array


public static <T> int count(T[] array, T item) {
int count = 0;
for (int i = 0; i < array.length; i++) {
if (array[i].equals(item)) {
count++;
}
}
return count;

}
}

86

You might also like