DSA Notes
DSA Notes
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
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";
}
}
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.
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;
}
}
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.
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.
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.
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.
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
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
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
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
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
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
Example:
class A {
private void privateMethod()
{
System.out.println(
"This is a private method in A");
}
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");
}
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.
// 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()");
Output:
Base fun()
Derived fun()
// 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
// 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:
// 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
Java Program to implement Abstract Class having constructor, data member, and
methods
import java.io.*;
abstract class Subject {
Subject() {
System.out.println("Learning Subject");
}
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
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();
}
class DSA {
public static void main(String[] args)
{
// Child1 c1=new Child1();
// c1.dsa1(); // will throw error since it is abstract
[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.
import java.io.*;
interface intr1 {
import java.io.*;
interface Vehicle {
void changeGear(int a);
void speedUp(int a);
void applyBrakes(int a);
}
interface P{
void print();
}
interface S{
void show();
}
class Main implements P,S{
interface Print{
void disp();
}
interface Show {
void disp();
}
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
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
interface Draw{
void draw();
static int cube(int a){
return a*a*a;
}
}
class DemoInterfaceStatic{
public static void main(String args[]){
Draw d=new Rectangle();
d.draw();
System.out.println(Draw.cube(3));
}
}
Output:
drawing rectangle
27
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
void display() {
System.out.println("Parent variable: " + super.parentVar);
System.out.println("Child variable: " + childVar);
}
}
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");
}
}
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");
}
}
31
Output:
Parent constructor
Child constructor
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
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);
}
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
class A {
int a;
int b;
A()
{
a = 10;
b = 20;
}
Output:
a = 10 b = 20
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
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
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.
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.
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");
}
}
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.
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.
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
//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");
}
}
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 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.
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.
class DSA {
Output:
Output
/ by zero
41
How Programmer Handle an Exception?
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{
• 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.
• 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`.
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.
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’.
class Student1 {
private String name;
private int marks;
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
}
}
}
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.
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;
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.");
}
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.");
}
scanner.close();
}
}
import java.util.Scanner;
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.
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.
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.
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.
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.
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;
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);
}
int left = 0;
int right = arr.length - 1;
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 {
if (n == 1) {
return 1;
int n = 10; // You can change this value to test with different numbers
System.out.println("The sum of the first " + n + " natural numbers is: " +
result);
}
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:
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:
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:
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)
Quadratic time complexity occurs when nested loops are involved, and the inner loop's operations
depend on the outer loop's iterations.
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").
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;
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
s=0;
for(k=1;k<=n;k++)
for(m=2;m<=n;m=m*2){
sum=sum+m;
}
System.out.println(s);
57
//Inner loop runs log(n) times
// Some operation here
}
}
}
}
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).
58
𝑛
𝑛
𝑛
g
𝑛
2
}
nestedLogHelper(n, k - 1);
}
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
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;
After insertion : 2 4 10 1 8 5
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);
63
10 50 40 20
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.
➢ 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.
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:
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;
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
}
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:
current = current.next;
current.next = newNode;
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.
69
for (int i = 0; i < index - 1 && current != null; i++) {
current = current.next;
}
if (current != null) {
newNode.next = current.next;
current.next = newNode;
}
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.
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.
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;
this.head = node1;
72
Node node2 = new Node(18);
node1.next = node2;
node2.next = node3;
if (current == null) {
this.head = newNode;
} else {
current = current.next;
current.next = newNode;
if (index == 0) {
newNode.next = current;
this.head = newNode;
} else {
current = current.next;
if (current != null) {
newNode.next = current.next;
current.next = newNode;
73
Node current = this.head;
if (index == 0) {
deletedValue = this.head.data;
this.head = this.head.next;
return deletedValue;
else {
previous = current;
current = current.next;
if (current != null) {
deletedValue = current.data;
previous.next = current.next;
returndeletedValue;
System.out.println(current.data);
current = current.next;
class Main {
74
Node newNode1 = new Node(22);
l1.createLinkedList();
l1.append(newNode1);
l1.insert(newNode2, 0);
l1.insert(newNode3, 2);
l1.delete(2);
l1.displayLinkedList();
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
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
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");
}
}
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;
78
c.age = 18;
System.out.println(c.name);
System.out.println(c.age);
c.showMessage();
}
}
Output:
Harsha
18
Child method is called
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;
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.
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.
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());
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;
}
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 T getValue() {
return value;
}
}
84
Box1<Object> objectBox = new Box1<>();
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);
}
}
}
86