Complete Java Notes
Complete Java Notes
BASICS TO ADVANCED
THANK YOU
SO MUCH!
COMLETE CODING BY
PRASHANT SIR
INDEX
CHALLENGES
KEY POINTS
3.4) Literals
3.5) Keywords
5.7) 2D Arrays
ARRAY CHALLENGES
CHALLENGES
KEY POINTS
7.5) Recursion
CHALLENGES
7.6) Random numbers & Math class
KEY POINTS
CHALLENGES
8.6) What is inheritance?
CHALLENGES
KEY POINTS
CHALLENGES
CHALLENGES
KEY POINTS
CHALLENGES
KEY POINTS
CHALLENGES
11.8) Map Interface
11.9) Enums
KEY POINTS
CHALLENGES
12.8) Intro to Executor Service
KEY POINTS
CHALLENGES
CHALLENGES
KEY POINTS
1. Introduction to Java
1.1) Why you must learn Java ?
Most popular(Runs on more than 6B devices).
Widely used(Web-Apps,Mobile Apps,Back-end,Enterprise Applications).
Object oriented.
Old but Gold.
High paying jobs.
Rich API’s and good community support.
1
.java Compiler Byte code(Intermediate output)
.class file contains this byte code.
This byte code can be runned on any type of machine using JVM to produce machine code.
Byte code JVM Machine code
Java has backward compatibility which means code written in older versions typically runs on newer
JVM’s.
Java(Programming language) is named after the JAVA Island in Indonesia,Known for it’s coffee.
coffee cup is the symbol of java which depicts that Our java is always fresh just like coffee.
Oracle acquired java in 2010.
2
In the perspective of the mankind,
Class --> Human
Object --> name of the person
Properties--> Height,Weight and color, etc.,
Methods --> breathing,going to office, etc.,
3
2. Java Basics
Java belongs to oracle(present).Oracle bought entire sun microsystems.
JDK is the SDK(software development kit) used to develop java applications.It is the superset of JRE.
JDK is used by the the developers like us to write the code.
JDK=(Developemental tools)+JRE
JRE=JVM+(set of libraries)+(other files)
JRE doesn’t have tools and utilities for developers like compilers and it’s a part of JDK but can be
downloaded separately.
4
JVM is machine specific but javac is architecture independent.
JVM converts the byte code into machine code and ensures WORE(Write once run everywhere).
JVM can not not be downloaded seperately like JRE and JDK. If we want to run the byte code into a
system we needed to download JRE/JDK.
2.5) Importance of the main method
Main method is the entry point of execution of every java program.Without main method JVM don’t
know where to start the execution of the program.
If there are ‘n’ no.of methods in a java program, main method will be executed first.
public static void main(String args[])
Main method cannot be duplicated and can not be overwritten.
Void is the return type of the main method.
Static methods can be called without needing to create an object for the class(need not to instantiate),
where the method was present.We make the main method public because JVM needs to access the
main method to execute the program.
CHALLENGES
public class Challenege {
public static void main(String args[]) {
int n = 5;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= i; j++) {
System.out.print("* ");
}
System.out.println();
}
}
}
// *
// * *
// * * *
// * * * *
// * * * * *
// * * * * *
// * * * *
// * * *
// * *
// *
5
public class Challenege {
public static void main(String args[]) {
int n = 5;
for (int i = 1; i <= n; i++) {
for (int j = i; j <= n; j++) {
System.out.print(" ");
}
for (int j = 1; j <= i; j++) {
System.out.print("* ");
}
System.out.println();
}
}
}
// *
// * *
// * * *
// * * * *
// * * * * *
KEY POINTS
.class file contains byte code and byte code is platform independent.
Println() adds new line at the end of the line.
JDK>JRE>JVM
Main method must needed to be declared as public.
Java was named after an island in Indonesia where coffee beans grow and java was first released
in 1995.
High level language(code) is not understood by java or any other languages.
6
3. Data types,Variables & Input
3.1) Variables?
These are the containers that are used to store data.
All the variables are not of same size.
Program stores in the computer storage and variables are stored in the memory.
Memory means RAM. Storage means Hardisk.RAM is volatile but not hardisk.
int a=20;
int is the data type and a is the name of the variable,20 is the value assigned to the variable.
Non-primitive data types doesn’t have a fixed size and they needed to be created using the new
keyword, where as primitive data types have the fixed size.
Data Type Size (in bytes)
byte 1
short 2
int 4
long 8
float 4
double 8
char 2
boolean 1 (JVM-dependent)
7
Value assignment to the variables of the datatype float need to be specifies as F/f, otherwise it will be
assumed as double and the same goes with long and need to be specified with either l or L at the end.
public class Variables {
public static void main(String[] args) {
int myInt = 5;
float myFloat = 3.14f;
byte myByte = 120;
short myShort = 500;
boolean isVegan = true;
double myDouble = 5888.888;
long myLong = 5874694855L;
}
}
3.3) Naming Conventions
Name of the variable is also known as the identifier.
Spaces are not allowed.
Only alphanumeric characters like [a-z],[A-Z],[0-9],$,_
Should not start with digits.
Keywords should not be used.
Make sure that name of the variable is simple but also understandable when others try to
understand our code.
Java identifiers are case sensitive.Below is the example and the code is valid
public class Variables {
public static void main(String[] args) {
int myInt = 5;
int myINT = 10;
}
}
3.4) Literals
Values assigned to the variables are known as the literals.From the above code in datatypes,
5 is a int literal.
true is a boolean literal and so on…
Syntax of the variable declaration is data_type identifier=literal;
3.5) Keywords
Keywords have predefined meaning and they can not be used as
8
Variable names
Class name
Method name
Package name
Any user-defined identifiers
9
2) Explicit type conversion
public class UserInput {
public static void main(String[] args) {
float f = 5.0d;// 5.0d is double
System.out.println(f); // on printing f the compiler throws error: incompatible types
}
}
We have to declare explicitly to convert literals of large sized data types to literals of small sized data
types.
public class UserInput {
public static void main(String[] args) {
// implicit
long big = 45;//45
float dec = 3;//3.0
double d = 3.4f;//3.4000000953674316
// explicit
float eDec = (float) 3.4;//3.4
long eBig = (long) 3.4;//3
int eInt = (int) 3.4;//3
}
}
10
4. Operators and if-else statement
4.1) Assignment operator
One file should have only one public class.
public class Chapter4 {
public static void main(String[] args) {
int num=5;//assigns 5(literal) to num(variable/identifier)
}
}
Exercise of the assignment operator
//to swap the values of a and b
temp=a;
a=b;
b=temp;
4.2) Arithmetic operators
11
public class Chapter4 {
public static void main(String[] args) {
System.out.println(9 / 3 / 3); // 1
System.out.println(9 / (3 / 3)); // 9
System.out.println(9 / (3 / 3 + 2)); // 3
//as execution goes from left to right along with BODMAS/PEMDAS
}
}
4.4) Shorthand operators
}
}
12
4.6) If-else
public class Chapter4 {
public static void main(String[] args) {
int age = 18;
if (age < 13)
System.out.println("You are a child.");
else if (age < 18)
System.out.println("You are a teenager.");
else if (age < 60)
System.out.println("You are an adult."); // This will be executed
else
System.out.println("You are a senior citizen.");
// As there is only single statement under if and also under else, we omitted {}
// but using {} is the best practice
}
}
13
}
public class Chapter4 {
public static void main(String[] args) {
int year = 296;
boolean isLeap = false;
if (year % 400 == 0 || (year % 4 == 0 && year % 100 != 0))
isLeap = true;
System.out.println(isLeap); // true
// if year%4==0 and year%100!=0 then it is a leap year and also
// if year%400==0 then also leap year
}
}
4.9) Operator precedence
Precedence: It decides which operator should be used first when there are multiple operators in an
expression.
Example: In 3 + 5 * 2 = 3 + 10 = 13.
Associativity: It tells us the order in which operators are used when there are multiple operators of
the same kind.
Example (Left to Right): For 5 - 3 - 1 = 2 - 1 = 1.
Example (Right to Left): For a = b = 10 is same as a = ( b = 10 ).
14
Below program is to check if the number is odd or even using only bit-wise operators
import java.util.Scanner;
public class Chapter4 {
public static void main(String[] args) {
// check weather the numbeer is odd/even using only bitwise operators
Scanner s = new Scanner(System.in);
int a = s.nextInt();
if ((a & 1) == 1) {
System.out.println("odd");
} else {
System.out.println("even");
}
// All the even numbers ends with 0.If we do this (num&1) and we get 1 means num
// also had 1 at the end which is odd
}
}
15
5. While loop,Methods &Arrays
5.1) Comments
To add notes to the code which doesnot execute.
// → single line comment
/* */ → multi-line comment
/** */ → Java docs
Java can have multiple static methods.static methods can be called from any class without creating an
object as long as they are public.
Body of the method is also known as method definition/function definition.
Main method will be executed first regardless of no.of methods, as main method is the entry point of
execution.
Function calling / Invoking a method: In the above syntax the function call will be max(a,b);
If there are no parameters then the syntax would be func_name();
JVM calls main method but we need to call the methods we declared.
import java.util.Scanner;
16
public static void main(String[] args) {
Scanner s = new Scanner(System.in);
System.out.print("Enter your name:");
String name = s.next();
greetins(name);
}
}
5.4) Return statement
To send a value back from function.
Return statement ends the execution immediately.
Code jumping can be possible through function calls.
We can return only one value from a method, not multiple.You can wrap multiple values in an array,
object, or a class to return them together.
Prefer return statement over global variables (declared inside a class and outside methods).
public class Chapter5 {
static int i = 5;
public static void main(String[] args) {
System.out.println(i); // can be accessed without creating an object.
}
}
17
void greet(String name) { // "name" is a parameter
System.out.println("Hello, " + name);
}
greet("Alice"); // "Alice" is an argument
Default parameters are not supported in java like python,c++. we need to use method over
loading.Method overriding is only occurs in inheritence.
public class Chapter5 {
// Method with one parameter
void greet(String name) {
System.out.println("Hello, " + name);
}
// Overloaded method with no parameters (default value)
void greet() {
greet("Guest"); // calling with default value
}
public static void main(String[] args) {
Chapter5 obj = new Chapter5();
obj.greet(); // Output: Hello, Guest
obj.greet("Alice"); // Output: Hello, Alice
}
}
CHALLENGES
1) Print multiplication table
import java.util.Scanner;
18
public class Chapter5 {
public static void main(String[] args) {
Scanner s = new Scanner(System.in);
System.out.print("Enter the number upto which you want to add odd numbers:");
int num = s.nextInt();
oddSum(num);
}
public static void oddSum(int num) {
int i = 1;
int count = 0;
while (i <= num) {
if (i % 2 == 0)
count += i;
i++;
}
System.out.println(count);
}
}
/*
* Enter the number upto which you want to add odd numbers:100
* 2550
*/
3) Factorial of a number
import java.util.Scanner;
19
while (i <= num) {
out *= i;
i++;
}
return out;
}
} else {
return -1;
}
}
5) LCM of 2 numbers
public class Lcm {
public static void main(String[] args) {
int val = findLcm(300, 550); // 33000
System.out.println(val);
}
public static int findLcm(int a, int b) {
int i = 1;
while (true) {
if ((a * i) % b == 0)
return a * i;
i++;
}
}
}
6) HCF of 2 numbers
public class Hcf {
public static void main(String[] args) {
System.out.println("HCF: " + hcf(20, 30)); // HCF: 10
}
public static int hcf(int a, int b) {
20
int small = Math.min(a, b);
while (small > 0) {
if (a % small == 0 && b % small == 0)
return small;
small--;
}
return 0;
}
}
8) Prime or not
public class Chapter5 {
public static void main(String[] args) {
isPrime(5);
}
public static void isPrime(int num) {
int i = 1, cnt = 0;
while (i <= num) {
if (num % i == 0)
cnt++;
i++;
}
if (cnt == 2)
System.out.println("prime"); // prime
else
System.out.println("not prime");
}
}
21
9) Reverse of an integer
public class Chapter5 {
public static void main(String[] args) {
reverse(123);
}
public static void reverse(int num) {
int rem = 0, reverse_num = 0;
while (num > 0) {
rem = num % 10;
reverse_num = (reverse_num * 10) + rem;
num = num / 10;
}
System.out.println("Reverse: " + reverse_num); // Reverse: 321
}
}
22
res += Math.pow(rem, count);
dummy /= 10;
}
return dummy1 == res ? true : false;
}
}
import java.util.Scanner;
23
// * * * * * *
// * * * * *
// * * * *
// * * *
// * *
// *
import java.util.Scanner;
24
// *
// * *
// * * *
// * * * *
// * * * * *
5.6) What is an Array?
Arrays are used to store multiple values of same datatype in single variable.
Indexing starts with zero.
All the variables are stored in continuous memory locations.
Name of the variable addresses to the first element of the array.
int[] arr=new int[10]; //Declaration
int[] arr={1,2,3,4,5,6};
25
// Enter 3 th element:6
// Enter 4 th element:9
// Enter 5 th element:1
// The array you entered: 8 7 6 9 1
// Enter the search element: 6
// is Found!: true
5.7) 2D Arrays
In a school there are 60 students and there are 6 subjects for each student then the possible 2-D array
to store marks of each student will be
int [][] stu_marks=new int[60][6];
2nd Student 1st subject marks can be accessed by stu_marks[1][0]
}
// 1 2 3
26
// 4 5 6
ARRAY CHALLENGES
1) Sum and avg. of all the elements in an array
import java.util.Scanner;
27
public class Maxminarray {
public static void main(String[] args) {
Scanner s = new Scanner(System.in);
int[] arr = new int[5];
for (int i = 0; i < arr.length; i++) {
System.out.print("Enter " + (i + 1) + " th element:");
arr[i] = s.nextInt();
}
int max = arr[0], min = arr[0];
for (int num : arr) {
if (num > max)
max = num;
else if (num < min)
min = num;
}
System.out.println("Max: " + max + ", Min: " + min);
}
}
// Enter 1 th element:5
// Enter 2 th element:8
// Enter 3 th element:9
// Enter 4 th element:4
// Enter 5 th element:7
// Max: 9, Min: 4
28
public class Deletingele {
public static void main(String[] args) {
Scanner s = new Scanner(System.in);
int[] arr = new int[5];
for (int i = 0; i < arr.length; i++) {
System.out.print("Enter " + (i + 1) + " th element:");
arr[i] = s.nextInt();
}
System.out.print("Enter the element you want to delete: ");
int se = s.nextInt(), i = 0, index = -1;
while (i < arr.length) {
if (se == arr[i])
index = i;
i++;
}
if (index == -1)
System.out.println("Element not found!");
else {
arr[index] = 0;
for (int j = index; j < arr.length - 1; j++) {
arr[j] = arr[j + 1];
}
arr[arr.length - 1] = 0;
for (int num : arr)
System.out.print(num + " ");
}
}
}
// Enter 1 th element:5
// Enter 2 th element:8
// Enter 3 th element:1
// Enter 4 th element:6
// Enter 5 th element:4
// Enter the element you want to delete: 1
// 5 8 6 4 0
6) Reverse an array
import java.util.Scanner;
29
System.out.print(num + " ");
}
}
}
// Enter 1 th element:5
// Enter 2 th element:6
// Enter 3 th element:9
// Enter 4 th element:4
// Enter 5 th element:3
// Entered array: 5 6 9 4 3
// Reversed array: 3 4 9 6 5
The below can also be the solution
import java.util.Scanner;
30
for (int i = 0; i < arr.length / 2; i++) {
int temp = arr[i];
arr[i] = arr[arr.length - 1 - i];
arr[arr.length - 1 - i] = temp;
}
int i = 0;
while (i < arr.length) {
if (arr[i] != copy[i])
isPalindrome = false;
i++;
}
if (isPalindrome == true) {
System.out.println("Palindrome array");
} else {
System.out.println("Not palindrome array");
}
}
}
// Enter 1 th element:1
// Enter 2 th element:2
// Enter 3 th element:3
// Enter 4 th element:2
// Enter 5 th element:1
// Palindrome array
31
}
return final_arr;
}
}
32
int j = 0;
while (j < arr[0].length) {
if (i == j)
digSum += arr[i][j];
j++;
}
i++;
}
System.out.println("Diagonal Sum: " + digSum); // Diagonal Sum: 11
}
}
In Java, reference types are types that refer to objects, not actual values. They store memory
addresses (references) of the objects.
public class Temp {
public static void main(String[] args) {
int[] arr = { 1, 2, 3 };
System.out.println(arr); // [I@2c7b84de
}
}
33
6. Classes & objects
6.1) (Process/Function/Procedure oriented) Vs. Object oriented
Procedure-Oriented Programming (POP):
A programming approach where the focus is on writing functions/procedures that operate on data.
The program is divided into functions. Example is
A recipe book – each step (procedure) tells what to do with the ingredients (data). All steps can
access the same ingredients.
Object-Oriented Programming (OOP):
A programming style that focuses on objects, which are instances of classes. Each object contains
data (fields) and behaviors (methods).Example is
A car is an object.
It has properties like color, speed, model (data).
It has behaviors like drive(), brake(), honk() (methods).
You can create many car objects from one Car class, each with its own data.
6.2) Instance variables and methods
Properties(variables) of the class which are declared inside the the class but outside any method are
know to be the instance variables.
Non-static methods inside a class is known as an instance method.
public class Person {
String name; // instance variable
int age; // instance variable
// instance method
void greet() {
System.out.println("Hello, my name is " + name);
}
}
Instance variables and instances aren’t same.Instances are objects of class whereas instance variables
are declared inside a class outside a method.
A simple analogy is given below,
Class → Blueprint
Object → A house that was built from blueprint
Instance variables → Rooms inside that house.
public class Car {
// instance variables
int noOfWheels;
String color;
float maxSpeed;
float currentFuelInLit;
int noOfSeats;
// instance methods
public void drive() {
System.out.println("Driving....");
34
currentFuelInLit--;
}
public void addFuel(float fuel) {
currentFuelInLit += fuel;
}
public float currentFuelLevel() {
return currentFuelInLit;
}
}
6.3) Declaring an object
New keyword is used to instantiate/create an object of a class.
Class declaration doesn’t takes up the memory but when the object is created the memory will be
allocated for the object in the heap.
public class Driver {
public static void main(String[] args) {
Car c1; // defining an reference variable for the car class
// new Car() is the object
c1 = new Car(); // storing the address of the object in c1,now c1 is known to be the
reference variable which stores the address of the object.
// Car()--> is known as the constructor.
}
}
New keyword unlocks the dynamic allocation feature which means that memory is created at
runtime/while execution by the JVM.
Constructor is called to initialize the object (car() is the constructor used to create an object c1).
C1 stores the reference of the object.
System.out.println(c1);
//Car@3fee733d
new Car() → it is the actual object.
C1 → it is the reference variable used to store the object address.
Better to call c1 as reference variable rather than calling it as an object as it is not an object actually.
Syntax as follows
Dot operator(.) is used to access the members of the class through reference variable.
ppublic class Driver {
public static void main(String[] args) {
Car c1;
35
c1 = new Car();
System.out.println(c1.currentFuelInLit);// 0.0
c1.drive(); // Driving....
System.out.println(c1.color); // null
}
}
Instance variables on declaration stores default values where as the local variables(members of a
method) need to be initialized first in order to use them.
// instance methods
public Car start() {
if (currentFuelInLit == 0)
System.out.println("No fuel,ca't start");
else if (currentFuelInLit < 5)
System.out.println("In reserve mode, Refeul needed!");
else
System.out.println("Starting.......");
return this;
}
public void drive() {
System.out.println("Driving....");
currentFuelInLit--;
}
public void addFuel(float fuel) {
currentFuelInLit += fuel;
}
public float currentFuelLevel() {
return currentFuelInLit;
}
}
36
startedCar.drive();
}
}
// Car startedCar = swift.start();
// startedCar.drive();
// the above 2 statements can be replaced with swift.start().drive() and everything works fine.
this() → used to invoke a constructor of the same class.
This can be passed as an argument in the method.
class Helper {
void display(Person p) {
System.out.println("Name: " + p.name);
System.out.println("Age: " + p.age);
}
}
class Person {
String name;
int age;
static Helper helper = new Helper();
Person(String name, int age) {
this.name = name; // this refers to instance variable
this.age = age; // same here
helper.display(this); // pass current object to display
}
}
public class Main {
public static void main(String[] args) {
Person p1 = new Person("Alice", 25);
}
}
Static keyword
Local variables are declared inside a method and their scope is within that method only.
37
Static methods can not access non-static members.
Class need not be instantiated to call static methods if only within the class.
static variables and methods are accessible anywhere inside the same class.
Static members can also be accessed from outside the class, using:
ClassName.staticVariable
ClassName.staticMethod()
We can access a static method from non-static method in java.
Reference variables are created for a class to access instance variables and instance methods.
.ClassName is used only to access static methods and static variables.
All the below scenarios in the table are to access the members of one class from another class.
Task Use new (Object) Use ClassName. (Static)
Access non-static method ✅Yes ❌No
Access static method ❌No ✅Yes
Call constructor ✅Yes ❌No
Shared utility method ❌No ✅Yes
Shared utility method is a static method which is used for common operations like add,sort,etc.,
Below is an example for utility method.
class MathUtils {
static int add(int a, int b) {
return a + b;
}
}
38
Use OuterClass.NestedClass to access a static nested class without creating an instance of the outer
class.
Non-static nested class can’t be accessed without creating an object for the outer class as well as for
the inner class as shown below
class Outer {
class Inner { // non-static nested class
void show() {
System.out.println("Inside non-static inner class");
}
}
}
void increment() {
staticCount++;
instanceCount++;
}
void showCounts() {
System.out.println("Static Count: " + staticCount);
System.out.println("Instance Count: " + instanceCount);
}
}
public class Main {
public static void main(String[] args) {
Counter c1 = new Counter();
Counter c2 = new Counter();
c1.increment();
c1.showCounts(); // staticCount = 1, instanceCount = 1
c2.increment();
c2.showCounts(); // staticCount = 2, instanceCount = 1
}
}
In java static variables are class-level not method-level.which means they can not be declared inside
any method but can be declared as a global variable(inside a class,outside a method).
void myMethod() {
static int x = 10; // ❌ Compilation error!
}
class MyClass {
static int x = 10; // ✅ Valid static variable
void myMethod() {
39
int y = 5; // ✅ Valid local variable
}
}
Static methods can be called without creating an object of the class.can only access static variables
and other static methods.They can be invoked using className.staticMethodName() and static
variables through className.staticVariableName.
class MyClass {
static int count = 0;
class MyClass {
int x = 5;
40
}
We can not access non-static members from static methods directly but we can access static
members form non-static methods directly.
class Demo {
static int a = 10; // static
int b = 20; // non-static
41
price = 500000;
}
Cars(String color) {
this.color = color;
price = 500000;
}
Cars() {
color = "black";
price = 500000;
}
Cars(String color) {
this.color = color;
price = 500000;
}
Cars() {
this("black"); // calling its own constructor
}
Demo(int x) {
this(); // calls Demo()
System.out.println("Parameterized constructor");
42
}
}
below will also be an example of the infinite looping
Demo(int x) {
this(10);
}
Code Blocks {}
There are 2 types of code blocks
1) Instance initialization block - runs every time when an object is created, even before the
execution of the constructor. For sharing common code among all the constructors.
2) static initialization block - static block is executed only once, and that's when the class is loaded
into memory for the first time, even before the main() method or any object is created. used to
initialize static variables.
public class Example {
static {
System.out.println("Static block");
}
{
System.out.println("Instance block");
}
Example() {
System.out.println("Constructor");
}
class Test {
static {
System.out.println("Static block in Test");
43
}
CHALLENGES
1) create a Book class for a library system
Title,author,isbn as instance variables
totalBooks as a static variable
borrowBook(),returnBook() as instance methods
getTotalBooks() as static method.
public class Book {
44
Book b2 = new Book("2024SSH");
getTotalBooks(); // 2
b2.borrowBook();
b1.borrowBook();
b1.returnBook();
getTotalBooks(); // 2
b2.returnBook();
getTotalBooks(); // 2
b2.returnBook(); // Already in the pool
}
}
is called.
all the variables declared inside the methods live in that allocated memory of the method in the stack.
2) Heap - No particular order, where all the objects live.
Follows dynamic memory allocation - memory is allocated at the runtime.
When an object is created,
Class c1=new Class();
new Class() is the object which will be stored in the heap, where as the c1 is the reference of the type
Class will be stored in the stack.
45
Local variables, which are declared inside a method or loop, are limited to that specific method or
loop only.
1. Generational Garbage Collection in Java means the heap is divided into parts based on how
long objects live.Young Generation: Where new objects go. Most of them die quickly and are
cleaned often.
2. Old Generation: Stores long-living objects that survived multiple cleanups.
3. Permanent Generation/Metaspace (Java 8+): Stores class-related info, not regular
objects.Memory cleanup frequency is very low on comparison with the other 2 generations.
finalize()
@Override
protected void finalize() throws Throwable {
System.out.println("Object is being garbage collected");
}
KEY POINTS
Oops concerns about data not the logic where as method oriented programming languages like c,
concerns about logic rather than data.
Instance methods/non-static methods in java can not be called without creating an object of the
class.
Class is a template used to create objects.
46
Static methods of the class can be called without creating an object.
new keyword is used to declare an array.
Variables declared inside a method are called local variables not instance variables.
We can not access instance variables through static methods directly, but can be through object
creation.
Each object has its own copy of instance variables, but static variables are shared across all
instances of the class.
In java any thing declared with the static keyword belongs to the whole class but not belongs to
the every individual object.
Variable scope is not defined at run-time but can be known at the compile-time on where the
variable is declared in the code block.
47
7. Control statements, Math & String
7.1) Ternary Operator
Variable=condition?expression1:expression1;
import java.util.Scanner;
7.2) Switch
import java.util.Scanner;
48
}
}
}
// Enter the week number: 7
// saturday
Usage of enhanced switch(after java12) was shown below
import java.util.Scanner;
switch (day) {
case 1:
case 2:
case 3:
System.out.println("Weekday");
break;
case 4:
case 5:
System.out.println("Almost Weekend");
break;
case 6:
case 7:
System.out.println("Weekend");
break;
default:
System.out.println("Invalid day");
}
// Weekday
CHALLENGES
1) Min of 2 numbers
import java.util.Scanner;
49
min = a < b ? a : b;
System.out.println("min:" + min); // min:5
}
}
2) Even or odd
3) Absolute of an integer
4) Score categorize
High if >80
Moderate if 50-80
Low if <50
50
case 10 -> "Oct";
case 11 -> "Nov";
case 12 -> "Dec";
default -> "Invalid input";
};
System.out.println(month);
}
}
7.3) Loops
Do-While
First execute the block then check the condition.
Guarantees to execute atleast once.
Unlike while, first iteration is unconditional.
Update the loop to avoid infinite loop.
import java.util.Scanner;
51
System.out.println("you age is " + age);
}
}
// Enter your age: -7
// Enter your age: 200
// Enter your age: 101
// Enter your age: 48
// you age is 48
Above is a program that asks the user repeatedly until he enters correct age.
For loop
Standard and most used loop.
Syntax:
for(initialization;condition;updation){
// body of for loop
}
import java.util.Scanner;
52
}
// 1
// 2
// 3
// 4
// 5
7.5) Recursion
Function calling itself is known as recursion.
Ideal for problems divisible into smaller and smaller problems.
import java.util.Scanner;
53
CHALLENGES
1) Use a do-while loop to find password checker until a valid password is entered.
import java.util.Scanner;
We can’t compare 2 strings using == operator as it compares the address stores in the reference
variable not the actual value.
2) Number guessing game
import java.util.Scanner;
54
// Multiplication table for what number: 10
// 10 * 1 = 10
// 10 * 2 = 20
// 10 * 3 = 30
// 10 * 4 = 40
// 10 * 5 = 50
// 10 * 6 = 60
// 10 * 7 = 70
// 10 * 8 = 80
// 10 * 9 = 90
// 10 * 10 = 100
55
for (int i : arr) {
if (i == find)
cnt++;
}
System.out.println(find + " occured " + cnt + " times!");
}
}
// Enter the number to find the no.of occurences in the array: 5
// 5 occured 2 times!
7) Read input from the user in a loop until they enter “exit”
import java.util.Scanner;
8) Sum all the +ve numbers entered by the user and skip -ve numbers
import java.util.Scanner;
56
// +ves sum = 22
57
// The string you have entered laaaaaaaaal was Palindrome
58
}
}
If you override the existing toString() method in java, the code is as follows
public class Home {
int noOfdoors;
int noOfwindows;
String hName;
59
public class StringTest {
public static void main(String[] args) {
String str1 = "Java"; // stored in string pool
String str2 = "Java"; // reuses str1 from string pool
String str3 = new String("Java"); // new object in heap
String str4 = str3.intern(); // refers to object in pool
// Comparing references
System.out.println("str1 == str2: " + (str1 == str2)); // true (same reference)
System.out.println("str1 == str3: " + (str1 == str3)); // false (different objects)
System.out.println("str1 == str4: " + (str1 == str4)); // true (interned reference)
// Comparing values
System.out.println("str1.equals(str3): " + str1.equals(str3)); // true (same content)
System.out.println("str3.equals(str4): " + str3.equals(str4)); // true (same content)
}
}
Can be concatinated using + operator.
Some of the methods of strings are length(), subString(), equals(), compareTo(), indexOf()
compareTo() compares the 2 strings lexicographically.
Return type of compareTo() function was int.
int res=Str1.compareTo(Str2);
0 if both are same, +ve if str1>str2, -ve if str1<str2.
In array .length is a field where as in string .length() is a method.
String uses more memory if you are modifying it frequently.
Formatted printing just like in c is possible with java also.
Above practice takes up the less memory compared to traditional string concatenating println()
method.
60
Feature String StringBuilder StringBuffer
Mutable ❌No ✅Yes ✅Yes
Thread-Safe ✅Yes ❌No ✅Yes
Performance ❌Slow ✅Fastest Slower than Builder
Best For Read-only Single-threaded Multi-threaded
61
String last_name = s.next();
sb.append(first_name).append(" ").append(last_name).toString();
System.out.printf("Your name is: %S", sb); // %S caps(s) is used to convert into
capitals
}
}
// Enter your first name: Hemanth
// Enter your last name: Muvvala
// Your name is: HEMANTH MUVVALA
OR
public class Student {
public static void main(String[] args) {
String fname = "Hemanth", lname = " Muvvala";
System.out.println(fname.concat(lname).toUpperCase()); // HEMANTH MUVVALA
System.out.println(fname); // Hemanth
System.out.println(lname); // Muvvala
// above 2 print statements shows that strings are immutable.
}
}
3) Find area and circumference of a circle for a given radius using Math.PI
import java.util.Scanner;
62
// 4
// Do you want to roll the dice(y/n): y
// 5
// Do you want to roll the dice(y/n): n
OR
int dice = (int)(Math.random() * 6) + 1;
OR
int dice = (int)(Math.ceil(Math.random() * 6));
5) Number guessing game where program selects a number and user had to guess.
import java.util.Scanner;
7) Create an object with final fields and use constructor to initialize them.
public class Student {
final int age;
final String name;
63
Student s = new Student(18, "Ramesh");
//s.age = 21; //Student.java:18: error: cannot assign a value to final variable age
}
}
// Hey HEMANTH! you are 20 years old.
// Hey RAMESH! you are 18 years old.
KEY POINTS
switch statement is only used to check the equality with constants.
Math.random() generates values from [0,1)
stringBuilder is faster than stringBuffer.
64
8. Encapsulation & Inheritance
8.1) Intro to OOP Principle
Oops principles are not specific to java.
There are 4 oop principles,
1) Encapsulation - only exposing the selected information from an object.
2) Abstraction - Hides the complex details to reduce complexity.
3) Inheritence - Entity can inherit attributes from another entity.
4) Polymorphism - Entities can have more than one form.
8.2) Encapsulation
Class is the combination of variables and methods.
Encapsulation hides internal data and allows access only through methods.
Encapsulation can be implemented in java through these access modifiers public, private, protected.
Getter and setter methods hides the properties and creates methods to expose information.
Maintains integrity by avoiding external interference.
Enhances modularity with the concepts of imports and packages.
import java.util.Scanner;
package in.Package1;
65
Packages avoids name collisions.
Packages can be used with the help of import statement.
Using import statement we can import classes, methods, packages and even interfaces.
Types of imports
1) Single_type import - imports a single class or interface from a package.
import java.util.Scanner;
2) On_demand import - imports all the classes and interfaces form a package.
import java.util.*;
There are 2 types of packages
1) Built-in packages
2) User-defined packages
Some of the built-in packages of java are java.util, java.lang, java.io etc.,
Built-in packages are those packages which we need not to import them explicitly.
A class can be either public/ default but can not be private/ protected.
66
As java is a case-sensitive language, it allows “Public” as the class name but not public.
Anything declared as private in Java can be accessed or modified indirectly using getter and setter
methods, which are usually declared as public and it is a fundamental concept in encapsulation.
package in.Package1;
//car.java
public class Car {
public String name;
public String model;
private int costOfPurchase;
public Car(String name, String model, int costOfPurchase) {
this.name = name;
this.model = model;
this.costOfPurchase = costOfPurchase;
}
public Car() {
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
return sb.append("name= ").append(name).append("\nmodel: ").append(model).append("\n
CostOf Purchase: ").append(costOfPurchase).toString();
}
}
package in.Package1;
//AccessTest.java
public class AccessTest {
public static void main(String[] args) {
Car c1 = new Car();
c1.name = "suzuki";
c1.model = "swift";
// c1.costOfPurchase = 5000; ❌ can not access private variables directly.
System.out.println(c1);
Car c2 = new Car("Toyota", "Fortuner", 50000);
System.out.println(c2);
}
}
Output:
name= suzuki
model: swift
CostOf Purchase: 0
name= Toyota
model: Fortuner
CostOf Purchase: 50000
You can read the private field costOfPurchase using the overridden toString() method from another
class.You cannot modify it directly from outside the Car class due to its private access.
67
Other than toString(), An inner class can access private members of the outer class in java.
public class Car {
private int cost = 1000;
class Engine {
void showCost() {
System.out.println("Cost: " + cost);
}
}
}
8.5) Getter and Setter methods
Getters are methods used to retrieve the value of a private variable.
Setters are methods used to modify the value of a private variable.
They encapsulate the access logic and provide controlled access to private fields of a class.
// Car.java
public class Car {
private String model;
private int price;
// Main.java
public class Main {
public static void main(String[] args) {
Car myCar = new Car();
}
}
If we use parameterized constructor then there will be no use of setter methods.Getter and setter
methods are typically declared as public, but they can also have default access if restricted to the
same package.
68
CHALLENGES
package com.example.geometry;
package com.example.geometry;
package com.example.utils;
import com.example.geometry.Circle;
import com.example.geometry.Triangle;
class Calculator {
public static void main(String[] args) {
Circle c1 = new Circle(5);
Triangle t = new Triangle(5, 4);
System.out.println("Area: " + Math.PI * Math.pow(c1.radius, 2));
System.out.println("Area of Triangle: " + (0.5 * t.base * t.height));
}
}
69
if (money <= 0) {
System.out.println("Invalid deposit");
} else {
this.balance += money;
balance();
}
}
public void withdraw(long money) {
if (this.balance < money)
System.out.println("Insufficient funds!");
else {
this.balance -= money;
balance();
}
}
}
3) Define a class Employee with private attributes name,age,salary and public methods to get and
set these attributes, and a package private method to display employee details. Create another
class in the same package to test access to the displayEmployeeDetails method.
70
e.displayEmployeeDetails();
}
}
1) Simple / single
2) Multiple (Not supported in java)
3) Multi-level
4) Hierarchical
71
8.8) Object class
In Java, the Object class is the superclass of all classes. Every class in Java implicitly extends Object,
unless it explicitly extends another class. Since Java does not support multiple inheritance with classes,
a class can extend only one other class at a time.
In any type of inheritance, such as single inheritance, if class B extends class A, then class A implicitly
extends the Object class. This means that all classes in Java—either directly or through a chain of
inheritance—ultimately inherit from the Object class.
toString()
equals()
hashCode()
getClass()
hashCode() is not designed to determine full equality, but to narrow down comparisons. It acts as a
fast pre-check: if two objects have different hash codes, they are definitely not equal. If they have the
same hash code, then we use equals() to do a full comparison.
If a class has many fields (say, 100), and you want to compare two objects using equals(), then
comparing each of those 100 fields can be expensive in terms of time and resources — especially in
large data structures like HashSet or HashMap.
That’s where hashCode() provides a performance optimization, two objects can have the same hash
code if their content is the same even if they belong to different classes, especially if they override the
hashCode() method.
If the hashcodes of both the objects are same then equals() may return true/ false.
If 2 objects returns true on equals() method then their hashcodes must be equal.
72
public Employe(String name, int age, String id) {
this.name = name;
this.age = age;
this.id = id;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public String getId() {
return id;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public void setId(String id) {
this.id = id;
}
@Override
public String toString() {
return "EqualsCheck [name=" + name + ", age=" + age + ", id=" + id + "]";
}
}
}
// toString method in String class doesn't compares the references as it overrides equals() by
default.
After overriding the equals method,
instanceof is a keyword that was used to check if the object is the instance of particular class /
subclass.
public class Employe {
private String name;
private int age;
private String id;
73
}
public String getId() {
return id;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public void setId(String id) {
this.id = id;
}
@Override
public String toString() {
return "EqualsCheck [name=" + name + ", age=" + age + ", id=" + id + "]";
}
@Override
public boolean equals(Object obj) {
if (obj instanceof Employe) {
Employe e = (Employe) obj;
return e.name.equals(name) && e.age == age && e.id.equals(id);
} else {
return false;
}
}
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + age;
result = prime * result + ((id == null) ? 0 : id.hashCode());
return result;
}
@Override
74
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Employe other = (Employe) obj;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
if (age != other.age)
return false;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
return true;
}
75
Local Inner Classes
Defined inside a method or block.
Scope is limited to that method/block.
Cannot be accessed outside their defined scope.
Anonymous Inner Classes
Classes without a name, created for one-time use (usually to implement interfaces or abstract
classes).
Defined and instantiated in a single expression.
Top-Level Classes
Cannot be declared private or protected.
Can only be public or package-private (default).
Inner classes, however, can be private, protected, default, or public.
a non-static inner class cannot be accessed without creating an object of the outer class.
public class Outer {
class Inner {
void show() {
System.out.println("Non-static inner class");
}
}
Static Nested Class Does not require an instance of the outer class.Only needs to be accessed using
the outer class name.
public class Outer {
static class Inner {
void show() {
System.out.println("Static nested class");
}
}
Local Inner Class (Defined in a method/block) are accessible only within the block where it is defined.
76
}
}
CHALLENGES
1. Start with a base class LibraryItem that has itemID, title, author and methods checkOut(),
returnItem(). create sub class like book, magazine, DVD, each inheriting from LibraryItem. Add unique
attributes to each subclass like ISBN for book, issueNumber for magazine and duration for DVD.
package in.Package2;
77
}
public void returnItem() {
System.out.println(this.toString() + " returned successfully");
}
}
package in.Package2;
package in.Package2;
package in.Package2;
78
}
public static void main(String[] args) {
Dvd d1 = new Dvd(154, "my project", "me", 50);
d1.returnItem();
}
public int getDuration() {
return duration;
}
@Override
public String toString() {
return super.toString() + "Dvd [duration=" + duration + "]";
}
public void setDuration(int duration) {
this.duration = duration;
}
}
2. Create a person class with name and age as attributes and override equals to compare person
objects based on their attributes and override hashcode() consistent with the definition of equals.
package in.Package2;
79
3. Create a class ArrayOperations with a static nested class Statistics. Statistics should have methods
like mean(), median() which operates on an array.
package in.Package2;
import java.util.Arrays;
KEY POINTS
Parent classes are known as general classes, whereas child classes are considered specific classes.
The access modifier of the object class can be either public or default, but it cannot be private or
protected as it is not an inner class.
A static nested class cannot access instance variables of the outer class.
Override both the toString() and hashCode() methods to maintain consistency.
80
9. Abstraction and Polymorphism
9.1) Abstraction
Abstraction hides complex implementation details and focuses only essential features.
We can’t see the fan’s circuit but it will work if we turn on the switch.
Focuses on functionality, more on what an object does but not how it does through clear interfaces.
Reduces the complexity by showing only relevant information in a class design.
The abstract keyword in Java can be applied to both classes and methods. An abstract class cannot be
instantiated, meaning we cannot create an object of it directly. However, it can be inherited by other
classes.
For example, consider an abstract class named Vehicle. We can create multiple subclasses that extend
the Vehicle class, such as Car, Bike, Cycle, etc. If someone asks you to create a Cycle, you can create
an object of the Cycle class. But if someone asks you to create a Vehicle, the question becomes
unclear—what kind of vehicle? Since Vehicle is abstract and represents a general concept, you cannot
create an object of it. Instead, you instantiate one of its specific subclasses.
package Lecture9;
package Lecture9;
81
this.noOfDoors = noOfDoors;
}
}
package Lecture9;
package Lecture9;
82
package Lecture9;
9.3) Interfaces
Interfaces in Java are not classes, but they are similar in structure. They are defined using the
interface keyword. To implement an interface in a class, we use the implements keyword, which
works similarly to extends in class inheritance, but is specifically used for interfaces.
Abstract methods can have a return type and parameters, but they do not have a method
body—this is because they are meant to be implemented by subclasses. The return type helps
define what kind of value the method is expected to return once it is implemented.
When an interface is compiled, it generates a .class file, just like a regular class.
Naming conventions for interfaces are the same as those for classes (typically starting with an
uppercase letter and using CamelCase).
By default, interfaces in Java can only contain abstract methods (before Java 8). All methods in
an interface are implicitly public and abstract, so there's no need to declare them explicitly with
those modifiers.
On the other hand, abstract methods in an abstract class can have any access modifier: public,
protected, default, or even private (though private abstract doesn't make practical sense since it
can't be overridden).
You can think of an abstract class as a "responsibility" that subclasses must fulfill. If a subclass
does not override all abstract methods of its abstract parent, it must also be declared abstract.
If the first subclass does override all abstract methods, then its own subclasses are not required
to override anything and don’t need to be abstract.
package Lecture9;
83
}
package Lecture9;
package Lecture9;
A class in Java can implement multiple interfaces, and it can also extend a class while implementing
multiple interfaces.
class Dog extends Animal implements Pet, Guard {
// class body
}
84
Interfaces can have default methods and also static methods.
interface Pet {
void play(); // abstract method
interface A {
default void show() {
System.out.println("Default A");
}
}
interface A {
default void show() {
System.out.println("A");
}
}
interface B {
default void show() {
System.out.println("B");
}
}
class MyClass implements A, B {
// ❌ Compilation error unless we override show()
// ✅ Resolving the conflict:
public void show() {
System.out.println("MyClass resolves conflict");
}
}
Static methods can not be overriden and are called using interface name.
InerfaceNmae.methodName();
Pet.petInfo();
Java does not support multiple inheritance with classes (i.e., a class cannot extend more than one
class) to avoid ambiguity problems like the Diamond Problem.
However, Java does support multiple inheritance using interfaces.
85
A class can implement multiple interfaces, effectively inheriting behaviors from all of them.
interface Flyable {
void fly();
}
interface Swimmable {
void swim();
}
// A class implementing both interfaces
class Duck implements Flyable, Swimmable {
public void fly() {
System.out.println("Duck is flying");
}
public void swim() {
System.out.println("Duck is swimming");
}
}
CHALLENGES
1) Create an abstract class shape with an abstract method calculateArea(). Implement 2 sub classes:
circle and square, each subclass should have relevant attributes like radius for circle, side for square
and their own implementation of the calculateArea() method.
package Lecture9;
package Lecture9;
86
public class Circle extends Shape {
private final int r;
public Circle(int r) {
this.r = r;
}
@Override
public void calculateArea() {
System.out.println("Area of the circle: " + Math.PI * r * r);
}
public int getR() {
return r;
}
}
package Lecture9;
package Lecture9;
2) Create an interface Flyable with an abstract method fly(). create an abstract class Bird that
implements Flyable. Implement a subclass eagle that extends bird. Provide an implementation for the
fly() method.
package Lecture9;
package Lecture9;
87
public abstract class Bird implements Flyable {
private final String breed;
public Bird(String breed) {
this.breed = breed;
}
public String getBreed() {
return breed;
}
}
package Lecture9;
package Lecture9;
88
Downcasting:
Casting a superclass reference back to a subclass type. To access sub-class specififc methods.
Manual and risky.
When you override a method in a subclass and then upcast the object to its superclass, Java still calls
the overridden method in the subclass, not the one in the superclass.
public class Main {
public static void main(String[] args) {
Animal a = new Dog(); // Upcasting
a.sound(); // Output is "Dog barks" not "Animal sounds"
}
}
89
}
package Lecture9;
In Java, when a method with the same name exists across multiple levels of an inheritance
hierarchy—such as classes A, B, and C, where C extends B and B extends A—each class can override
that method. If class C calls super.start(), it will invoke the start() method defined in class B, because
super refers only to the immediate parent class. Even though class A also defines a start() method, it
cannot be directly accessed from class C using something like super.super.start(), because Java does
not support multi-level super calls like super.super. To access class A’s start() method from class C, an
indirect approach must be used: you can define a method in class B that explicitly calls super.start()
(which refers to A's method), and then call that method from class C.
package Lecture9;
90
package Lecture9;
package Lecture9;
package Lecture9;
91
}
package Lecture9;
package Lecture9;
}
}
Java passes reference’s value for objects, modifications to the objects in the method can affect the
originals.
Primitive types always passes by value. Infunction changes doesn’t affect the originals.
92
package Lecture9;
CHALLENGES
1) In a class calculator, create multiple add methods that overloads each other and can sum 2 integers,
3 integers, or 2 doubles. Demonstrate how each can be called with different number of parameters.
package Lecture9;
93
If the method add(int a, int b) is commented out, then the call c.add(10, 20) will use the method
add(double a, double b) because Java supports implicit type casting from int to double.
However, if the method add(double a, double b) is commented out, the call c.add(4.5, 3.5) will result
in a compile-time error since Java does not allow implicit narrowing from double to int.
2) Define a base class Vehicle with method service() and a subclass Car that overrides the serivice().In
car’s service() provide a specific implementation that calls super.service() as well, to show how
overriding works.
public class Vehicle {
public void service() {
System.out.println("Vehicle is servicing....");
}
}
@Override
public void service() {
System.out.println("car is servicing.....");
}
public void callParentService() {
super.service();
}
public static void main(String[] args) {
Car1 c = new Car1();
c.service(); // car is servicing.....
c.callParentService(); // Vehicle is servicing....
}
}
KEY POINTS
1) Abstraction hides the internal implementation and shows only the functionality to the users.
2) An abstract class in Java does not need to have any abstract methods. However, if a method is
declared as abstract, then the class must also be declared abstract.
3) In Java, an interface cannot have instance fields or variables.
All variables declared in an interface are implicitly:
interface MyInterface {
int VALUE = 100; // implicitly public static final
}
Is same as
interface MyInterface {
public static final int VALUE = 100;
}
94
6) Java supports pass by value for reference types.
7) When an object is passed to a method then java copies reference to the object.
95
10. Exception and File Handling
10.1) What is an Exception
Exception is a disruptive event that interrupts the normal flow of execution of a program.
It’s an instance of a problem that arises when program is running.
Exceptions are objects that encapsualtes the info about he error and that info includes type and state
of the progarm when the error occurred.
10.2) Try-Catch
Exceptions are classes.
Try block is the block of code that causes the exception.
Catch block is the block of code that catches and handles the exception thrown by the try block.
96
If your code has multiple catch blocks for different exceptions, only the first exception that is thrown
in the try block and matches a catch block will be caught. Once an exception is caught by a catch block,
no further catch blocks will be checked for that exception.
Below is how you handle a single exception.
package Lecture10;
import java.util.Scanner;
public class Calculator {
public static void main(String[] args) {
Scanner s = new Scanner(System.in);
int a, b;
System.out.print("Enter numerator: "); // Enter numerator: 5
a = s.nextInt();
System.out.print("Enter denominator:"); // Enter denominator: 0
b = s.nextInt();
try {
System.out.println("Division: " + (a / b));
} catch (ArithmeticException hurrrayyy) {
System.out.println(hurrrayyy.getMessage()); // by zero
System.out.println("can't divide with zero"); // can't divide with zero
}
}
}
when writing multiple catch blocks in Java, the general rule is that the more specific exceptions
should be caught first, followed by the more general ones.
Most specific exceptions should come first (like ArithmeticException).
More general exceptions like Exception should come after specific ones.
The most general Throwable class should be the last one, as it can catch any exception or error
that wasn't caught earlier.
We can write multiple try-catch blocks.
package Lecture10;
import java.util.Scanner;
public class Calculator {
public static void main(String[] args) {
Scanner s = new Scanner(System.in);
int a, b;
System.out.print("Enter numerator: "); // Enter numerator: 5
a = s.nextInt();
System.out.print("Enter denominator: "); // Enter denominator: 0
b = s.nextInt();
try {
// This will cause ArrayIndexOutOfBoundsException
int[] arr = new int[5];
arr[5] = 10; // This will throw ArrayIndexOutOfBoundsException
} catch (ArrayIndexOutOfBoundsException | ArithmeticException e) {
// Catch both exceptions in a single block
System.out.println("Exception caught: " + e.getMessage());
if (e instanceof ArithmeticException) {
System.out.println("Can't divide by zero.");
} else if (e instanceof ArrayIndexOutOfBoundsException) {
System.out.println("Limit of array exceeded.");
97
}
} catch (Exception ex) {
System.out.println("General exception");
} catch (Throwable t) {
System.out.println("Throwable Exception.");
}
try {
// Now perform the division operation, which could throw ArithmeticException
System.out.println("Division: " + (a / b)); // This can throw ArithmeticException
} catch (ArithmeticException hurrrayyy) {
System.out.println(hurrrayyy.getMessage());
System.out.println("Can't divide with zero");
} catch (Exception ex) {
System.out.println("General exception");
} catch (Throwable t) {
System.out.println("Throwable Exception.");
}
}
}
Checked exceptions are those exceptions that must be either caught or declared in the method using
throws keyword.
Unchecked exceptions are need not to be handled explicitly.
All the exceptions we handles above are the unchecked exceptions.
Checked exceptions must be handled and it’s your wish to handle unchecked exceptions.
User-defined exceptions in Java are custom exception classes that you create yourself to handle
specific, application-related error.
Only used for checked exceptions (e.g., IOException, SQLException), not for unchecked ones like
ArithmeticException.
class Calculator {
Throw keyword
Used to explicitly throw an exception(either new or an existing exception object) form a method or a
block of code.
You can throw any Throwable (checked or unchecked).
Ex: throw new ArithmeticException(“Division by zero”);
exit code = 0: program ran successfully.
exit code ≠ 0: abnormal termination (usually due to an unhandled exception).
package Lecture10;
import java.util.Scanner;
public class Calculator {
public static void main(String[] args) {
a();
}
public static void a() {
99
b();
}
public static void b() {
c();
}
public static void c() {
d();
}
public static void d() {
Scanner s = new Scanner(System.in);
int a, b;
System.out.print("Enter numerator: ");
a = s.nextInt();
System.out.print("Enter denominator:");
b = s.nextInt();
try {
int[] arr = new int[5];
arr[5] = 10;
System.out.println("Division: " + (a / b));
} catch (ArithmeticException hurrrayyy) {
System.out.println(hurrrayyy.getMessage());
System.out.println("can't divide with zero");
} catch (Throwable t) {
System.out.println("Limit of array exceeded.");
throw t;
}
}
}
throw throws
Java throw keyword is used to explicitly throw an
Java throws keyword is used to declare an exception.
exception.
Checked exceptions cannot be propagated using
Checked exceptions can be propagated with throws.
throw.
Syntax: throw is followed by an instance of an
Syntax: throws is followed by exception class names.
exception class.
Example: throw new NumberFormatException("Invalid
Example: throws IOException, SQLException
input");
throw is used inside a method body. throws is used in the method declaration (signature).
You can throw only one exception at a time using
You can declare multiple exceptions using throws.
throw.
100
Example: throw new IOException("Connection Example: public void method() throws IOException,
failed!!"); SQLException
Here the exception is not handled by main() methods, it also escaped by defining throws in its
signature.
Here is how you create and handle a user-defined exception.
// This is a checked exception because it extends Exception
public class InvalidAgeException extends Exception {
public InvalidAgeException(String message) {
super(message);
}
}
import java.util.Scanner;
101
// Inner try block logic
} catch (Exception e1) {
// Inner catch block
} catch (AnotherException e2) {
// Another inner catch block
} finally {
// Inner finally block (always executes)
}
} catch (OuterException e3) {
// Outer catch block
} catch (Exception e4) {
// Another outer catch block
} finally {
// Outer finally block (always executes)
}
package Lecture10;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner s = new Scanner(System.in);
System.out.print("Did you purchased the ticket: ");
boolean bool = s.nextBoolean();
try {
if (!bool) {
throw new CustomException(bool);
} else {
System.out.println("You can gooo!");
}
} catch (CustomException e) {
System.out.println(e.getMessage());
} catch (Exception e) {
System.out.println("General exception: " + e.getMessage());
} finally {
System.out.println("Thaks for using this service");
}
}
}
102
If we enter a number instead of true/false then it will be caught by the generic catch (Exception e)
block in our code.
CHALLENGE
Write a program that asks the user to enter 2 integers and then divides first by second. The program
should handle any arithmetic exceptions that may occur (like division by 0) and display an appropriate
message.
package Lecture10;
import java.util.Scanner;
public class DivisonBy0 {
public static void main(String[] args) {
try {
Scanner s = new Scanner(System.in);
int a, b;
System.out.print("Enter 1st num: "); // true
a = s.nextInt();
System.out.print("Enter 2nd num: ");
b = s.nextInt();
System.out.println("Division: " + (a / b));
} catch (ArithmeticException e) {
System.out.println("Invalid input \nArithmetic exception occured");
} catch (Exception e) {
System.out.println("General exception: " + e.getMessage()); // General exception:
For input string: "true"
} finally {
System.out.println("Thank you for using this program!"); // Thank you for using
this program!
}
}
}
103
FileWriter(File f) → creates a FileWriter obj given a file object.
flush() → forcefully clear any buffered data and send it to the intended destination I.e., file.
close() → Releases the resources.
close() automatically calls flush(), but calling flush() manually is useful when you want to write
intermediate data without closing the stream.
import java.io.FileWriter;
104
import java.io.FileReader;
import java.io.IOException;
CHALLENGE
File not found exception handling.
Write a program to read a filename from the user and display its content. The progarm should handle
the situation where the file doesnot exists.
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.Scanner;
OR
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.Scanner;
105
while ((val = fr.read()) != -1) {
System.out.print((char) val);
}
} catch (FileNotFoundException f) {
System.out.println("file can not be found: " + f.getMessage());
} catch (IOException e) {
System.out.println("IOException occured: " + e.getMessage());
}
}
}
KEY POINTS
A method that throws a checked exception must declare the exception using the throws
keyword in its signature.
We can handle more than 1 exception in a single catch block.
Finally block will always be executed even if try-catch have return statements.
Catch block will not be executed if an error isn’t occurred in the try block.
Unchecked exceptions are direct subclass of Exception not Throwable but Exception is direct
subclass of Throwable.
A catch block can not exist independently without try block.
throw keyword is used to propagate an exception up the call stack.
106
11. Collections & Generics
11.1) Variable arguments
Java's varargs feature allows methods to accept any number of arguments.
It is declared using an ellipsis (...).
Internally, varargs are treated like arrays.
A method using varargs can still be called even if no arguments are passed.
If you want to require at least two arguments, you can declare two fixed parameters followed by
varargs:
void sum(int a, int b, int... nums){
Here, a and b are mandatory, and nums can accept any additional arguments.
Varargs must always be the last parameter in the method signature.
Its also valid to write String… args as the main method signature.
package Lecture11;
107
int a = null; // invalid
Integer b = null; // valid
AutoBoxing is the automatic conversion of primitive types to their corresponding wrapper class
objects.
Unboxing is the automatic conversion of wrapper class objects back to their respective primitive
types.
package Lecture11;
108
We can create custom collections by implementing the Collection interface (or a more specific
interface such as List, Set, or Queue) in our class and overriding methods such as add(), remove(),
clear(), and size().
A LinkedList in Java is an example of multiple inheritance via interfaces, as it implements both
the List and Queue interfaces.
109
printList(list); // Output: 10, 20, 30, 40
// Removing elements
list.remove(0); // Removes element at index 0 (10) -> list: [20, 30, 40]
list.remove(Integer.valueOf(40)); // Removes the value 40 -> list: [20, 30]
// 10
// 20
// 30
// 40
// -----------------------
// 20
// 30
// -----------------------
// 20
// 30
// 30
// -----------------------
// false
// -1
// -----------------------
<> are used in Java generics to help developers catch type-related errors at compile time. These
are known as diamond operators or diamond brackets.
After compilation, the compiler performs a process called type erasure, which removes the
generic type information. This means the type parameters (inside <>) do not exist at runtime.
Generics are used at compile time only to enforce type safety. Example:
List li = new ArrayList();
110
The above line is valid in Java, but it creates a raw type. This means the list can store any type of
object (e.g., Integer, String, etc.), but it disables compile-time type checking and may lead to runtime
ClassCastException. It's recommended to use generics like this:
List<String> li = new ArrayList<>();
This ensures that only String objects can be added to the list, providing better type safety and
avoiding casting issues.
Method Description
add(E e) → Adds an element to the list at the end.
add(int index, E element) → inserts an element at the specified index, shifting elements to the right.
remove(int index) → Removes the element at the specified index.
remove(Object o) → Removes the first occurrence of the specified element.
get(int index) → Retrieves the element at the specified index.
set(int index, E element) → Replaces the element at the specified index with the given element.
contains(Object o) → Checks if the list contains the specified element.
indexOf(Object o) → Returns the index of the first occurrence of the specified element.
clear() → Removes all elements from the list.
size() → Returns the number of elements currently in the list.
Method Description
add(E e) → inserts specific element, throws exception if it can not be added.
offer(E e) → Same as above but returns false if the element can not be added.
remove() → retreives and removes the head of the queue, throws exception if queue is empty.
poll() → same as above but returns null if queue is empty.
element() → Retrieves but doesnot remove the head of the queue, throws exception if queue is empty.
peek() → Same as above but returns null if queue is empty.
Since we need to print collections multiple times, we define a custom utility class named Hemanth
with a static method print(). This method accepts a Collection object as an argument, which allows it
to handle any type of collection (like List, Queue, Set, etc.) because Collection is a common parent
interface.
111
import java.util.Collection;
import java.util.LinkedList;
import java.util.Queue;
// Collection is: 10 20
// 10
// 10
// Collection is: 10 20
// 10
// Collection is: 20
// 20
// Collection is:
// null
11.6) Set Interface
Contains unique elements.
Doesnot guarentees any specific order of elements.
No positional access I.e doesnot support indexing.
Common implementations are HashSet, LinkedHashSet, and TreeSet(maintains order and
uniqueness).
The contains() method in a Set is usually faster than in a List.
Method Description
add(E e) → Returns true if the element was added, false if it already exists
remove(Object o) → Returns true if the element was present and removed, else false.
contains(Object o) → Returns true if the element exists in the set.
112
size() → returns no.of elements in the set.
isEmpty() → Returns true if the set has no elements, else false.
import java.util.HashSet;
import java.util.Set;
// true
// true
// true
// false
// Collection is: Apple Hemanth Line2
// 3
113
}
// Collection is: 10 5 -7
// Collection is: -7 5 10
// 10
// -7
// Collection is: 10 5 -7
// 1
CHALLENGES
1) Write a method concatinate strings that takes variable arguments of string type and
concatinates them into a single string.
public class Challenge1 {
public static void concatinateString(String... lines) {
StringBuilder sb = new StringBuilder();
for (String line : lines)
sb.append(line).append(" ");
System.out.println("Final String: " + sb.toString());
}
2) Write a program that sorts a list of String objects in descending order using a custom comparator.
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Arrays;;
114
Collection is: C B A Z
Collection is: Z C B A
OR
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
// Collection is: C A B D
// Collection is: A B C D
// Collection is: D C B A
3) Use the collections class to count the frequency of a particular element in an arraylist.
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
// Collection is: 1 2 3 2
// Frequency of 2: 2
115
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("one");
list.add("Two");
list.add("Three");
System.out.print("Before ");
Hemanth.print(list);
swap(list, 0, 2);
}
}
5) Create a program that reverses the elements of a list and prints the reversed list.
import java.util.List;
import java.util.Arrays;
OR
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Arrays;
// Before [1, 2, 3]
// After [3, 2, 1]
6) create a PriorityQueue of a custom class Student with attributes name and grade. Use a
comparator to order by grade.
import java.util.Comparator;
import java.util.PriorityQueue;
import java.util.Queue;
116
public static class InnerStudent1 {
private final String name;
private final char grade;
public InnerStudent1(String name, char grade) {
this.name = name;
this.grade = grade;
}
public String getName() {
return name;
}
public char getGrade() {
return grade;
}
@Override
public String toString() {
return name + " : " + getGrade();
}
}
public static void main(String[] args) {
PriorityQueue<InnerStudent1> q = new PriorityQueue<>(new Comparator<InnerStudent1>() {
@Override
public int compare(Student1.InnerStudent1 o1, Student1.InnerStudent1 o2) {
return o1.getGrade() - o2.getGrade();
}
});
q.offer(new InnerStudent1("Hemanth", 'E'));
q.offer(new InnerStudent1("Amrish", 'C'));
q.offer(new InnerStudent1("Nobitha", 'F'));
q.offer(new InnerStudent1("Deskisuki", 'A'));
System.out.println(q);
System.out.println(q.poll());
System.out.println(q.poll());
System.out.println(q.poll());
System.out.println(q.poll());
System.out.println(q.poll());
}
}
// [Deskisuki : A, Amrish : C, Nobitha : F, Hemanth : E]
// Deskisuki : A
// Amrish : C
// Hemanth : E
// Nobitha : F
// null
7) Write a program that takes a string and returns the number of unique characters using set.
import java.util.HashSet;
import java.util.Scanner;
import java.util.Set;
117
// Enter a string: Hemanth
// Number of unique elements are: 7
OR
we can use the method toCharArray.
import java.util.HashMap;
import java.util.Map;
// 4
// 68
// null
// 68
// {A=65, B=66, C=67}
118
// false
// [A, B, C]
// [65, 66, 67]
// A:65 B:66 C:67
11.9) Enums
Enums (short for enumerations) are special types of classes that represent a fixed set of
constants.
They are used when a variable can only take one out of a small set of predefined values (e.g.,
directions, colors, traffic lights).
Enum constants are usually written in uppercase letters by convention.
Enums are defined using the enum keyword.
enum Color {
RED, GREEN, BLUE;
}
Semicolon is optional if you are no longer declaring a constructor or an attribute or any methods.
You can access enum constants using the dot (.) operator:
Color myColor = Color.RED;
values() → Returns an array of all enum constants in the order they're declared.
valueOf(String name) → Returns the enum constant with the specified name (case-sensitive). Throws
IllegalArgumentException if the name doesn't match.
public enum TreafficLights {
RED("Stop"), GREEN("Go"), YELLOW("Get ready to go");
import javax.swing.Action;
119
// Light from valueOf(): RED
// Action: Stop
// All traffic lights and actions:
// RED -> Stop
// GREEN -> Go
// YELLOW -> Get ready to go
With generics, explicit casting is not required, as the type is already known to the compiler.
ArrayList list = new ArrayList();
list.add("Java");
Generics are denoted using angle brackets (<>), where you specify the type parameter (e.g.,
List<String> means a list that holds String values).
The "diamond operator" (<>) specifically refers to a feature introduced in Java 7 that allows you
to omit the type on the right-hand side of a declaration when it can be inferred by the compiler.
Below is the SpecificClass implementation where we can only store integers (or the defined type). To
store other types, you'd need separate classes.
public class SpecificClass {
private final int var;
Below is the generic lass where we can create any type of object , no type cast needed:The type is
preserved at compile time (but erased at runtime — known as type erasure).
120
public class GenericClass<T> {
private final T var;
public T getVar() {
return var;
}
public GenericClass(T var) {
this.var = var;
}
}
Type Erasure is a process by which Java compiler removes all generic type information during
compilation. This means that generic types exist only at compile time, and the compiled bytecode
contains non-generic code (i.e., raw types).
CHALLENGES
1) Create an enum called Day that represents the days of the week. Write a program that prints out
all the days of the week from this enum.
public enum Day {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY;
}
// MONDAY
// TUESDAY
// WEDNESDAY
// THURSDAY
// FRIDAY
// SATURDAY
// SUNDAY
2) Enhance the Day enum by adding an attribute that indicates whether it is a weekday or weekend.
Add a method in the enum that returns whether it’s a weekday or weekend, and write a program to
print out each day along with its type.
public enum Day {
MONDAY("Weekday"), TUESDAY("Weekday"), WEDNESDAY("Weekday"), THURSDAY("Weekday"),
FRIDAY("Weekday"),SATURDAY("Weekend"), SUNDAY("Weekend");
121
private final String type;
private Day(String type) {
this.type = type;
}
public String getType() {
return type;
}
}
// MONDAY->Weekday
// TUESDAY->Weekday
// WEDNESDAY->Weekday
// THURSDAY->Weekday
// FRIDAY->Weekday
// SATURDAY->Weekend
// SUNDAY->Weekend
3) Create a map where the keys are country names(as String) and the values are their capitals(also
String). Populate the map with at least 5 countries and their capitals. Write a program that prompts
user to enter a country name and then displays the corresponding capital, if it exists in the map.
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
122
KEYPOINTS
Autoboxing automatically converts the primitive type to its respective wrapper class object. For
example, int to Integer, double to Double, etc.
Not every class in the collections library implements the Collection interface, like Map. Map is
part of the Java Collections Framework, but it does not implement the Collection interface.
List maintains insertion order and allows adding elements at specified positions.
Implementations like ArrayList and LinkedList maintain order and allow indexed insertion.
In Map, keys are unique, but values can be duplicated. A key maps to exactly one value, but
multiple keys can map to the same value.
Enums in Java can implement interfaces and can also have attributes, methods, and constructors.
Enums are powerful and can behave much like regular classes.
With the diamond operator (<>), we don’t need to specify the type on the right-hand side when
initializing an object. This was introduced in Java 7 for cleaner and type-safe code:
Wrapper classes in Java like Integer and Double cannot be extended because they are final
classes. You cannot inherit from these classes.
123
12. Multi threading & Executor Service
12.1) What is a Thread?
A Thread is a small part of a program that can execute concurrently with other parts of the program.
It allows a program to perform multiple tasks simultaneously, such as handling multiple user requests
or executing different tasks at the same time.
Threads can be created by either:
Extending the Thread class, or by
Implementing the Runnable interface.
Use threads when you need to perform independent tasks concurrently, such as managing multiple
requests or executing large, time-consuming jobs.
Thread Communication:
Threads can communicate with each other to coordinate their work using methods like wait(), notify(),
and notifyAll().
wait(): Causes the current thread to release the monitor and go to the waiting state.
notify(): Wakes up a single thread that is waiting on the monitor.
notifyAll(): Wakes up all threads that are waiting on the monitor.
Thread and CPU Interaction:
Threads have a direct relationship with the CPU. Each thread runs on a CPU core, and if you have
more threads than CPU cores, some threads may enter a waiting state until a core becomes available.
Single-threaded Program:
In a single-threaded program (or single-core system), all tasks run sequentially, with only one task
being processed at a time.
The main thread, created by the Java Virtual Machine (JVM), executes the program.
Dual-Core CPUs and Beyond:
Dual-core processors (released by Intel in 2005) allow for parallel execution of multiple threads,
improving performance by utilizing both cores.
Multi-threading vs. Multi-programming:
Multi-threading: Involves multiple threads running within the same program.
Multi-programming: Involves multiple independent programs running on the system, often
sharing system resources.
Thread Behavior on Multiple Cores:
If there are 4 threads in your program and your system has 4 cores, the threads can run
simultaneously across the cores.
If the number of threads exceeds the number of available CPU cores, some threads will remain in a
waiting state until a core becomes available.
public class Main {
public static void main(String[] args) {
long start_time = System.currentTimeMillis();
124
// task-1
for (int i = 1; i <= 100; i++) {
System.out.printf("%d* ", i);
}
System.out.println("\nCompleted * task");
// task-2
for (int i = 1; i <= 100; i++) {
System.out.printf("%d$ ", i);
}
System.out.println("\nCompleted $ task");
// task-3
for (int i = 1; i <= 100; i++) {
System.out.printf("%d# ", i);
}
System.out.println("\nCompleted # task");
long end_time = System.currentTimeMillis();
System.out.println("Total Time taken(in ms) is: " + (end_time - start_time));
}
}
// 1* 2* 3* 4* 5* 6* 7* 8* 9* 10* 11* 12* 13* 14* 15* 16* 17* 18* 19* 20* 21* 22* 23* 24* 25*
26* 27* 28* 29* 30* 31* 32* 33* 34* 35* 36* 37* 38* 39* 40* 41* 42* 43* 44* 45* 46* 47* 48*
49* 50* 51* 52* 53* 54* 55* 56* 57* 58* 59* 60* 61* 62* 63* 64* 65* 66* 67* 68* 69* 70* 71*
72* 73* 74* 75* 76* 77* 78* 79* 80* 81* 82* 83* 84* 85* 86* 87* 88* 89* 90* 91* 92* 93* 94*
95* 96* 97* 98* 99* 100*
// Completed * task
//
1$ 2$ 3$ 4$ 5$ 6$ 7$ 8$ 9$ 10$ 11$ 12$ 13$ 14$ 15$ 16$ 17$ 18$ 19$ 20$ 21$ 22$ 23$ 24$ 25$ 26$
27$ 28$ 29$ 30$ 31$ 32$ 33$ 34$ 35$ 36$ 37$ 38$ 39$ 40$ 41$ 42$ 43$ 44$ 45$ 46$ 47$ 48$ 49$ 50
$ 51$ 52$ 53$ 54$ 55$ 56$ 57$ 58$ 59$ 60$ 61$ 62$ 63$ 64$ 65$ 66$ 67$ 68$ 69$ 70$ 71$ 72$ 73$
74$ 75$ 76$ 77$ 78$ 79$ 80$ 81$ 82$ 83$ 84$ 85$ 86$ 87$ 88$ 89$ 90$ 91$ 92$ 93$ 94$ 95$ 96$ 97
$ 98$ 99$ 100$
// Completed $ task
// 1# 2# 3# 4# 5# 6# 7# 8# 9# 10# 11# 12# 13# 14# 15# 16# 17# 18# 19# 20# 21# 22# 23# 24# 25#
26# 27# 28# 29# 30# 31# 32# 33# 34# 35# 36# 37# 38# 39# 40# 41# 42# 43# 44# 45# 46# 47# 48#
49# 50# 51# 52# 53# 54# 55# 56# 57# 58# 59# 60# 61# 62# 63# 64# 65# 66# 67# 68# 69# 70# 71#
72# 73# 74# 75# 76# 77# 78# 79# 80# 81# 82# 83# 84# 85# 86# 87# 88# 89# 90# 91# 92# 93# 94#
95# 96# 97# 98# 99# 100#
// Completed # task
// Total Time taken(in ms) is: 57
125
The number of instances is equal to the number of threads only if the class extends Thread or
implements Runnable, and you explicitly call start() on each instance.
For example, if you create three instances of such a class and call start() on each, then three
separate threads will be created and run concurrently.
But if you simply create 3 instances of a regular class that doesn’t extend Thread or implement
Runnable, then you won’t have 3 threads - everything will run on the main thread.
In the below example 3 threads are created and started and then executed independently.
public class Task extends Thread {
private final char symbol;
Task(char symbol) {
this.symbol = symbol;
}
public char getSymbol() {
return symbol;
}
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
System.out.printf("%d%c ", i, symbol);
}
System.out.println("\n" + Thread.currentThread().getName() + " : Completed " + symbol
+ " task");
}
}
class Main {
public static void main(String[] args) {
Task t1 = new Task('*');
Task t2 = new Task('$');
Task t3 = new Task('#');
long start_time = System.currentTimeMillis();
t1.start();
t2.start();
t3.start();
long end_time = System.currentTimeMillis();
System.out.println("Total Time taken(in ms) is: " + (end_time - start_time));
System.out.printf("%s : completed", Thread.currentThread().getName());
}
}
// 1# 2# 3# 4# 5# 6# 7# 8# 9# 10# 11# 12# 13# 14# 15# 16# 17# 18# 19# 20# 21#
// 22# 23# 24# 25# 26# 27# 28# 29# 30# 31# 32# 33# 34# 35# 36# 37# 38# 39# 40#
// 41# 42# 43# 44# 45# 46# 47# 1$ 2$ 3$ 4$ 5$ 6$ 7$ 8$ 9$ 10$ 11$ 12$ 13$ 14$
// 15$ 16$ 17$ 18$ 19$ 20$ 21$ 22$ 23$ 24$ 25$ 26$ 27$ 28$ 29$ 30$ 31$ 32$ 33$
// 34$ 35$ 36$ 37$ 38$ 39$ 40$ 41$ 42$ 43$ 44$ 45$ 46$ 47$ 48$ 49$ 50$ 51$ 52$
// 53$ 54$ 55$ 56$ 57$ 58$ 59$ 60$ 61$ 62$ 63$ 64$ 65$ 66$ 67$ 68$ 69$ 70$ 71$
// 72$ 73$ 74$ 75$ 76$ 77$ 78$ 79$ 80$ 81$ 82$ 83$ 84$ 85$ 86$ 87$ 88$ 89$ 90$
// 91$ 92$ 93$ 94$ 95$ 96$ 97$ 98$ 99$ 100$ 1* 2* 3* 4* 5* 6* 7* 8* 9* 10* 11*
// 12* 13* 14* 15* 16* 17* 18* 19* 20* 21* 22* 23* 24* 25* 26* 27* 28* 29* 30*
// 31* 32* 33* 34* 35* 36* 37* 38* 39* 40* 41* 42* 43* 44* 45* 46* 47* 48* 49*
// 50* 51* 52* 53* 54* 55* 56* 57* 58* 59* 60* 61* 62* 63* 64* 65* 66* 67* 68*
// 69* 70* 71* 72* 73* 74* 75* 76* 77* 78* 79* 80* 81* 82* 83* 84* Total Time
// taken(in ms) is: 0
// 48# 49# 50# 51# 52# 53# 54# 55# 56# 57# 58# 59# 60# 61# 62# 63# 64# 65# 66#
// 67# 68# 69# 70#
126
// Thread-1 : Completed $ task
// 85* 86* 87* 88* 89* 90* 91* 92* 93* 94* 95* 96* 97* 98* 99* 100* main :
// completed71#
// Thread-0 : Completed * task
// 72# 73# 74# 75# 76# 77# 78# 79# 80# 81# 82# 83# 84# 85# 86# 87# 88# 89# 90#
// 91# 92# 93# 94# 95# 96# 97# 98# 99# 100#
// Thread-2 : Completed # task
127
Thread State Description
A thread is created but not yet started. It has been initialized but
New
start() has not been called.
The thread is ready for execution. The start() method has been
Runnable
called, and it is waiting to be scheduled by the JVM.
The thread is actively executing its tasks. The JVM is actively
Running
scheduling the thread to run.
The thread is alive but not executing. It may be waiting for
Blocked/Sleeping/Waiting
resources, locks, or other conditions to be met.
The thread has finished execution or was stopped. It no longer runs
Terminated
and its lifecycle has ended.
128
t3.setPriority(Thread.NORM_PRIORITY);
t3.start();
long end_time = System.currentTimeMillis();
System.out.printf("%s executed succesfully in %d ms.",
Thread.currentThread().getName(),
(end_time - start_time));
}
}
129
t3.start();
}
}
The synchronized keyword in Java ensures that only one thread can execute a block of code at a
time, providing mutual exclusion and preventing race conditions.
When a thread enters a synchronized block or method, it acquires a lock on the object (for
instance methods) or on the class (for static methods).
Changes made by threads inside a synchronized block are visible to all other threads.
1. Instance method → locks the object
2. Static method → locks the class
public class Counter {
private int count = 0;
130
SynchronizedThread(Counter count) {
this.count = count;
}
@Override
public void run() {
for (int i = 1; i <= 100000; i++) {
count.increment();
}
}
}
131
12.7) Thread Communication
1. sleep(long millis):
sleep should not be called on a specific thread object because it is a static method of the Thread
class. This means that Thread.sleep(milliseconds) pauses the currently executing thread,
regardless of which thread object it is called on. If we write Thread.sleep(timeInMilliseconds);
inside the main method, the main thread will transition from the running state to the timed
waiting state, and then back to the running state after the specified duration and sleep() need to
handle the InterruptedException.
2. yield():
Causes the currently executing threads to pause and allow other threads to execute. It's a way of
suggesting that other threads of the same priority can run.
3. wait():
Causes the current thread to wait until another thread invokes the notify() or notifyAll() method
for this object. It releases the lock held by this thread.
4. notify():
Wakes up a single thread that is waiting on the objects monitor. If any threads are waiting, one is
chosen to be awakened.
5. notifyAll():
Wakes up all the threads that are waiting on the object’s monitor.
132
CHALLENGES
1) Write a program that creates 2 threads. Each thread should print “Hello form Thread X”, where X
is the no.of the thread(1 or 2), 10 times then terminate.
public class HelloThread extends Thread {
private final int threadNo;
If we call t1.run() instead of t1.start(), the run() method will be executed just like a normal method
call, and it will not start a new thread. As a result, the code inside run() will execute in the current
thread, which is typically the main thread. So Thread.currentThread().getName() will return main.
// (1)Hello form Thread-2
// (2)Hello form Thread-2
// (3)Hello form Thread-2
// (4)Hello form Thread-2
// (5)Hello form Thread-2
// (6)Hello form Thread-2
// (1)Hello form Thread-1
// (2)Hello form Thread-1
// (3)Hello form Thread-1
// (4)Hello form Thread-1
// (5)Hello form Thread-1
// (6)Hello form Thread-1
// (7)Hello form Thread-1
// (8)Hello form Thread-1
// (9)Hello form Thread-1
// (10)Hello form Thread-1
// (7)Hello form Thread-2
// (8)Hello form Thread-2
// (9)Hello form Thread-2
// (10)Hello form Thread-2
133
2) Write a program that starts a thread and prints its state after each significant event (creation,
starting and termination). use Thread.sleep() to simulate long-running tasks and
Thread.getState() to print the thread’s state.
public class PrintOdd extends Thread {
@Override
public void run() {
for (int i = 1; i <= 100; i += 2) {
System.out.print(i + " ");
}
}
}
// NEW
// RUNNABLE
// 1 3 5 7 9 11 13 15 17 19 21 23 25 27 29 31 33 35 37 39 41 43 45 47 49 51 53
// 55 57 59 61 63 65 67 69 71 73 75 77 79 81 83 85 87 89 91 93 95 97 99
// TERMINATED
Note:
The run() method (used in threads) is not allowed to say it throws a checked exception like
InterruptedException. This is because the original run() method in the Runnable interface doesn't
throw any checked exceptions.
So, if you use Thread.sleep() inside the run() method — which can throw InterruptedException — you
must catch that exception using a try-catch block.
public void run() throws InterruptedException { // ❌ Not allowed
Thread.sleep(1000);
}
So basically:
run() can’t use throws InterruptedException.
You have to handle the exception inside the method using try-catch.
134
If you want to stop or report the error, you can wrap it in a RuntimeException and throw that.
3) Create 3 threads. Ensure that 2nd thread starts only after the 1st thread ends and the 3rd starts
only after the 2nd thread ends using the join method. Each thread should print its start and end
along with its name.
public class Symbols extends Thread {
private final char symbol;
}
}
135
4) Simulate a traffic signal using threads. Create 3 threads representing three signals: RED, YELLOW,
GREEN. Each signal should be on for a certain time, then switch to the next signal in order. Use
sleep for timing and synchronize to make sure only one signal is active at a time.
public enum TrafficEnum {
RED("Stop"), YELLOW("turn on the engine"), GREEN("You can goooooo");
TrafficThread(String light) {
this.light = light;
}
@Override
public void run() {
System.out.println(TrafficEnum.valueOf(light).getAction());
}
}
}
}
// Stop
// turn on the engine
// You can goooooo
136
ExecutorService is an interface in the Java Concurrency API that provides a powerful framework
for managing and executing tasks asynchronously, without the need to manually handle thread
creation and management.
Instead of creating a new thread for each task, ExecutorService maintains a pool of reusable
threads, which significantly improves performance — particularly when executing many short-
lived tasks.
This approach reduces the overhead associated with thread creation and destruction and allows
for better control over resource usage.
In simple terms, the Java Concurrency API enables multithreading, and ExecutorService is one of
its core tools for handling tasks in a clean, efficient, and scalable way.
In Java, threads managed by an ExecutorService do not die immediately after completing a task.
Instead, they remain alive in a thread pool and wait to execute more tasks — until the executor
is explicitly shut down.
It's similar to a waiter in a restaurant:
We don’t fire the waiter after serving a single customer — they continue serving more
customers as they arrive.
Likewise, in ExecutorService, threads are reused to handle multiple tasks efficiently, rather than
being destroyed and recreated for each one.
This thread reuse significantly reduces performance overhead and provides better resource
management compared to manually creating and starting a new thread for every task.
Demo of newSingleThreadExecutor() is shown below which executes only one thread at a time..
public class Symbols extends Thread {
private final char symbol;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
137
Symbols s1 = new Symbols('$');
Symbols s2 = new Symbols('%');
service.submit(s1);
service.submit(s2);
service.shutdown();
}
}
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
138
// pool-1-thread-2 with the symbol:% is started
// pool-1-thread-1 with the symbol:$ is started
// $ $ $ % % % % % % % % % $ % % % % % % % % % % % % % % % % % % % % % % % $ $ $
// $ $ % $ % $ $ $ % % % $ % % % $ % % $ $ $ $ $ % % % % % % % % % $ % % % % $ %
// % % $ % $ $ % $ $ $ $ % % % % % $ $ $ % $ % $ $ % % % % % % % % % % $ $ $ $ $
// $ $ $ % % % % % % $ % % $ $ $ $ % $ $ % $ % % % % % % $ $ $ % % % $ % $ $ $ $
// % % % $ $
// pool-1-thread-2 with the symbol:% is ended
// $ $ $ $ $ $ $ $
// pool-1-thread-2 with the symbol:= is started
// = = $ $ $ $ = = $ = = = = = = = = $ $ = = $ $ $ $ = = = = $ $ $ $ $ $ $ $ $ $
// $ $ $ $ $ $ = = $ = = = = = = = = = = = = = = = $ $ = = $ =
// pool-1-thread-1 with the symbol:$ is ended
// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
// = = = = = = = = = = = = = = = = = = = = = = =
// pool-1-thread-2 with the symbol:= is ended
And lets see an example where we shutdown the executor within 10 seconds regardless of weather
executor fully executed or not.
public class Symbols extends Thread {
private final char symbol;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
139
if (service.awaitTermination(10, TimeUnit.SECONDS)) {
System.out.println("Completed under 10 seconds");
} else {
System.out.println("Haven't completed under 10 seconds");
service.shutdownNow();
}
}
}
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
140
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService service = Executors.newFixedThreadPool(2);
Name task1 = new Name("hemanth");
Name task2 = new Name("Java");
Name task3 = new Name("KG Coding");
Future<String> name1 = service.submit(task1);
Future<String> name2 = service.submit(task2);
Future<String> name3 = service.submit(task3);
System.out.println("\n" + name1.get());
System.out.println("\n" + name2.get());
System.out.println("\n" + name3.get());
service.shutdown();
}
}
CHALLENGES
1) Write a program that creates a single-threaded executor service. Define and submit a simple
Runnable task that prints numbers from 1-10. After submission, shutdown the executor.
public class Print1210 implements Runnable {
@Override
public void run() {
for (int i = 1; i <= 10; i++) {
System.out.println(i);
}
}
}
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
// 1
// 2
// 3
// 4
// 5
// 6
// 7
// 8
// 9
// 10
OR
import java.util.concurrent.ExecutorService;
141
import java.util.concurrent.Executors;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
142
}
}
}
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
143
// pool-1-thread-3 is ended
// pool-1-thread-1 is ended
// Completed in 10 seconds
3) Write a program that uses an executor service to execute multiple tasks. Each task should calculate
and return the factorial of a number provided to it. Use future objects to receive the results of the
calculations. After all tasks are submitted, retrieve the results from the future, print them, and ensure
the executor service is shutdown correctly.
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
// 3628800
// 362880
// 120
OR
import java.util.ArrayList;
import java.util.List;
144
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
// 1
// 2
// 6
// 24
// 120
// 720
// 5040
// 40320
// 362880
// 3628800
KEY POINTS
1. Main thread is the only user created thread that is created automatically when a program starts.
2. run() is automatically called when thread is started using the start().
3. A thread can not be in both runnable and running states.
4. join() methods causes the calling thread to stop execution until the thread it joins with, stops
running.
5. Synchronized keyword prevents 2 threads from executing a method simultaneously.
6. notify() wakes up single thread, notifyAll() wakes up all the threads.
7. Executor service must be explicitly shutdown to terminate the threads it manages.
8. Thread.yield() is a static method in Java that hints to the thread scheduler that the current
thread is willing to pause its execution temporarily, allowing other threads of the same or higher
priority a chance to execute.
145
13. Functional Programming
13.1) What is Functional programming
1) It’s a way of writing programs where we use functions as small building blocks.
2) Functions can be passed as arguments, returned from other functions, and assigned to variables.
3) Data is immutable.
4) Pure functions, always gives same results for same inputs known as the pure functions.
5) Supports functional interfaces, these are like templates for functions, making it easier to use
them in different parts of the program.
146
System.out.print(fruit + " ");
}
});
import java.util.List;
import java.util.function.BinaryOperator;
147
List<Integer> nums = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9);
int sum = 0;
for (int num : nums)
sum += num;
System.out.println("Sum using normal foreach: " + sum);
int newSum = nums.stream().reduce(0, new BinaryOperator<Integer>() {
@Override
public Integer apply(Integer t, Integer u) {
return t + u;
}
});
System.out.println("Sum using reduce and streams: " + newSum);
int newNewSum = nums.stream().reduce(0, (a, b) -> a + b);
System.out.println("sum using lambda, reduce and streams: " + newNewSum);
int max = nums.stream().reduce(Integer.MIN_VALUE, (a, b) -> a > b ? a : b);
System.out.println("Max of the list is " + max);
}
}
CHALLENGES
1) Write a lambda that takes 2 integers and returns their multiplication. Then, apply this lambda to a
pair of numbers.
import java.util.function.BinaryOperator;
// Mul: 40
OR
public interface FunctionalInterface {
int hemanth(int a, int b);
}
// Mul: 30
2) Convert an array of strings into a stream. Then, use the stream to print each string to the console.
import java.util.stream.Stream;
148
public class Challenge2 {
public static void main(String[] args) {
String[] strs = { "one", "two", "three", "four" };
Stream<String> strsStream = Stream.of(strs);
strsStream.forEach(str -> System.out.print(str + " "));
}
}
3) Given a list of strings, use stream operations to filter-out strings that have length of 10 or more and
then concatenate the remaining strings.
import java.util.List;
// 1234 123456
OR
import java.util.List;
4) Given a list of integers, use stream operations to filter odd numbers and print them.
import java.util.List;
import java.util.stream.Stream;
// Odds are: 1 3 5 7 9
149
13.5) Functional Interfaces
It has only 1 abstract method, it can have multiple default or static methods.
They are intended to use with lamda expressions.
@FunctionalInterface annotation is not mandatory but helps the compiler to identify.
Predicate, Consumer, BinaryOperator, Runnable, Callable, Comparator are the user-defined
intefaces can be functional if they have one abstract method.
// Normal displyaing:
// 1
// 3
// 5
// 7
// 9
// Display using Method references:
// 1
// 3
// 5
// 7
// 9
// Addition: 45
150
13.7) Functional vs. Structural Programming
Aspect Imperative (Structural) Declarative (Functional)
You tell the computer how to do You tell the computer what to do, and
1. Computation
things, step by step. it figures out the steps.
Easy to follow steps, but can get long Shorter and cleaner, easier to read
2. Readability
and complex as code grows. once you understand it.
You have full control over the code Harder to customize due to fixed
3. Customization
and structure. structure or hidden logic.
Harder to optimize because of too Easier to optimize and manage with
4. Optimization
much control and complexity. simpler structure.
Code structure is detailed and Code structure is simple and focused
5. Structure
specific (what to do, and how). (just what to do).
import java.util.List;
import java.util.Optional;
151
else
System.out.println("Not found!");
}
}
CHALLENGES
1) create your own functional Interface with a single abstract method that accepts an integer and
returns a boolean. Implement using a lambda that checks if the number is prime.
public interface PrimeOrNot {
boolean isPrime(int num);
}
2) Write 2 versions of a program that calculates the factorial of a number: one using structural
programming and other using functional programming.
import java.util.Scanner;
import java.util.stream.IntStream;
// Enter a number: 5
// 120
// 120
OR
public class Challengee2 {
public static void main(String[] args) {
Challengee2 obj = new Challengee2();
System.out.println(obj.factorial(5));
}
152
int factorial(int num) {
if (num == 0 || num == 1)
return num;
else
return num * factorial(num - 1);
}
}
AND
public interface FactInterface {
int fact(int num);
}
3) Write a function that accepts a string and returns an Optional<String>. If string is empty or null,
return an empty optional, otherwise, return an optional containing the uppercase version of a string.
import java.util.Optional;
// HI
// Optional.empty
// Optional.empty
153
13.9) Intermediate vs. Terminal Operations
Intermediate Operations
Intermediate operations transform a stream into another stream.
They do not execute immediately — they’re lazy and only run when a terminal operation (like
collect(), forEach(), etc.) is called.
Feature Description
Lazy Execution Only processed when a terminal operation is invoked.
Chainable You can connect multiple intermediate operations (e.g., .filter().map()).
Transform Stream They take a stream and return a new one (not a final result).
Stateless Operations Do not rely on previously seen elements (e.g., map, filter).
Stateful Operations May need to remember or compare elements (e.g., sorted, distinct).
names.stream()
.filter(name -> name.startsWith("J")) // intermediate (stateless)
.map(String::toUpperCase) // intermediate (stateless)
.sorted() // intermediate (stateful)
.forEach(System.out::println); // terminal
Terminal operations
Initiates the stream processing and closes the stream and after this stream cannot be used.
Produces a result(like a sum or list).
It is not chainable.
collect, forEach, reduce, sum, max, min, count are some examples of terminal.
154
System.out.println(nums.stream().max(Integer::compareTo));
nums.stream().min(Integer::compareTo).ifPresent(System.out::println);
List<String> wordings = Arrays.asList("Hi", "My", "Name", "Is", "Hemanth");
System.out.println(wordings.stream().collect(Collectors.toList()));
}
}
// Optional[9]
// 1
// [Hi, My, Name, Is, Hemanth]
CHALLENGES
1. Given an array of integers, create a stream, use the distinct operation to remove duplicates, and
collect the result into a new list.
import java.util.List;
import java.util.stream.Collectors;
// [1, 2, 3, 4, 5, 6, 7, 8, 9]
155
2. Create a list of employees with name and salary fields. Write a comparator that sorts the
employees by salary. Then use this comparator to sort your list using the sort stream operation.
public class Employee {
private String empName;
private int salary;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
OR
import java.util.Comparator;
import java.util.List;
156
}
}).forEach(System.out::println);
}
}
// Hemanth- $500
// Kurup- $700
// Alexander- $1000
OR
list.stream().sorted((e1, e2) -> e1.getSalary() - e2.getSalary())
.forEach(System.out::println);
OR
list.stream().sorted((e1, e2) -> Integer.compare(e1.getSalary(), e2.getSalary()))
.forEach(System.out::println);
OR
list.stream().sorted(Comparator.comparingInt(Employee::getSalary))
.forEach(System.out::println);
3. Create a list of strings representing numbers (“1”,”2”,…). convert each string to an integer, then
again calculating squares of each number using the map operation and sum up the resulting
integers.
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
OR
List<String> numWords = IntStream.range(1, 11)
.mapToObj(Integer::toString)
.collect(Collectors.toList());
numWords.stream()
.map(Integer::parseInt)
.map(num -> num * num)
.reduce(Integer::sum)
.ifPresent(System.out::println);
157
KEYPOINTS
Functions can be assigned to variables, passed as arguments, or returned from other functions.
Lambda expressions in Java can be used only with functional interfaces — these are interfaces
that have exactly one abstract method.If an interface has more than one abstract method, it
cannot be implemented using a lambda expression.
A java stream represents a sequence of elements and supports various methods which can be
pipelined to produce the desired result.
Filter method is an intermediate operation not the terminal operation and the return type is the
type of the stream.
Functional interface in java is an interface with exactly one abstract method.
Optional class in java is used to avoid NullPointerException.
method references in Java are not limited to static methods. They can refer to several types of
methods — including static, instance, and even constructors.
Intermediate operations doesnot execute immediately as they are lazy, they’ll be executed only
if they are followed by a terminal operation.
The max and min operations on stream returns an Optional describing the maximum or
minimum element.
The sorted operation in stream is not terminal operation but a intermediate operation and it
sorts elements of the stream in their natural order.
158