JAVA
Java was conceived by James Gosling, Patrick Naughton, Chris Warth, Ed Frank, and Mike Sheridan
at Sun Microsystems, Inc. in 1991.
Java is a high-level, object-oriented programming language designed to be platform-independent
—code written in Java can run on any device with a Java Virtual Machine (JVM).
It is widely used for building web, mobile, and desktop applications, and is recognized for its
simplicity, reliability, and the principle of "write once, run anywhere"
Java Applets
If the user clicks a link that contains an applet, the applet will be automatically downloaded and
run in the browser. Applets are intended to be small programs.
They are typically used to display data provided by the server, handle user input, or provide
simple functions, such as a loan calculator, that execute locally, rather than on the server.
Principle
Write Once, Run Anywhere (WORA): Java programs, once written and compiled to bytecode, can
run on any system equipped with a Java Virtual Machine (JVM), enabling platform independence.
Properties of Java
Object-Oriented: Java organizes code using objects and classes, emphasizing principles like
inheritance, encapsulation, abstraction, and polymorphism.
Simple and Familiar: Java’s syntax is straightforward and easy to learn, especially for developers
familiar with C or C++.
Robust and Secure: Automatic memory management (garbage collection), strong type checking,
and restrictions on unsafe operations make Java reliable and secure.
Architecture-Neutral and Portable: Bytecode can execute on any platform without recompilation if
a JVM is available.
High Performance: Just-In-Time (JIT) compilation helps deliver efficient execution close to native
performance.
Multithreaded and Dynamic: Java supports concurrent execution (multithreading) and dynamic
loading of classes at runtime.
Common Uses
Desktop Applications
Mobile Apps (especially Android)
Web Applications
Enterprise Systems
Games and Internet of Things (IoT) devices
Java Virtual Machine (JVM)
The Java Virtual Machine (JVM) is a software engine that lets computers run Java programs by converting
Java's platform-independent bytecode into machine code for any operating system or hardware.
The steps of the Java Virtual Machine (JVM) working are:
Loading: JVM loads the compiled Java bytecode (.class files) into memory using a class loader.
Verification: It checks the bytecode to ensure it is valid, secure, and follows Java's rules.
Preparation: JVM allocates memory for class variables and sets default values.
Initialization: Static variables get their assigned values, and static blocks run.
Execution: JVM finds the main() method and starts executing the program, either interpreting bytecode line-
by-line or using a Just-In-Time (JIT) compiler for faster native code.
Memory Management: JVM manages runtime memory and performs garbage collection to free unused
objects.
The working of the Java Virtual Machine (JVM)
Loading: JVM loads the compiled Java bytecode (.class files) from the disk into memory. The class loader
reads the bytecode and stores related class information (like class name, methods, variables) into the method
area in memory.
Linking: This step verifies the correctness of the bytecode, prepares memory for static variables, and resolves
symbolic references to direct references inside the JVM. Verification ensures the code is valid and safe to run.
Initialization: Static variables are assigned their defined values, and static blocks are executed in order. This
completes the setup of the loaded classes.
Execution: The JVM’s execution engine runs the bytecode instructions. It can interpret the bytecode one
instruction at a time or use Just-In-Time (JIT) compilation to convert frequent bytecode into native machine
code for better performance.
Memory Management: JVM manages runtime memory divided into areas like method area, heap, stack, and
program counter registers. It also performs garbage collection to automatically remove unused objects and
free memory.
How JVM Works
Class Loader: Loads the compiled Java bytecode (.class files) into the JVM memory.
Bytecode Verifier: Checks the bytecode for security and correctness.
Runtime Data Areas: JVM creates areas for storing objects, variables, method calls, etc.
Method Area: Stores class-level data like methods and metadata.
Heap: Stores objects created at runtime.
Stack: Stores method calls and local variables.
PC Register: Keeps track of the current instruction in each thread.
Execution Engine: Executes the bytecode either by interpreting or using Just-In-Time (JIT) compilation to native
machine code.
Garbage Collector: Automatically frees memory by removing objects no longer in use.
The Hello class is compiled into bytecode (Hello.class).
JVM's Class Loader loads Hello.class.
Bytecode Verifier ensures it is valid.
Runtime Data Areas prepare memory for variables and
method calls.
Execution Engine runs the main method.
Output "Hello, JVM!" is printed.
Garbage Collector manages memory automatically.
Importance of JVM
Platform Independence: JVM allows the same Java program to run on any device or operating system that has
a JVM implementation. This "write once, run anywhere" capability is central to Java's success.
Security: JVM verifies bytecode before execution and runs programs in a secure environment (sandbox),
protecting the system from malicious code.
Performance: JVM uses Just-In-Time (JIT) compilation to convert bytecode into native machine code during
execution, optimizing performance compared to pure interpretation.
Memory Management: JVM automatically manages memory allocation and garbage collection, reducing the
burden on developers and preventing memory leaks or crashes.
Multithreading Support: JVM natively supports multithreading, enabling concurrent execution of code for
better resource use and faster applications.
Extensibility and Dynamic Loading: JVM can load new code dynamically during runtime, which allows Java
applications to be updated or extended without restarting.
Core Components Of JVM
Class Loader Subsystem
It is mainly responsible for three activities.
Loading
Linking
Initialization
Loading: The Class loader reads the “.class” file, generate the corresponding binary data and save it in
the method area.
For each “.class” file, JVM stores the following information in the method area.
The fully qualified name of the loaded class and its immediate parent class.
Whether the “.class” file is related to Class or Interface or Enum.
Modifier, Variables and Method information etc.
Linking: Performs verification, preparation, and (optionally) resolution.
Verification: It ensures the correctness of the .class file i.e. it checks whether this file is properly
formatted and generated by a valid compiler or not.
If verification fails, we get run-time exception java.lang.VerifyError.
This activity is done by the component ByteCodeVerifier. Once this activity is completed then the class
file is ready for compilation.
Preparation: JVM allocates memory for class static variables and initializing the memory to default
values.
Resolution: It is the process of replacing symbolic references from the type with direct references.
It is done by searching into the method area to locate the referenced entity.
Initialization: In this phase, all static variables are assigned with their values defined in the code and
static block(if any).
This is executed from top to bottom in a class and from parent to child in the class hierarchy.
In general, there are three class loaders:
Bootstrap class loader: Every JVM implementation must have a bootstrap class loader, capable of
loading trusted classes.
It loads core java API classes present in the “JAVA_HOME/lib” directory.
This path is popularly known as the bootstrap path. It is implemented in native languages like C, C++.
Extension class loader: It is a child of the bootstrap class loader.
It loads the classes present in the extensions directories “JAVA_HOME/jre/lib/ext”(Extension path) or
any other directory specified by the java.ext.dirs system property.
It is implemented in java by the sun.misc.Launcher$ExtClassLoader class.
System/Application class loader: It is a child of the extension class loader. It is responsible to load
classes from the application classpath. It internally uses Environment Variable which mapped to
java.class.path.
It is also implemented in Java by the sun.misc.Launcher$AppClassLoader class.
Class Loaders
There are three primary types of class loaders:
Bootstrap Class Loader: Loads core Java API classes from the JAVA_HOME/lib directory. It is
implemented in native code and is not a Java object.
Extension Class Loader: Loads classes from the JAVA_HOME/jre/lib/ext directory or any directory
specified by the java.ext.dirs system property. It is implemented in Java.
System/Application Class Loader: Loads classes from the application classpath, which is specified by
the java.class.path environment variable. It is also implemented in Java.
JVM Memory Areas
Method area: In the method area, all class level information like class name, immediate parent class
name, methods and variables information etc. are stored, including static variables.
There is only one method area per JVM, and it is a shared resource.
Heap area: Information of all objects is stored in the heap area.
There is also one Heap Area per JVM. It is also a shared resource.
Stack area: For every thread, JVM creates one run-time stack which is stored here.
Every block of this stack is called activation record/stack frame which stores methods calls.
All local variables of that method are stored in their corresponding frame.
After a thread terminates, its run-time stack will be destroyed by JVM.
It is not a shared resource.
PC Registers: Store address of current execution instruction of a thread.
Obviously, each thread has separate PC Registers.
Native method stacks: For every thread, a separate native stack is created.
It stores native method information.
Execution Engine
Execution engine executes the “.class” (bytecode). It reads the byte-code line by line, uses data and
information present in various memory area and executes instructions.
It can be classified into three parts:
Interpreter: It interprets the bytecode line by line and then executes.
The disadvantage here is that when one method is called multiple times, every time interpretation is
required.
Just-In-Time Compiler(JIT): It is used to increase the efficiency of an interpreter.
It compiles the entire bytecode and changes it to native code so whenever the interpreter sees repeated
method calls, JIT provides direct native code for that part so re-interpretation is not required, thus
efficiency is improved.
Garbage Collector: It destroys un-referenced objects.
Java Native Interface (JNI)
It is an interface that interacts with the Native Method Libraries and provides the native libraries(C, C++)
required for the execution.
It enables JVM to call C/C++ libraries and to be called by C/C++ libraries which may be specific to
hardware.
Native Method Libraries
These are collections of native libraries required for executing native methods.
They include libraries written in languages like C and C++.
Java Development Kit (JDK)
The Java Development Kit (JDK) is software for Java developers.
It includes the Java interpreter, Java classes, and Java development tools: compiler,
debugger, disassembler, appletviewer, stub file generator, and documentation generator.
The JDK includes:
Java Runtime Environment (JRE): Runs Java programs.
Java Compiler (javac): Converts Java source code into bytecode.
Java Interpreter (java): Loads and runs Java applications.
Additional tools: Debugger (jdb), documentation generator (javadoc), archiver (jar), and more for
packaging, monitoring, and managing Java applications.
The JDK enables us to write applications that are developed once and run anywhere on any Java virtual
machine.
Java applications that are developed with the JDK on one system can be used on another system
without changing or recompiling the code.
The Java class files are portable to any standard Java virtual machine.
Java packages
A Java package is a way of grouping related classes and interfaces in Java.
Java packages are similar to class libraries that are available in other languages.
Java interpreter
The Java interpreter is the part of the Java virtual machine that interprets Java class files for a particular
hardware platform. The Java interpreter decodes each bytecode and performs the corresponding
operation.
Java vs C++
Platform Dependency: Java is platform-independent due to JVM; C++ is platform-dependent and
requires recompilation for different OS.
Compilation: Java compiles source code to bytecode, which JVM interprets or JIT compiles. C++
compiles directly to machine code.
Memory Management: Java uses automatic garbage collection; C++ uses manual memory management
with pointers.
Pointer Support: Java restricts direct pointer manipulation; C++ allows full pointer arithmetic and
usage.
Multiple Inheritance: Java disallows multiple inheritance of classes (uses interfaces); C++ supports
multiple inheritance.
Programming Paradigm: Java is purely object-oriented; C++ supports both procedural and object-
oriented paradigms.
Syntax Complexity: Java syntax is simpler and cleaner; C++ syntax is more complex with features close
to hardware.
Exception Handling: Both support exception handling, but Java enforces checked exceptions, C++ does
not.
Threading: Java has built-in multithreading; C++ requires external libraries.
Standard Libraries: Java provides a vast built-in standard library (collections, networking, GUI); C++ has
STL but less extensive standard API.
Runtime: Java runs on JVM with runtime checks improving security; C++ is compiled into native code
and runs without virtual machine.
Performance: C++ generally offers higher performance and low-level system access; Java’s
performance depends on JVM and optimization.
Use Cases: Java is favored for web, mobile (Android), and enterprise apps; C++ is preferred for systems
programming, game development, and performance-critical applications.
Security: Java offers a more secure environment due to bytecode verification and sandboxing; C++
programs have fewer built-in safety checks.
Garbage Collection: Java automatically cleans up unused objects; C++ requires explicit deallocation.
Platform-Specific Features: C++ can access system hardware and platform-specific features more
easily.
Portability: Java’s compiled bytecode and JVM ensure code portability; C++ lacks this portability out of
the box.
Development Speed: Java allows faster development due to simpler memory management and
extensive libraries.
Native Methods: Java can call native C/C++ methods via JNI for system-level operations.
Community and Ecosystem: Both have strong communities; Java dominates enterprise ecosystems,
whereas C++ excels in systems and embedded domains.
Java is Simpler and easier to Where as C++ More complex,
Syntax Simplicity
learn closer to hardware details
C++ for System/software
Java for Web, mobile apps,
Use Cases development, game engines,
enterprise applications
performance-critical apps
Data Types in Java
Non-Primitive Data Types
Includes String, Arrays, Classes, and Interfaces.
Objects and more complex data structures belong here.
These store references rather than actual values.
Literals
In Java, literals are fixed values written directly in the code that represent constant data.
Types of Literals in Java
Integer Literals
Represent whole numbers without decimals.
Can be decimal (base 10), octal (base 8, starting with 0), hexadecimal (base 16, starting with 0x), or binary
(base 2, starting with 0b).
Example: int dec = 100; int hex = 0x1A; int bin = 0b1010;
Floating-Point Literals
Represent decimal numbers.
Can be of type float (suffix f or F) or double.
Example: float f = 3.14f; double d = 3.14159;
Character Literals
Single Unicode characters enclosed in single quotes.
Supports escape sequences like \n, \t, \\.
Example: char c = 'A'; char newLine = '\n';
String Literals
Sequences of characters enclosed in double quotes.
Example: String greeting = "Hello, Java!";
Boolean Literals
Represent logical values: true or false.
Example: boolean isJavaFun = true;
Null Literal
Represents a null reference (no value).
Example: String str = null;
Variable
In Java, variables are used to store data values during the execution of a program.
A variable is a named memory location used to hold a value.
Variables have a type which defines what kind of data they can store (e.g., int, double, String).
Rules for Variable Naming
Variable names can contain letters (A-Z, a-z), digits (0-9), underscore (_), and dollar sign ($).
Must begin with a letter, underscore, or dollar sign — cannot start with a digit.
Cannot contain spaces or Java reserved keywords.
Variable names are case-sensitive (myVar and myvar are different).
No length limit but should be meaningful and concise.
Types of Variables in Java
Local Variables
Declared inside a method, constructor, or block.
Exist only during the execution of that method/block.
No default value; must be initialized before use.
Instance Variables (Non-Static Fields)
Declared inside a class but outside any method.
Each object/instance of the class gets its own copy.
Default values are provided (0, false, null).
Static Variables (Class Variables)
Declared with the static keyword inside a class but
outside methods.
Shared among all instances of the class.
Default values are provided.
Local variables are created when the method is called and destroyed when it finishes.
Instance variables are created when an object is created and destroyed when the object is garbage
collected.
Static variables are created once when the class is loaded by JVM and are destroyed when the class is
unloaded.
Identifiers
An identifier in Java is the name used to identify variables, classes, methods, packages, interfaces, and other
programming elements within the code.
It acts as a unique label or reference to these elements so they can be used and manipulated throughout the
program.
Arrays in Java
An array in Java is a collection or container that holds multiple values of the same data type under
a single variable name. Instead of declaring multiple variables, you can store a fixed-size sequence
of elements.
Arrays store elements of the same type.
The size of an array is fixed once it’s created.
Array elements are indexed starting from 0.
Arrays can be of primitive types (int, float, etc.) or objects (String, custom classes).
Access elements using the index number in square brackets [index].
Java Operators
Operators are used to perform operations on variables and values.
Java Assignment Operators
Assignment operators are used to assign values to variables.
Java Comparison Operators
Comparison operators are used to compare two values (or variables). This is important in programming,
because it helps us to find answers and make decisions.
The return value of a comparison is either true or false.
Java Arrays
Arrays are used to store multiple values in a single variable, instead of declaring separate variables for each
value.
To declare an array, define the variable type with square brackets [ ] :
Access the Elements of an Array
You can access an array element by referring
to the index number.
Java Arrays Loop
Loop Through an Array
You can loop through the array elements with the for loop, and use the length property to specify how many times
the loop should run.
Java Multi-Dimensional Arrays
Multidimensional Arrays
A multidimensional array is an array that contains other arrays.
You can use it to store data in a table with rows and columns.
Access Elements
To access an element of a two-dimensional array, you need two indexes: the first for the row, and the second for
the column.
Control statements in Java
Java Syntax
Example explained
Every line of code that runs in Java must be inside a
class. And the class name should always start with an
uppercase first letter. In our example, we named the class
Main.
Note: Java is case-sensitive: "MyClass" and "myclass" has
different meaning.
The name of the java file must match the class name
Java Methods
A method is a block of code which only runs when it is called.
We can pass data, known as parameters, into a method.
Methods are used to perform certain actions, and they are also known as functions.
Create a Method
A method must be declared within a class. It is defined with
the name of the method, followed by parentheses ().
Java provides some pre-defined methods, such as
System.out.println(), but we can also create your own
methods to perform certain actions:
Call a Method
To call a method in Java, we write the method's
name followed by two parentheses () and a
semicolon;
Java Method Parameters
Parameters and Arguments
Information can be passed to methods as a
parameter.
Parameters act as variables inside the method.
Parameters are specified after the method name,
inside the parentheses.
We can add as many parameters as you want, just
separate them with a comma.
When a parameter is passed to the method, it
is called an argument.
Return Values
we used the void keyword in all examples, which indicates that the method should not return a value.
If We want the method to return a value, We can use a primitive data type (such as int, char, etc.) instead of
void, and use the return keyword inside the method:
Method overloading
Method overloading in Java is when a class has multiple methods with the same name but different parameter
lists (number, type, or order of parameters).
It is a type of compile-time polymorphism: the method to call is decided at compile time based on the
arguments provided.
Methods must have the same name but a different parameter list (not just return type).
We can overload by changing:
Number of parameters
Data types of parameters
Order of parameters
Java Recursion
Recursion is the technique of making a function call itself.
This technique provides a way to break complicated
problems down into simpler problems which are easier to
solve.
Halting Condition
Just as loops can run into the problem of infinite looping,
recursive methods can run into the problem of infinite
recursion.
Infinite recursion is when the method never stops calling
itself.
Every recursive method should have a halting condition,
which is the condition where the method stops calling itself.
Java - What is OOP?
OOP stands for Object-Oriented Programming.
Procedural programming is about writing procedures or methods that perform operations on the data, while
object-oriented programming is about creating objects that contain both data and methods.
Object-oriented programming has several advantages over procedural programming:
OOP is faster and easier to execute
OOP provides a clear structure for the programs
OOP helps to keep the Java code DRY "Don't Repeat Yourself", and makes the code easier to maintain, modify and
debug
OOP makes it possible to create full reusable applications with less code and shorter development time
What are Classes and Objects?
In Java, a class is a blueprint or template that defines the properties (fields/attributes) and behaviors (methods)
common to all objects of that type.
An object is an instance of a class: it represents a real entity with actual values for those properties and the ability
to perform actions using those methods.
Classes and objects are the two main aspects of object-oriented programming.
Example:
What is a Class?
A class is a user-defined data type.
It serves as a template for creating objects.
Defines fields (state/data), methods (behavior/actions),
constructors, etc.
No memory is allocated just by defining a class; memory
is allocated when an object is created from it.
Example:
What is an Object?
An object is an instance of a class.
It has its own state (unique values for fields) and can
use the class's methods.
Created using the new keyword followed by the class
name.
Create a Class
To create a class, use the keyword class:
Create an Object
In Java, an object is created from a class. We have
already created the class named Main, so now we can
use this to create objects.
To create an object of Main, specify the class name,
followed by the object name, and use the keyword
new:
Class Syntax
Class declaration: Use the keyword class followed by
the class name (by convention starting with uppercase).
Access modifier: Usually public or default (no modifier).
Fields: Variables to hold data.
Constructor: Special method to initialize new objects,
named same as the class.
Methods: Define behaviors or actions the class can
perform.
Object Creation Syntax
Class Attributes in Java
Class attributes, also known as fields or instance variables, are variables defined inside a class but outside any
method.
These attributes represent the state or properties of an object created from the class. They hold data unique
to each object instance.
Attributes define the characteristics of a class or object.
They can be of primitive types (int, boolean) or reference types (String, custom classes).
Each object has its own copy of instance attributes.
Accessed using the dot (.) operator on an object.
Can have default values if not initialized (e.g., int defaults to 0, objects to null).
Can be modified or made read-only using final.
Class attributes are fundamental to defining the state
of objects in Java programming.
They store the data associated with each object
created from the class.
Accessing Attributes
To access an attribute (field) of a class, you first
create an object of the class.
Then use the dot operator (.) to get the attribute
value from the object.
Modifying Attributes
Attributes can be modified by assigning new values
using the dot operator on the object.
Types of Attributes in Java Classes
Class attributes (also known as fields or data members) represent the state or properties of a class or object.
They can be classified broadly into two types:
Instance Attributes
Belong to individual objects (instances) of the class.
Each object has its own copy of instance attributes.
Values of instance attributes are unique to each object.
Accessed using the object reference variable.
Declared without the static keyword.
Static Attributes (Class Attributes)
Belong to the class itself, not to any particular instance.
Shared by all objects of the class.
Changes made to static attributes affect all instances.
Declared using the static keyword.
Can be accessed directly by the class name or via
objects.
Access Modifiers for Attributes
Attributes can also have access modifiers: private, public, protected, or default (package-private).
Controls the visibility and encapsulation of attributes.
Syntax of a Class Method
access_modifier: (optional) Specifies the visibility (e.g.,
public, private).
return_type: Data type of the value the method returns;
use void if no value is returned.
methodName: Name of the method following Java
naming conventions.
parameter_list: Zero or more input parameters, each with
a type and name.
method body: Contains statements that define the task to
perform.
Types of Classes
Concrete Class
A regular class that can be instantiated to create objects.
Defines properties (fields) and behaviors (methods).
Abstract Class
Cannot be instantiated directly.
Can contain abstract methods (without body) that subclasses must implement.
Provides partial implementation.
Final Class
Declared as final to prevent inheritance (cannot be
subclassed).
Useful for immutable classes.
Nested and Inner Classes
A class defined within another class.
Types: Static Nested Classes and Non-static Inner Classes.
Singleton Class
A design pattern where a class has only one instance.
Provides a global point of access.
String Handling in Java
Java provides a String class to work with sequences of characters.
Strings in Java are immutable, meaning once created, their values cannot be changed.
Example Code
Java Constructors
The constructor is called when an object of a class is created.
A constructor is a special method used to initialize objects when they are created.
It is called automatically at the time of object creation.
The constructor name must be the same as the class name.
It does not have a return type, not even void.
It can be used to assign initial values to object attributes.
Types of Constructors
Default Constructor
A constructor with no parameters.
Provided automatically by the compiler if no constructors are defined.
Initializes object fields with default values (0 for numbers, null for objects, false for
booleans).
Can also be explicitly defined by the programmer.
Parameterized Constructor
A constructor that takes arguments to initialize an object with
specific values.
Allows creating objects with different states.
Copy Constructor (Conceptual in Java)
Used to initialize an object using another object of the same class.
Java doesn't provide a built-in copy constructor like C++, but it can be implemented manually.
this Keyword in Java
The this keyword in Java is a reference variable that refers to the current object — the instance of the class where
the keyword is used.
It allows us to refer to the current class’s instance variables, methods, or constructors, especially when names are
shadowed by parameters or local variables.
Garbage Collection in Java
Garbage Collection (GC) in Java is an automatic memory management process that frees memory occupied by
objects no longer in use (unreferenced/unreachable objects).
It prevents memory leaks and improves program efficiency without explicit programmer intervention.
How Garbage Collection Works
Objects are created on the heap memory.
When objects are no longer referenced, they become eligible for garbage collection.
The Java Virtual Machine (JVM) periodically runs the garbage collector to identify and remove these unused
objects, freeing memory.
Programmer cannot control exactly when GC runs but can request it using System.gc() or
Runtime.getRuntime().gc() (requests, not guarantees).
Reachable vs Unreachable Objects: Objects are reachable if accessible through references; unreachable objects
are garbage.
Generational Heap: Heap is divided into:
Young Generation: Where new objects are allocated; frequent minor GC occurs here.
Old Generation: Stores long-lived objects; major/full GC occurs less frequently.
Mark and Sweep Algorithm: Garbage collector marks reachable objects and sweeps away unmarked (garbage).
Finalize Method: Deprecated in modern Java; previously called before object destruction for cleanup.
Types of Garbage Collection in Java
Minor GC: Cleans unreachable objects in the Young Generation.
Major (Full) GC: Cleans unreachable objects in the Old Generation and Young Generation; more time-consuming.
Garbage Collectors in JVM:
Serial GC: Single-threaded, suitable for small apps.
Parallel GC: Multi-threaded, default JVM collector for throughput.
Concurrent Mark Sweep (CMS) GC: Low pause collector for old generation.
Garbage First (G1) GC: Modern collector optimizing pause time and throughput.
How to Make Objects Eligible for GC
Nullify references (obj = null).
Reassign references to new objects (obj = new Object();).
Objects created inside methods become eligible after method ends.
Objects isolated from all reachable references.
Finalize Method
The finalize() method in Java is called by the Garbage Collector (GC) just before an object is destroyed.
It gives the object a chance to perform cleanup activities such as releasing resources (file handles, network
connections) before the object is removed from memory.
It is defined in the Object class and can be overridden by user-defined classes to customize cleanup behavior.
It does not take any parameters and does not return any value.
It can throw a Throwable if needed.
How finalize() Works
Automatically invoked once by the garbage collector on an object when it detects the object is no longer reachable.
Provides an opportunity to free up resources.
I It is unpredictable when or if finalize() will be called, so it should not be relied upon for critical cleanup.
The method has been deprecated since Java 9 and removed in Java 18, favoring better alternatives like try-with-
resources or java.lang.ref.Cleaner.
Reasons to Avoid finalize()
Poor performance impact.
No guarantee of timely execution.
Difficult to debug and maintain.
Better resource management via try-with-resources and explicit cleanup.
Modifiers
The public keyword is an access modifier, meaning that it is used to set the access level for classes,
attributes, methods and constructors.
Java modifiers are keywords that define the visibility, behavior, and characteristics of classes, methods,
variables, and constructors.
We divide modifiers into two groups:
Access Modifiers - controls the access level
Non-Access Modifiers - do not control access level, but provides other functionality
Access Modifiers
These define how other classes can access a class or its members.
Java provides four main types:
Public
Syntax: public
Scope: Accessible from any other class in any package.
Private
Syntax: private
Scope: Accessible only within the declared class.
Protected
Syntax: protected
Scope: Accessible within the same package and
subclasses (even if they are in different packages).
Default (no modifier)
Syntax: no keyword used
Scope: Accessible only within the same package.
Non-Access Modifiers
These modify the way classes/methods/variables behave, regardless of visibility:
Non-access modifiers do not control visibility (like public or private), but instead add other features to classes,
methods, and attributes.
The most commonly used non-access modifiers are final, static, and abstract.
Static
Belongs to the class, not instances
A static method means that it can be accessed without
creating an object of the class, unlike public:.
Syntax: static
Final
Cannot be modified after assignment (for variables), or
inherited/overridden (for classes/methods).
If we don't want the ability to override existing attribute
values, declare attributes as final
Syntax: final
Abstract
Used for classes that can't be instantiated, or methods
without a body.
An abstract method belongs to an abstract class, and it
does not have a body.
The body is provided by the subclass
Syntax: abstract
Inheritance
Inheritance in Java is the process where one
class (the subclass or child class) acquires the
properties (fields) and behaviors (methods) of
another class (the superclass or parent class).
It enables code reusability, logical hierarchy,
and is a core pillar of object-oriented
programming.
To inherit from a class, use the extends keyword.
subclass (child) - the class that inherits from another class
superclass (parent) - the class being inherited from
Types of Inheritance in Java
Single Inheritance
A subclass inherits from one superclass only. Promotes a simple hierarchy
Multilevel Inheritance
A class inherits from a subclass, forming a chain. Example: Grandparent → Parent → Child.
Hierarchical Inheritance
Multiple subclasses inherit from the same superclass.
Multiple Inheritance (Not Supported via Classes)
A class inherits from more than one class.
Java does NOT support multiple inheritance with classes to
avoid ambiguity.
However, it can be achieved using interfaces.
Hybrid Inheritance (Not Supported Directly)
Combination of two or more types of inheritance (single, multiple, multilevel, hierarchical).
As with multiple inheritance, it is only possible with interfaces in Java.
super Keyword
The super keyword in Java is used within a subclass to refer to its immediate parent class (superclass).
It allows access to parent class variables, methods, and constructors, especially when there's an ambiguity due to
member overriding or when we want to ensure the parent class's version is used.
n Java, the super keyword is used to refer to the parent class of a subclass.
The most common use of the super keyword is to eliminate the confusion between superclasses and subclasses
that have methods with the same name.
Uses of the super Keyword
Accessing Parent Class Variables
If the subclass has a variable with the same name as the parent, super.variable can be used to refer to the parent
variable.
Accessing Parent Class Methods
If the subclass overrides a method, super.method() will access the parent's method.
Invoking Parent Class Constructor
Use super() as the first statement in a subclass constructor to call a parent class's constructor.
Method overriding
Method overriding in Java occurs when a subclass provides its own implementation for a method that is already
defined in its superclass.
This feature enables runtime polymorphism, allowing a program to decide at runtime which method
implementation to use, depending on the actual object type.
Rules of Method Overriding
The method in the subclass must have the
same name, return type, and parameter list as
the method in the superclass.
The overriding method cannot have a more
restrictive access modifier (e.g., making a
public method private).
final and static methods cannot be
overridden.
The @Override annotation is recommended
to inform the compiler of the intent to
override; it also helps catch errors.
Abstract Class
Data abstraction is the process of hiding certain details and showing only essential information to the user.
An abstract class in Java is a class that cannot be instantiated directly.
Declared using the abstract keyword.
Used to provide a base class that other classes can extend.
Can contain:
Abstract methods (methods without a body)
Non-abstract methods (methods with implementations)
Fields, constructors
Provides a common protocol for subclasses while allowing them to implement the details.
If a class has at least one abstract method, it must be declared abstract.
The abstract keyword is a non-access modifier, used for
classes and methods:
Abstract class: is a restricted class that cannot be used
to create objects (to access it, it must be inherited from
another class).
Abstract method: can only be used in an abstract class,
and it does not have a body. The body is provided by
the subclass (inherited from).
Polymorphism in Java
Polymorphism in Java is an object-oriented concept where a method or object can take multiple forms and
behaviors, depending on its context, most importantly at runtime.
Polymorphism means "many forms," allowing objects or methods to behave differently based on their actual class
at runtime.
This is especially powerful in Java due to its use of inheritance and interfaces.
The same method name or object reference can lead to different results, depending on the subclass involved.
Facilitates code reusability and cleaner design using inheritance and interfaces.
Types of Polymorphism
Compile-Time Polymorphism (Static): Achieved through method overloading. The decision about which method
to call is made at compile time based on method signatures.
Runtime Polymorphism (Dynamic): Achieved through method overriding. The JVM determines which overridden
method to call at runtime based on the actual object type
Compile-time polymorphism in Java, also known as static polymorphism or early binding, allows method calls to
be resolved by the compiler before the program runs. The most common mechanism for achieving compile-time
polymorphism is method overloading.
Compile-Time Polymorphism Works
The Java compiler decides which overloaded method to call based on the method signature:
number, order, or data type of parameters.
The function binding (matching function call to function body) happens during compilation, not
runtime.
Method overloading allows multiple methods with the same name in a class, differentiated by their
parameter lists.
When invoking obj.add(10, 20), the compiler
matches the method call with the version that
has two integer parameters.
When invoking obj.add(10, 20, 30), the three-
parameter version is chosen.
The selection is made while compiling the
program, so the correct method is bound
before execution.
Fast execution: The correct method is known before runtime; no dynamic lookup needed.
No inheritance involved: Overloading occurs within the class itself.
Type safety and error checking: If an ambiguous or unmatched method call is made, the compiler throws an error.
Runtime polymorphism in Java, also known as dynamic polymorphism or late binding, is where the method to be
executed is determined by the JVM at runtime, based on the object's actual class. The mechanism for achieving
runtime polymorphism is method overriding.
Runtime Polymorphism Works
The parent class defines a method, and the child (subclass) overrides it with its own implementation.
A parent-class reference variable holds a child-class object (upcasting). When the method is called, the
overridden method in the child class is executed, not the one in the parent.
The check for which method to execute happens at runtime, enabling flexible code behavior.
Here, the reference type is Bike, but the actual object
created is Hayabusa.
The overridden run() method of Hayabusa is called at
runtime, not the one from Bike
Characteristics
Method overriding: Child class overrides a method already present in its parent class.
Upcasting: Parent references pointing to child objects allows runtime selection.
Extensibility: New subclasses can add unique behavior without modifying existing code.
No support for data member overriding: Only methods are dynamically bound (if accessed by parent reference,
parent data member is used)
Virtual function
A virtual function is a member function declared in a base class using the virtual keyword, designed to be
overridden in a derived class to achieve runtime polymorphism—meaning the specific function to execute is
chosen dynamically at runtime based on the actual type of the object.
Allows a base class pointer or reference to invoke overridden functions in the derived class, not just its own
implementations.
Enables flexible code architectures like plugin systems and polymorphic interfaces.
The compiler maintains a vtable (virtual table of function pointers per class) and a vptr (pointer to vtable per
instance) to resolve calls at runtime.
The method getSalary() is overridden in both Manager and
Developer classes.
At runtime, based on the actual object type (Manager or
Developer), the correct version of getSalary() is executed—
even though the references are of parent type Employee
Interface in Java
An interface in Java is a reference type that is similar to a class.
An interface is a completely "abstract class" that is used to group related methods with empty bodies
It contains only abstract methods (method signatures with no body), default methods (with body), static
methods, and constant variables.
Used to define a contract or blueprint for classes, specifying methods that implementing classes must
provide.
Supports multiple inheritance by allowing a class to implement multiple interfaces.
Helps achieve abstraction and loose coupling.
All methods declared in an interface are by default public and abstract (except default and static
methods).
Variables in interfaces are implicitly public, static, and final (constants).
Interfaces cannot be instantiated directly.
Classes use the implements keyword to implement interfaces.
Advantages of Using Interfaces
Enables multiple inheritance of type.
Promotes loose coupling by separating specification from
implementation.
Defines consistent APIs that various unrelated classes can
implement.
Supports polymorphism by allowing objects to be accessed
through interface references.
Constructor in Multilevel Inheritance
Multilevel inheritance is a type of inheritance where a class inherits from a subclass, thus forming a chain of
inheritance.
For example, Class C inherits from Class B, and Class B inherits from Class A.
When an object of the most derived class is created, constructors for all ancestor classes are called in order from
the topmost superclass to the most derived subclass.
Constructor Call Order in Multilevel Inheritance:
The constructor of the superclass (topmost parent) is called first.
Then the constructor of the middle class is called.
Finally, the constructor of the current class is called.
This ensures that the initialization starts from the top of the hierarchy down to the bottom.
Explanation:
When new C() is called, it triggers the constructor call chain.
Constructor of class A (superclass of B) is called first.
Then constructor of B (superclass of C) is executed.
Finally, constructor of C runs, completing the initialization.
If we want to explicitly call a specific superclass constructor with parameters, use super(parameters) inside the
subclass constructor.
Each class in the hierarchy can have its own constructors and initialization logic.
If a constructor is not invoked explicitly, Java automatically inserts a no-argument super() call.
Explanation:
When new SportsCar() is called, Java first calls the constructor of
the top superclass Vehicle.
Then the Car constructor is called.
Finally, the SportsCar constructor runs.
This ensures constructors are executed from the top to the
bottom of the inheritance chain, properly initializing objects.
Using final with Inheritance
The final keyword in Java is used to restrict the modification of variables, methods, and classes.
When combined with inheritance, it has specific implications:
final Variables
Once assigned, their value cannot be changed.
Useful for constants.
Subclasses inherit final variables but cannot modify
them.
final Methods
Cannot be overridden by subclasses.
Ensures the method’s behavior remains unchanged in
inheritance.
Promotes security and consistency.
final Classes
Cannot be subclassed (inherited).
The class is closed for inheritance.
Useful for immutable classes like String.
Variables in Java Interfaces
Implicit Modifiers: public, static, and final
Any variable declared in an interface is implicitly public, static, and final, even if these keywords are not explicitly
stated.
public: Accessible everywhere (no access restriction).
static: Belongs to the interface itself, not to instances (interfaces cannot be instantiated).
final: The value is constant and cannot be changed once assigned.
Why These Modifiers Are Applied?
Interfaces define contracts and constants—not instance data.
No instance variables are allowed because interfaces do not have state.
Defining variables as static final ensures constants with fixed values shared across all implementing classes.
Enables defining global constants that can be used consistently.
Declaration and Initialization Rules
Variables must be initialized when declared, or there will be a compile-time error.
Since they are final, once assigned, their value cannot be changed.
Allowed types include primitive types (int, float, boolean, etc.), String, or even object references.
However, being final, object references cannot be made to point elsewhere, but the object's internal state can still be
mutable unless immutable objects are used.
Access and Usage
Since variables are public static final implicitly:
They can be accessed as InterfaceName.VARIABLE_NAME without any object reference.
Implementing classes can access these constants directly without needing to qualify with the
interface name (but it's clearer to use the interface name).
Limitations
No instance-level state can be maintained in interfaces.
Cannot declare instance or non-final variables.
Attempts to declare a variable without initialization or without making it final result in compilation errors.
When to Use Interface Variables?
To provide shared constant values across multiple classes implementing the interface.
To avoid "magic numbers" or strings scattered in code.
To maintain consistency in application-wide constants related to the interface’s functionality.
Explanation:
MAX_USERS and APP_NAME are variables declared in the interface Constants.
These variables are automatically public static final constants.
The implementing class Application can directly use these constants without needing to define or initialize them.
When running, it prints the constant values.
Extending Interfaces
An interface in Java can extend one or more interfaces.
This allows one interface to inherit abstract methods from the parent interface(s).
Extending interfaces supports multiple inheritance of type, which Java classes cannot do due to ambiguity issues.
The extending interface inherits all the abstract methods of its parent interface(s).
Any class implementing the extending interface must implement all inherited methods.
Interfaces provide multiple inheritance of type by allowing one
interface to extend multiple interfaces.
A class can implement one or more interfaces and must
implement all their methods.
This mechanism addresses the limitations of single inheritance
with classes and avoids ambiguity.
Since Java 8, interfaces can have default and static methods,
which complicate multiple inheritance of implementation, but the
compiler enforces rules to resolve conflicts.
Nested Interface
A nested interface is an interface declared inside another class or interface.
It can be used to group related interfaces together.
Nested interfaces inside a class can have any access modifier (public, protected, private, or default).
Nested interfaces inside another interface are implicitly public and static.
Nested interfaces allow better organization and modular code design.
Dynamic Method Dispatch
In simple terms, dynamic method dispatch in Java means that when you call a method on an object, Java figures
out which version of the method to run based on the actual object type at runtime — not just the type of the
reference variable.
Dynamic Method Dispatch is a runtime mechanism by which a call to an overridden method is resolved at
runtime rather than compile time.
It is the core concept behind runtime polymorphism in Java.
Java uses the principle: a superclass reference variable can refer to a subclass object.
When a superclass reference is used to invoke an overridden method, Java determines at runtime which
subclass's version of the method to execute depending on the actual object type being referred to.
Working :
Suppose you have a superclass reference pointing to a subclass object.
When calling an overridden method via this reference, Java calls the method version corresponding to the actual
object’s class, not the reference type.
This method resolution happens during program execution (runtime).
Enables flexible and extensible designs.
The type of the object (not the reference variable)
decides which overridden method is executed.
Enables runtime polymorphism and flexible code.
Allows common interfaces with different
implementations.
Requires method overriding where superclass and
subclass have methods with the same signature.
Why Use Dynamic Method Dispatch?
Promotes loose coupling by programming to
interfaces or superclasses.
Allows substitutability of subclass objects without
changing reference types.
Facilitates maintenance and scalability in large
applications.
Enums
An enum is a special "class" that represents a group of constants (unchangeable variables, like final variables).
An enum (short for "enumeration") in Java is a special data type that represents a fixed set of constants.
It is used to define a collection of named constant values that are unchangeable and type-safe.
Enum constants are public, static, and final by default.
Provides a way to represent a group of related values clearly and safely.
Enum Constructor
An enum constructor in Java is similar to a class constructor but is used to initialize enum constants.
It is called automatically once for each enum constant when the enum class is loaded.
Enum constructors are either private or package-private (default). You cannot make them public or protected.
We cannot explicitly call enum constructors; they are invoked internally when the constants are created.
Enum constructors cannot be public or protected.
Enum constants are implicitly public static final.
Enum constructors are useful when you want to associate additional data with enum constants.
You can also define methods within enums to act on these values.
Working
Enum constants (SMALL, MEDIUM, LARGE) each call the enum
constructor with a specific description.
This initializes the description field for each constant.
We can then access these values via getter methods or directly
within enum methods.
Enum constructors enhance enums by associating state and
behavior with each constant.
Java User Input
The Scanner class is used to get user input, and it is found in the java.util package.
Use the Scanner class from the java.util package.
Create a Scanner object with System.in as input, which reads from the keyboard.
Use various built-in methods of Scanner to read different types of input data.
Basic Steps Input Types
Import Scanner.
Create an instance: Scanner sc = new Scanner(System.in);
Use appropriate nextXXX() methods to read input.
Close the scanner after use with sc.close();.
Packages
A package in Java is used to group related classes.
A package in Java is a namespace that organizes a set of related classes, interfaces, and sub-packages.
Think of it as a folder/directory on your computer used to group related files.
Packages help avoid naming conflicts by allowing classes with the same name in different packages.
They improve code organization, reusability, maintainability, and access control.
Benefits of Using Packages
Prevents name clashes between classes.
Makes large projects manageable by grouping related classes.
Controls access via access specifiers (public, protected, default).
Easier to locate and use classes and interfaces.
Declaring a Package
The package statement must be the first line in a Java source file (excluding comments).
Syntax example:
Types of Packages
Built-in Packages: Provided by the Java API (e.g., java.util, java.lang, java.io).
User-defined Packages: Created by developers to organize their own classes.
Classpath
The classpath is an environment variable or a parameter used by the Java Virtual Machine (JVM) and the Java
compiler to specify the locations where class files (.class) and packages can be found.
It tells Java where to look for user-defined classes, third-party libraries (JAR files), and other resources
needed to run Java programs.
By default, the classpath includes the Java runtime classes and the current directory (".") if no other path is
specified.
If Java can't find the .class files for your program or libraries, it will throw an error like
ClassNotFoundException or NoClassDefFoundError.
We need to ensure your classpath includes all directories and JAR files required for compilation and
execution.
Explanation:
. denotes the current directory.
Multiple paths are separated by ;
on Windows or : on Unix/Linux.
How Classpath Works:
Java searches the classpath entries in order until it
finds the required class.
It can include:
Directories containing compiled .class files
JAR (Java Archive) files that bundle multiple classes
and resources
Access Protection
Access protection controls the visibility and accessibility of classes, methods, variables, and constructors in
Java.
It helps in encapsulating data and securing parts of the code from unauthorized or unintended access.
Protected
Declared using protected.
Accessible within the same package and by
subclasses in other packages.
Allows controlled access for subclasses while
restricting others.
Why Use Access Protection?
To implement encapsulation — hiding internal data and
exposing only what is necessary.
To protect fields and methods from unintended modification.
To control the scope and lifecycle of class members.
To design more secure and maintainable software.
Exception Handling
Exception handling is a mechanism in Java to handle
runtime errors so that the normal flow of the application
can be maintained.
It prevents the program from terminating abruptly and
allows graceful recovery.
Java Errors
Java errors can be broadly classified into three main types:
Compile-Time Errors
Runtime Errors
Logical Errors
Compile-Time Errors
These errors occur when the program fails to compile due to
syntax or semantic mistakes.
Detected by the compiler before the program runs.
Common examples include :
Missing Semicolon
Missing Semicolon
Mismatched Types
Runtime Errors
Occur while the program is running.
Typically caused by illegal operations such as dividing by
zero, accessing invalid array indices.
These raise Exceptions and can crash the program if not
handled.
Runtime errors occur when the program compiles but
crashes or behaves unexpectedly.
Logical Errors
Program runs without crashing but produces
incorrect output.
Hardest to find since the program doesn't crash or
throw errors.
Caused by incorrect logic or mistakes in the
algorithm.
Logical errors happen when the code runs, but the
result is not what we thought:
Exception handling in Java uses these main constructs:
try
catch
finally
throw
throws
The try statement allows you to define a block of code to be tested for errors while it is being executed.
The catch statement allows you to define a block of code to be executed, if an error occurs in the try block.
try-catch block
Handle exceptions by wrapping the risky code in a try block and catching exceptions in catch.
try-catch-finally block
finally block is executed always, whether exception occurs or not.
The finally statement lets you execute code, after try...catch, regardless of the result:
throw keyword
Explicitly throw an exception from a method or block.
The throw statement allows you to create a custom error.
The throw statement is used together with an exception
type.
There are many exception types available in Java:
ArithmeticException, FileNotFoundException,
ArrayIndexOutOfBoundsException, SecurityException,
etc
throws keyword
Declare exceptions a method might throw,
passing the responsibility to handle to the
caller.
User-defined exception
Create custom exceptions by extending Exception or RuntimeException
Nested Try Statement
A nested try statement in Java is a try block inside another
try block.
It helps in handling exceptions at different levels of code
granularity.
The inner try block can handle exceptions related to a
specific part, while the outer try block can handle broader
exceptions.
Working
If an exception occurs in the inner try block and is caught by
its associated catch block, outer catch blocks aren't executed
for that exception.
If an inner try block doesn't handle an exception, it
propagates to the outer try's catch blocks.
If no catch block handles it, the exception is thrown to the
JVM, terminating the program.
Built-In Exception
Java provides a rich set of built-in exceptions (predefined classes) to handle common error situations
gracefully.
These exceptions are categorized as:
Checked Exceptions
Unchecked Exceptions (Runtime Exceptions)
Errors
Checked exceptions force better error management.
Unchecked exceptions indicate bugs or logic errors.
Use try-catch-finally blocks, throw and throws keywords to manage exceptions properly.
Checked Exceptions
Checked during compile time.
The programmer must handle these either by try-catch or declaring with throws.
Common checked exceptions:
Unchecked Exceptions (Runtime Exceptions)
Not checked at compile time.
Usually occur due to programming bugs.
Examples include:
Errors
Serious problems that applications usually cannot handle.
Examples include:
Uncaught Exceptions
An uncaught exception in Java is an exception that occurs during program execution but is not handled by any
try-catch block in the calling hierarchy.
If the exception reaches the top of the stack (main method) without being caught, the Java Virtual Machine (JVM)
terminates the thread and prints details of the uncaught exception, including the exception name, description, and
stack trace in the console.
Creating Your Own Exception Class
Creating your own exception class in Java allows you to define custom errors specific to your application's logic.
We can create checked or unchecked exceptions by extending the Exception or RuntimeException classes,
respectively.
Steps to Create a Custom Exception
Declare a new class extending Exception (checked) or RuntimeException (unchecked).
Provide constructors (with or without messages).
Optionally, add custom methods for extra details.
Throw your exception in code using the throw keyword.
Handle it with try-catch or propagate with throws.
Example 1: Checked Custom Exception
Example 2: Unchecked Custom Exception
Working Lifecycle
Creation: class MyException extends Exception { ... }
Throwing: throw new MyException("problem details");
Propagation: If not caught locally, bubbles up to the caller (and so on, up the stack).
Catching: catch (MyException e) { ... }
Default Handling: If not caught anywhere, the JVM's default handler prints a stack trace and terminates the thread.
How Custom Exceptions Work
Inheritance Structure
Custom exceptions are classes that typically extend Exception (for checked exceptions) or RuntimeException (for
unchecked exceptions).
By extending these, your class becomes part of the Java Exception Hierarchy.
Java’s exception handling mechanism (try-catch-finally, throw/throws, stack unwinding) works the same way with
custom exceptions as with built-in ones.
Instantiation and Throwing
You create an object of your custom exception and use the throw keyword to signal that an exceptional situation
has occurred.
For checked exceptions, the compiler forces you to either handle or declare them in the method signature using
throws.
Catching and Handling
Code that might generate the exception is placed inside a try block.
The custom exception can be caught explicitly in a catch block—where you access its message or custom data.
Java Thread Model
The Java Thread Model enables a program to perform multiple tasks concurrently within a single process, using
individual execution threads.
This model is the foundation of Java's multithreading capabilities and is essential for building responsive
applications, efficient I/O handling, and parallel computation.
Threads allows a program to operate more efficiently by doing multiple things at the same time.
Threads can be used to perform complicated tasks in the background without interrupting the main program.
Core Concepts
Thread
The smallest unit of execution within a Java program.
Each thread runs independently but shares process resources (heap, method area) with other threads.
Main Thread
Every Java application has one initial thread, called the main thread.
Additional threads can be created and started as needed.
Thread Lifecycle
Java threads transition through specific states during their lifecycle:
New: Thread object created but not started.
Runnable: After start() is called; thread waiting for CPU time.
Running: Thread is executing its run() method.
Blocked/Waiting: Thread is alive but waiting for a resource, lock, or signal.
Timed Waiting: Thread waits for a specified amount of time (sleep(), wait(timeout)).
Terminated (Dead): Thread has completed execution or was stopped.
Creating Threads
Scheduling and Execution
The Thread Scheduler determines which thread runs at a given time, based on priorities and system policies.
Threads share CPU time ("time slicing"), making Java's model pre-emptive and priority-based.
Multiple threads may appear to run simultaneously (concurrent execution), especially on multicore CPUs.
Synchronization
Threads share resources (variables, objects); race conditions may occur if access isn't managed.
Java provides synchronization primitives (synchronized keyword, wait(), notify(), monitors), ensuring only one
thread accesses critical sections at a time.
Every Java object acts as a monitor with an implicit lock
Interthread Communication
Threads can communicate and coordinate via methods like wait(), notify(), and notifyAll(), typically for managing
dependency or resource sharing.
"Pre-emptive and Priority-based"
Pre-emptive Scheduling
The thread scheduler can forcibly pause a currently running thread and switch to another thread at any time,
not just when the running thread finishes or yields.
This ensures that higher-priority tasks can interrupt lower-priority tasks, improving responsiveness in
multitasking scenarios
Priority-based Scheduling
Every Java thread has a priority (integer from 1 to 10; Thread.MIN_PRIORITY to Thread.MAX_PRIORITY; default
is 5).
The scheduler always selects the highest-priority runnable thread for execution.
If a new thread with higher priority becomes runnable, it can preempt (interrupt) the running lower-priority
thread.
Threads with the same priority are scheduled in a time-sharing (round-robin) fashion, but the exact order is
not guaranteed and is JVM/OS dependent.
Java Thread Model: Important Keywords, Description, Syntax, and Examples
Java Thread Communication Keywords: wait(), notify(), and notifyAll()
Suspending Threads
Java originally provided methods like suspend() and resume() in the Thread class to pause and
resume threads.
These methods are now deprecated because they can cause deadlocks or leave threads suspended
indefinitely.
Current best practice is to use inter-thread communication techniques like wait(), notify(), and
controlled flags to suspend and resume threads safely.
Recommended Safe Approach: Using Wait and Notify
Use a shared boolean flag with synchronization to control suspension and resumption.
Inside a loop, threads check the flag and call wait() if suspended.
Another thread calls notify() to resume waiting threads.
Stopping Threads
Stopping a thread gracefully or prematurely in Java requires care to avoid inconsistent program states.
The older methods stop(), suspend(), and resume() are deprecated due to unsafe behavior like deadlocks
and resource corruption.
Recommended ways to stop threads:
Using a volatile boolean flag to signal the thread to stop.
Using the interrupt() method to request thread interruption.
Using a Volatile Flag
The thread should periodically check a volatile boolean flag and stop itself by exiting the run
method when the flag is set.
Using interrupt() Method
Sends an interrupt signal to a thread.
If the thread is blocked (sleep, wait, join), it will immediately throw InterruptedException.
The interrupted thread can check Thread.interrupted() or isInterrupted() to handle the interrupt and stop gracefully.
Why Thread.stop() is Deprecated?
It forcibly terminates the thread regardless of what it is doing.
Can leave resources (files, memory, locks) in an inconsistent or corrupted state.
May cause deadlocks.
Never use stop() in production code.