Unit-3
Introduction to Inheritance in Java
Inheritance in Java is a fundamental concept of Object-Oriented Programming
(OOP) that allows a class to acquire the properties and behaviors (fields and
methods) of another class. This mechanism promotes code reusability,
extensibility, and the creation of a hierarchical structure among classes. The
class that inherits is called the subclass (or child class, derived class), and the
class from which it inherits is called the superclass (or parent class, base class).
Process of Inheritance
When a subclass extends a superclass using the extends keyword, it gains
access to the public and protected members of the superclass. Default
members are also inherited if both classes reside in the same package. Private
members of the superclass are not directly inherited but can be accessed
indirectly through public or protected methods of the superclass. When an
object of the subclass is created, memory is allocated for both the subclass's
own unique members and the inherited members from the
superclass. Subclass constructors must explicitly or implicitly invoke a
superclass constructor using super().
Types of Inheritance
Java supports several types of inheritance:
Single Inheritance:
A subclass inherits from a single superclass. This is the most common type.
Multilevel Inheritance:
A chain of inheritance where a class inherits from another class, which in turn
inherits from a third class.
Hierarchical Inheritance:
Multiple subclasses inherit from a single superclass.
Multiple Inheritance (through Interfaces):
Java does not support multiple inheritance of classes directly but achieves it
through interfaces, allowing a class to implement multiple interfaces.
Universal Super Class: Object Class
In Java, the [Link] class is the root of the class hierarchy. Every class
in Java, directly or indirectly, inherits from the Object class. This means all
classes implicitly inherit methods like equals(), hashCode(), toString(),
and getClass() from the Object class.
Inhibiting Inheritance of Class Using final
The final keyword in Java can be used to prevent inheritance. When a class is
declared as final, it cannot be extended by any other class. This is useful for
creating immutable classes or ensuring that the implementation of a class
remains consistent and cannot be altered by subclasses.
Example:
Java
final class MyFinalClass {
// Class members and methods
}
// This will result in a compilation error:
// class SubClass extends MyFinalClass {
// // ...
// }
Access Control and Inheritance in Java
Access control determines the visibility and accessibility of class members
(fields, methods, constructors) within a Java program. When a class inherits
from another, the access modifiers of the superclass's members play a crucial
role in determining what the subclass can access.
private:
Members declared private are only accessible within their own class. They are
not inherited by subclasses in a directly accessible way.
default (package-private):
Members with no explicit access modifier are accessible only within the same
package. Subclasses in different packages cannot access them.
protected:
Members declared protected are accessible within their own class, by
subclasses (even in different packages), and by other classes within the same
package. This is commonly used for members intended for inheritance.
public:
Members declared public are accessible from anywhere in the program,
including by subclasses and unrelated classes.
Multilevel Inheritance in Java
Multilevel inheritance involves a chain of inheritance where a class inherits
from a class, which in turn inherits from another class. This creates a hierarchy
of classes, with each level extending the functionality of the previous one.
Java
class Grandparent {
void displayGrandparent() {
[Link]("I am a Grandparent.");
}
}
class Parent extends Grandparent {
void displayParent() {
[Link]("I am a Parent.");
}
}
class Child extends Parent {
void displayChild() {
[Link]("I am a Child.");
}
}
In this example, Child inherits from Parent, and Parent inherits
from Grandparent. A Child object can access methods from
both Parent and Grandparent.
Application of the super Keyword in Java
The super keyword in Java is a reference variable used to refer to the
immediate parent class object. Its primary applications are:
Accessing Parent Class Variables: When a subclass has a field with the
same name as a field in its superclass, [Link] can be used
to explicitly refer to the superclass's version.
Java
class Animal {
String type = "Generic Animal";
}
class Dog extends Animal {
String type = "Dog";
void printType() {
[Link]("Subclass type: " + type);
[Link]("Superclass type: " + [Link]);
}
}
Calling Parent Class Methods: If a subclass overrides a method from its
superclass, [Link]() can be used within the subclass to
invoke the superclass's implementation of that method.
Java
class Shape {
void draw() {
[Link]("Drawing a generic shape.");
}
}
class Circle extends Shape {
@Override
void draw() {
[Link](); // Calls the draw() method of the Shape class
[Link]("Drawing a circle.");
}
}
Invoking Parent Class Constructors: super() or super(arguments) is used
within a subclass constructor to call a corresponding constructor of the
immediate superclass. This call must be the very first statement in the
subclass constructor. If no explicit super() call is made, the compiler
implicitly inserts a call to the superclass's no-argument constructor.
Java
class Vehicle {
Vehicle(String make) {
[Link]("Vehicle make: " + make);
}
}
class Car extends Vehicle {
Car(String make, String model) {
super(make); // Calls the Vehicle constructor
[Link]("Car model: " + model);
}
}
1. Constructor Method and Inheritance:
Constructors:
Special methods used to initialize objects. They have the same name as the
class and no return type.
Constructor Inheritance:
Constructors are not inherited by subclasses. However, a subclass constructor
implicitly or explicitly calls a superclass constructor to ensure the superclass
part of the object is properly initialized before the subclass's own
initialization. This is known as constructor chaining, often using super().
2. Method Overriding:
Definition:
When a subclass provides a specific implementation for a method that is
already defined in its superclass. The overriding method in the subclass must
have the same name, return type, and parameters as the method in the
superclass.
Purpose:
Allows subclasses to provide their own distinct behavior for inherited methods,
achieving runtime polymorphism.
3. Dynamic Method Dispatch (Runtime Polymorphism):
Definition:
The mechanism by which a call to an overridden method is resolved at runtime,
not compile time. When a superclass reference variable refers to a subclass
object, the actual method executed depends on the type of the object being
referred to, not the type of the reference variable.
Example:
If Parent obj = new Child(); and Parent and Child both have a show() method,
calling [Link]() will execute Child's show() method.
4. Abstract Classes:
Definition:
Classes that cannot be instantiated directly and may contain abstract methods
(methods declared without an implementation).
Purpose:
Provide a common base for related subclasses, defining a contract that
subclasses must adhere to by implementing the abstract methods.
Characteristics:
Can have both abstract and concrete (implemented) methods, and can have
constructors (though they cannot be directly called).
5. Interfaces and Inheritance:
Interfaces:
Blueprints of a class, containing only abstract methods (before Java 8) and
constants. From Java 8 onwards, interfaces can also contain default and static
methods.
Interface Inheritance:
Classes implement interfaces, thereby agreeing to provide implementations for
all the abstract methods defined in the interface. A class can implement
multiple interfaces, achieving a form of multiple inheritance of behavior.
Inheritance vs. Implementation:
Classes inherit from other classes (using extends), while classes implement
interfaces (using implements).
Introduction to Interfaces
In Java, an interface is a blueprint of a class. It can contain method signatures
(abstract methods), default methods, static methods, and constants. Interfaces
are used to achieve abstraction and support multiple inheritance of types, as
Java does not support multiple inheritance of classes. They define a contract
that implementing classes must adhere to, specifying what a class should do,
but not how it does it.
Declaration of an Interface
An interface is declared using the interface keyword. All methods declared
within an interface are implicitly public and abstract (unless they are default or
static methods introduced in Java 8), and all fields are implicitly public, static,
and final.
Java
interface MyInterface {
int MY_CONSTANT = 10; // Implicitly public static final
void abstractMethod(); // Implicitly public abstract
default void defaultMethod() {
[Link]("This is a default method.");
}
static void staticMethod() {
[Link]("This is a static method.");
}
}
Implementation of an Interface
A class implements an interface using the implements keyword. When a class
implements an interface, it must provide concrete implementations for all the
abstract methods declared in that interface. If a class fails to implement all
abstract methods, it must be declared as an abstract class itself.
Java
class MyClass implements MyInterface {
@Override
public void abstractMethod() {
[Link]("Implementation of abstractMethod.");
}
// No need to implement default or static methods, but can override default
methods
}
Multiple Interfaces
A key advantage of interfaces is that a Java class can implement multiple
interfaces. This allows a class to inherit and adhere to multiple contracts,
achieving a form of multiple inheritance of behavior. The class must provide
implementations for all abstract methods from all implemented interfaces.
Java
interface AnotherInterface {
void anotherAbstractMethod();
}
class MyClassWithMultipleInterfaces implements MyInterface,
AnotherInterface {
@Override
public void abstractMethod() {
[Link]("Implementation of abstractMethod from
MyInterface.");
}
@Override
public void anotherAbstractMethod() {
[Link]("Implementation of anotherAbstractMethod from
AnotherInterface.");
}
}
1. Nested Interfaces:
An interface declared inside another interface or a class is known as a
nested interface (or inner interface).
They are primarily used to group related interfaces or to resolve
namespaces.
Nested interfaces declared within an interface are implicitly public static.
Nested interfaces declared within a class can have any access modifier
(public, protected, package-private, private).
Example:
Java
class OuterClass {
interface InnerInterface {
void doSomething();
}
}
interface OuterInterface {
interface NestedInterface {
void performAction();
}
}
2. Inheritance of Interfaces:
Interfaces can inherit from other interfaces using the extends keyword.
An interface can extend multiple other interfaces, achieving multiple
inheritance of type.
The inheriting interface automatically includes all the abstract methods,
default methods, and static methods of the parent interfaces.
Classes implementing the inheriting interface must provide
implementations for all abstract methods from all parent interfaces.
Example:
Java
interface ParentInterfaceA {
void methodA();
}
interface ParentInterfaceB {
void methodB();
}
interface ChildInterface extends ParentInterfaceA, ParentInterfaceB {
void methodC();
}
3. Default Methods in Interfaces:
Introduced in Java 8, default methods allow adding new methods to
interfaces without breaking existing implementations.
They are non-abstract methods with an implementation provided
directly within the interface, using the default keyword.
Classes implementing the interface can use the default implementation
or override it.
Example:
Java
interface MyInterface {
void abstractMethod();
default void defaultMethod() {
[Link]("Default implementation");
}
}
4. Static Methods in Interfaces:
Also introduced in Java 8, static methods in interfaces belong to the
interface itself, not to any implementing object.
They are called directly using the interface name and cannot be
overridden by implementing classes.
Static methods are primarily used for utility methods related to the
interface.
Example:
Java
interface UtilityInterface {
static void helperMethod() {
[Link]("Static helper method");
}
}
5. Functional Interfaces:
A functional interface is an interface that contains exactly one abstract
method.
They are crucial for working with Lambda expressions and method
references in Java 8 and later.
Functional interfaces can have any number of default and static
methods.
The @FunctionalInterface annotation is used to mark an interface as
functional, which helps the compiler enforce the single abstract method
rule.
Example:
Java
@FunctionalInterface
interface MyFunctionalInterface {
void singleAbstractMethod();
default void anotherMethod() {} // Default methods are allowed
}
6. Annotations:
Annotations are a form of metadata that can be added to Java source
code.
They provide information about the code without directly affecting its
execution.
Annotations can be used for various purposes, such as compile-time
checks, code generation, and runtime processing.
Common built-in annotations
include @Override, @Deprecated, @SuppressWarnings,
and @FunctionalInterface. Custom annotations can also be defined.
Example:
Java
@Override
public void someMethod() {
// Overriding a method from a superclass or interface
}
@Deprecated
public void oldMethod() {
// This method is deprecated
}
Unit IV:
Packages in Java are a mechanism that encapsulates a group of
classes, sub-packages and interfaces. Packages are used for:
Prevent naming conflicts by allowing classes with the same
name to exist in different packages, like
[Link] and [Link].
Make it easier to organize, locate and use classes, interfaces
and other components.
Provide controlled access for Protected members that are
accessible within the same package and by subclasses. Default
members (no access specifier) are accessible only within the
same package.
By grouping related classes into packages, Java promotes data
encapsulation, making code reusable and easier to manage. Simply
import the desired class from a package to use it in your program.
Creating Custom Packages
Step 1: Create a directory in which we create our packages and Java
files.
mkdir PROGRAMMING
Step 2: Now, change the directory and create another folder inside
the main folder
cd PROGRAMMING
mkdir JavaProgramming
cd JavaProgramming
mkdir arrays
Step 3: Now create an empty text file and write the below Java code
and don't forget to save it with the same name as the class with .java
extension ([Link])
TwoPointers Class.
package [Link];
// Main class present inside the package
public class TwoPointers {
public static void main(String[] args) {
[Link]("Inside the package");
}
}
Note: Do not forget to add the package name inside the program file.
Step 4: Now run the program with the define folder path
javac src\JavaProgramming\arrays\[Link]
java src\JavaProgramming\arrays\[Link]
Output:
Runing program with Folder path
Folder Structure:
This is the visual representation of a custom package in Java in the
below image. First, we create a folder named Progamming and inside
it we create a package Javaprogramming and then create another
subpackage, which is called arrays. Then, we create a Java class file
inside it, which is shown in the image below:
Folder Structure
Working of Java Packages
Directory Structure: Package names and directory structures are
closely related. For example, if a package name is [Link],
then three directories are, college, staff and cse, where cse is inside
staff and staff is inside the college.
Naming Conventions: Package names are written in reverse order of
domain names, e.g., [Link]. In a college, the
convention might be:
[Link]
[Link]
[Link]
Example:
import [Link].*;
Here, util is a sub-package created inside the java package.
Accessing Classes Inside a Package
In Java, we can import classes from a package using either of the
following methods:
1. Import a specific class:
import [Link];
This imports only the Vector class from the [Link] package.
2. Import all classes from a package:
import [Link].*;
This imports all classes and interfaces from the [Link] package but
does not include sub-packages.
Example: Import the Vector class
import [Link];
public class Geeks {
public Geeks() {
// [Link] is imported, We are able to access it directly in
our code.
Vector v = new Vector();
[Link] l = new [Link]();
[Link](3);
[Link](5);
[Link](7);
[Link](l);
}
public static void main(String[] args) {
new Geeks();
}
}
Output
[3, 5, 7]
Note:
Using import package.*; imports all classes in a package, but
not classes from its sub-packages.
When two packages have classes with the same name (e.g.,
[Link] and [Link]), use the fully qualified
name to avoid conflicts:
import [Link];
import [Link];
Types of Java Packages
Built-in Packages
User-defined Packages
1. Built-in Packages
These packages consist of a large number of classes which are a part
of Java [Link] of the commonly used built-in packages are:
[Link]: Contains language support classes(e.g classes which
defines primitive data types, math operations). This package is
automatically imported.
[Link]: Contains classes for supporting input / output
operations.
[Link]: Contains utility classes which implement data
structures like Linked List, Dictionary and support ; for Date /
Time operations.
[Link]: Contains classes for creating Applets.
[Link]: Contain classes for implementing the components for
graphical user interfaces (like button , ;menus etc). 6)
[Link]: Contain classes for supporting networking operations.
2. User-defined Packages
These are the packages that are defined by the user.
1. Create the Package:
First we create a directory myPackage (name should be same as the
name of the package). Then create the MyClass inside the directory
with the first statement being the package names.
Example:
package myPackage;
public class MyClass
{
public void getNames(String s)
{
[Link](s);
}
}
2. Use the Class in Program:
Now we will use the MyClass class in our program.
import [Link];
public class Geeks {
public static void main(String args[]) {
// Initializing the String variable with a value
String s = "GeeksforGeeks";
// Creating an instance of class MyClass in the package.
MyClass o = new MyClass();
[Link](s);
}
}
Note: [Link] must be saved inside the myPackage directory
since it is a part of the package.
Static Import In Java
Static Import in Java is about simplifying access to static members
and separates it from the broader discussion of user-defined
packages.
Static import is a feature introduced in Java programming language
(versions 5 and above) that allows members (fields and methods)
defined in a class as public static to be used in Java code without
specifying the class in which the field is defined.
Example:
import static [Link].*;
class Geeks {
public static void main(String args[]) {
// We don't need to use '[Link]' as imported using static.
[Link]("GeeksforGeeks");
}
}
Output
Handling Name Conflicts
When two packages contain a class with the same name (e.g.,
[Link] and [Link]), specify the full package name to
avoid conflicts.
import [Link].*;
import [Link].*;
//And then use Date class, then we will get a compile-time error :
Date today ; //ERROR-- [Link] or [Link]?
The compiler will not be able to figure out which Date class do we
want. This problem can be solved by using a specific import
statement:
import [Link];
import [Link].*;
If we need both Date classes then, we need to use a full package
name every time we declare a new object of that class. For Example:
[Link] deadLine = new [Link]();
[Link] today = new [Link]();
Directory Structure and CLASSPATH
Package names correspond to a directory structure. For example, a
class Circle in package [Link].project1.subproject2 is stored as:
$BASE_DIR/com/zzz/project1/subproject2/[Link]
Here $BASE_DIR represents the base directory of the package.
The "dot" in the package name corresponds to a sub-directory
of the file system.
The base directory ($BASE_DIR) could be located anywhere in
the file system.
Hence, the Java compiler and runtime must be informed about
the location of the $BASE_DIR so as to locate the classes.
It is is accomplished by an environment variable
called CLASSPATH.
CLASSPATH is similar to another environment variable PATH,
which is used by the command shell to search for the
executable programs.
Setting CLASSPATH
CLASSPATH can be set by any of the following ways:
CLASSPATH can be set permanently in the environment the
steps In Windows is
Go to Control Panel -> System -> Advanced -> Environment Variables.
Select "System Variables" to apply the CLASSPATH for all users
on the system.
Select "User Variables" to apply it only for the currently logged-
in user.
Edit or Create CLASSPATH : If CLASSPATH already exists, select it
and click "Edit" or If it doesn't exist, click "New"
Enter CLASSPATH Details: In the "Variable name" field, enter:
"CLASSPATH", In the "Variable value" field, enter the directories
and JAR files separated by semicolons.
In the "Variable value" field, enter the directories and JAR files
separated by semicolons. Example:
.c:\javaproject\classes;d:\tomcat\lib\[Link]
The dot (.) represents the current working directory.
To check the current setting of the CLASSPATH, issue the
following command:
> SET CLASSPATH
CLASSPATH can be set temporarily for that particular CMD shell
session by issuing the following command:
> SET CLASSPATH=.;c:\javaproject\classes;d:\tomcat\lib\servlet-
[Link]
Instead of using the CLASSPATH environment variable, you can also
use the command-line option -classpath or -cp of the javac and java
commands, for example,
> java –classpath c:\javaproject\classes
[Link].project1.subproject2.MyClass3
Illustration of user-defined packages: Creating our first package: File
name – [Link]
package package_name;
public class ClassOne {
public void methodClassOne()
{
[Link]("Hello there its ClassOne");
}
}
Creating our second package: File name – [Link]
package package_one;
public class ClassTwo {
public void methodClassTwo()
{
[Link]("Hello there i am ClassTwo");
}
}
Making use of both the created packages: File name – [Link]
import package_name.ClassOne;
import package_one.ClassTwo;
public class Testing {
public static void main(String[] args)
{
ClassTwo a = new ClassTwo();
ClassOne b = new ClassOne();
[Link]();
[Link]();
}
}
Now having a look at the directory structure of both the packages
and the testing class file:
Access Modifiers in the Context of Packages
Public: Members with the public modifier are accessible from
anywhere, regardless of whether the accessing class is in the
same package or not.
Protected: Members with the protected modifier are accessible
within the same package, In subclasses
Default: Members with no modifier are accessible only within
the same package
Private: Members with the private modifier are accessible only
within the same class. They cannot be accessed by classes in
the same package, subclasses, or different packages.
1. PATH and CLASSPATH:
PATH:
An operating system environment variable that specifies directories
where executable programs (like [Link], [Link]) are located. The
operating system uses the PATH to find these executables when you
run a command from the command line.
CLASSPATH:
A Java-specific environment variable or command-line option (-
classpath or -cp) that tells the Java Virtual Machine (JVM) where to
find user-defined classes, as well as classes from the Java API and
third-party libraries (in .class files or .jar files).
2. Access Control (Access Modifiers):
Java's access modifiers control the visibility and accessibility of
classes, methods, and variables. There are four types:
public: Accessible from anywhere.
protected: Accessible within the same package and by
subclasses (even if in different packages).
default (no modifier): Accessible only within the same package.
private: Accessible only within the declaring class.
3. Packages in Java SE:
Packages are a mechanism to organize classes and interfaces
into logical groups, preventing naming conflicts and providing a
level of access control.
They are mapped to directory structures in the file system. For
example, a class MyClass in package [Link] would
reside in a directory structure
like com/example/app/[Link].
The import statement is used to bring classes from other
packages into the current scope, making them directly usable
without their fully qualified names.
4. [Link] Package and its Classes:
The [Link] package is fundamental to Java and is
automatically imported into every Java program. It contains
core classes that are essential for the Java language.
Key classes in [Link] include:
o Object: The root of the class hierarchy; every class in Java
directly or indirectly inherits from Object.
o String: Represents sequences of characters.
o System: Provides access to system resources, including
standard input/output streams
([Link], [Link], [Link]).
o Math: Provides methods for performing basic numeric
operations, such as elementary exponential, logarithm,
square root, and trigonometric functions.
o Wrapper classes (e.g., Integer, Double, Boolean): Provide
object representations for primitive data types.
o Thread: Used for creating and managing threads in
multithreaded programming.
o Throwable, Exception, Error: Core classes for exception
handling.
1. [Link] Class:
Object is the root of the class hierarchy in Java. Every class in
Java, directly or indirectly, inherits from the Object class.
It provides fundamental methods that are common to all
objects, such
as equals(), hashCode(), toString(), getClass(), wait(), notify(),
and notifyAll().
2. [Link] (Enumeration):
Enum is a special class type in Java that allows defining a fixed
set of named constants.
Enums are implicitly final and extend [Link].
They can have constructors, instance variables, methods, and
can implement interfaces, similar to regular classes.
Example: public enum Day { MONDAY, TUESDAY, WEDNESDAY }
3. [Link] Class:
The Math class provides a collection of static methods for
performing common mathematical operations.
It contains methods for basic numeric operations
(e.g., abs(), round(), min(), max()), trigonometric functions
(e.g., sin(), cos()), exponential functions (e.g., pow(), sqrt()), and
more.
All methods in Math are static, so they are called directly on the
class (e.g., [Link](25)).
4. Wrapper Classes:
Wrapper classes provide a way to use primitive data types
(like int, char, double) as objects.
For each primitive type, there is a corresponding wrapper class
in the [Link] package
(e.g., Integer for int, Character for char, Double for double).
They are essential when working with Java Collections
Framework (which only stores objects) and for utilizing object-
oriented features with primitive values.
5. Auto-boxing and Auto-unboxing:
Auto-boxing: The automatic conversion of a primitive data type
to its corresponding wrapper class object. This feature,
introduced in Java 5, simplifies code by eliminating the need for
explicit object creation.
Java
int primitiveInt = 10;
Integer wrapperInt = primitiveInt; // Auto-boxing: int to Integer
Auto-unboxing: The automatic conversion of a wrapper class
object to its corresponding primitive data type. This also
simplifies code by allowing direct use of wrapper objects where
primitive values are expected.
Java
Integer wrapperInt = 20;
int primitiveInt = wrapperInt; // Auto-unboxing: Integer to int
A Wrapper class in Java is one whose object wraps or
contains primitive data types. This leads to two key features:
Autoboxing and Unboxing.
1. Autoboxing
The automatic conversion of primitive types to the object of their
corresponding wrapper classes is known as autoboxing. For example:
conversion of int to Integer, long to Long, double to Double, etc.
2. Unboxing
It is just the reverse process of autoboxing. Automatically converting
an object of a wrapper class to its corresponding primitive type is
known as unboxing. For example, conversion of Integer to int, Long to
long, Double to double, etc.
1. [Link] Package: Classes and Interfaces
The [Link] package contains the Java Collections Framework, legacy
collection classes, event model, date and time facilities,
internationalization, and various utility classes.
Key
Classes: ArrayList, HashMap, HashSet, Stack, Vector, StringToke
nizer, UUID, Scanner, Timer, TimerTask.
Key
Interfaces: List, Set, Map, Queue, Iterator, Comparator, Compar
able.
2. [Link] Class
The Formatter class provides functionality for parsing format strings
and formatting arbitrary data using a printf-style syntax. It allows for
precise control over output formatting, including alignment, padding,
precision, and data type conversions.
Java
import [Link];
public class FormatterExample {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder();
Formatter formatter = new Formatter(sb);
[Link]("Hello, %s! You are %d years old.", "Alice", 30);
[Link]([Link]()); // Output: Hello, Alice! You are
30 years old.
[Link]();
}
}
3. [Link] Class
The Random class is used to generate a stream of pseudorandom
numbers. It can generate various primitive types
(e.g., int, long, float, double, boolean) within specified ranges.
Java
import [Link];
public class RandomExample {
public static void main(String[] args) {
Random random = new Random();
int randomNumber = [Link](100); // Generates a
random int between 0 (inclusive) and 100 (exclusive)
[Link]("Random number: " + randomNumber);
}
}
4. [Link] Package (Java 8 Date and Time API)
The [Link] package, introduced in Java 8, provides a modern and
comprehensive API for handling dates and times. It addresses many
of the shortcomings of the
older [Link] and [Link] classes.
Key
Classes: LocalDate, LocalTime, LocalDateTime, ZonedDateTime,
Instant, Duration, Period, ZoneId, DateTimeFormatter.
5. [Link] Class
The Instant class represents a single, instantaneous point on the
time-line, often used for recording event timestamps. It is a point in
time in UTC (Coordinated Universal Time) since the epoch (1970-01-
01T00:00:00Z) with nanosecond precision.
Java
import [Link];
public class InstantExample {
public static void main(String[] args) {
Instant now = [Link](); // Current instant
[Link]("Current Instant: " + now);
Instant epochInstant = [Link](0); // Instant at
the epoch
[Link]("Epoch Instant: " + epochInstant);
Instant plusSeconds = [Link](3600); // Add one hour
[Link]("Instant plus one hour: " + plusSeconds);
}
}
Date/Time Formatting in Java
Java provides robust mechanisms for formatting and parsing dates
and times, primarily through the [Link] package introduced in Java
8. The key class for formatting is DateTimeFormatter.
Using DateTimeFormatter:
Creating a Formatter: You can create
a DateTimeFormatter using predefined constants or by
specifying a custom pattern.
Java
import [Link];
import [Link];
// Predefined formatter
DateTimeFormatter isoDateTime =
DateTimeFormatter.ISO_LOCAL_DATE_TIME;
// Custom pattern formatter
DateTimeFormatter customFormatter =
[Link]("yyyy/MM/dd HH:mm:ss");
Formatting: Use the format() method of
the DateTimeFormatter instance.
Java
LocalDateTime now = [Link]();
String formattedIso = [Link](isoDateTime); // e.g., 2025-10-
03T10:47:30.123
String formattedCustom = [Link](customFormatter); // e.g.,
2025/10/03 10:47:30
TemporalAdjusters Class in Java
The TemporalAdjusters class (within [Link]) provides a
set of static factory methods for
obtaining TemporalAdjuster instances. These adjusters are used to
perform common date calculations and modifications
on Temporal objects (like LocalDate, LocalDateTime, etc.).
Key Features and Usage:
Predefined Adjusters:
TemporalAdjusters offers methods for common adjustments:
firstDayOfMonth(), lastDayOfMonth()
firstDayOfNextMonth(), lastDayOfNextMonth()
firstDayOfYear(), lastDayOfYear()
next(DayOfWeek), previous(DayOfWeek)
nextOrSame(DayOfWeek), previousOrSame(DayOfWeek)
dayOfWeekInMonth(int ordinal, DayOfWeek
dayOfWeek) (e.g., "first Monday in June")
Applying Adjusters:
Use the with() method of a Temporal object, passing the
desired TemporalAdjuster.
Java
import [Link];
import [Link];
import [Link];
LocalDate today = [Link]();
LocalDate firstDayOfNextMonth =
[Link]([Link]());
LocalDate nextMonday =
[Link]([Link]([Link]));
Custom Adjusters: You can create your own
custom TemporalAdjuster by implementing
the TemporalAdjuster interface and
its adjustInto(Temporal) method. This allows for highly specific
date manipulation logic.
Exception Handling:
Exception handling in Java is an effective mechanism for managing
runtime errors to ensure the application's regular flow is maintained.
Some Common examples of exceptions include
ClassNotFoundException, IOException, SQLException,
RemoteException, etc. By handling these exceptions, Java enables
developers to create robust and fault-tolerant applications.
Example: Showing an arithmetic exception or we can say a divide by
zero exception.
import [Link].*;
class Geeks {
public static void main(String[] args)
{
int n = 10;
int m = 0;
int ans = n / m;
[Link]("Answer: " + ans);
}
}
Output:
output
Note: When an exception occurs and is not handled, the program
terminates abruptly and the code after it, will never execute.
Example: The below Java program modifies the previous example to
handle an ArithmeticException using try-catch and finally blocks and
keeps the program running.
import [Link].*;
class Geeks {
public static void main(String[] args)
{
int n = 10;
int m = 0;
try {
// Code that may throw an exception
int ans = n / m;
[Link]("Answer: " + ans);
}
catch (ArithmeticException e) {
// Handling the exception
[Link](
"Error: Division by zero is not allowed!");
}
finally {
[Link](
"Program continues after handling the exception.");
}
}
}
Output
Error: Division by zero is not allowed!
Program continues after handling the exception.
workFlow
Java Exception Hierarchy
In Java, all exceptions and errors are subclasses of the Throwable
class. It has two main branches
1. Exception.
2. Error
The below figure demonstrates the exception hierarchy in Java:
Heirarchy of exception
Major Reasons Why an Exception Occurs
Exceptions can occur due to several reasons, such as:
Invalid user input
Device failure
Loss of network connection
Physical limitations (out-of-disk memory)
Code errors
Out of bound
Null reference
Type mismatch
Opening an unavailable file
Database errors
Arithmetic errors
Errors are usually beyond the control of the programmer and we
should not try to handle errors.
Types of Java Exceptions
Java defines several types of exceptions that relate to its various class
libraries. Java also allows users to define their it's exception
Exceptions can be categorized in two ways:
1. Built-in Exceptions
Checked Exception
Unchecked Exception
2. user-defined Exceptions
1. Built-in Exception
Build-in Exception are pre-defined exception classes provided by Java
to handle common errors during program execution. There are two
type of built-in exception in java.
Checked Exceptions
Checked exceptions are called compile-time exceptions because
these exceptions are checked at compile-time by the compiler.
Examples of Checked Exception are listed below:
ClassNotFoundException: Throws when the program tries to
load a class at runtime but the class is not found because it's
belong not present in the correct location or it is missing from
the project.
InterruptedException: Thrown when a thread is paused and
another thread interrupts it.
IOException: Throws when input/output operation fails.
InstantiationException: Thrown when the program tries to
create an object of a class but fails because the class is abstract,
an interface or has no default constructor.
SQLException: Throws when there is an error with the
database.
FileNotFoundException: Thrown when the program tries to
open a file that does not exist.
Unchecked Exceptions
The unchecked exceptions are just opposite to the checked
exceptions. The compiler will not check these exceptions at compile
time. In simple words, if a program throws an unchecked exception
and even if we did not handle or declare it, the program would not
give a compilation error. Examples of Unchecked Exception are listed
below:
ArithmeticException: It is thrown when there is an illegal math
operation.
ClassCastException: It is thrown when we try to cast an object
to a class it does not belong to.
NullPointerException: It is thrown when we try to use a null
object (e.g. accessing its methods or fields).
ArrayIndexOutOfBoundsException: This occurs when we try to
access an array element with an invalid index.
ArrayStoreException: This happens when we store an object of
the wrong type in an array.
IllegalThreadStateException: It is thrown when a thread
operation is not allowed in its current state.
2. User-Defined Exception
Sometimes, the built-in exceptions in Java are not able to describe a
certain situation. In such cases, users can also create exceptions,
which are called "user-defined Exceptions".
Methods to Print the Exception Information
1. printStackTrace(): Prints the full stack trace of the exception,
including the name, message and location of the error.
2. toString(): Prints exception information in the format of the
Name of the exception.
3. getMessage() : Prints the description of the exception
Try-Catch Block
A try-catch block in Java is a mechanism to handle exception. The try
block contains code that might thrown an exception and the catch
block is used to handle the exceptions if it occurs.
Internal working of try-catch Block
Java Virtual Machine starts executing the code inside the try
block.
If an exception occurs, the remaining code in the try block is
skipped and the JVM starts looking for the matching catch
block.
If a matching catch block is found, the code in that block is
executed.
After the catch block, control moves to the finally block (if
present).
If no matching catch block is found the exception is passed to
the JVM default exception handler.
The final block is executed after the try catch block. regardless
of whether an exception occurs or not.
try {
// Code that may throw an exception
} catch (ExceptionType e) {
// Code to handle the exception
}
Nested try-catch
In Java, you can place one try-catch block inside another to handle
exceptions at multiple levels.
public class NestedTryExample {
public static void main(String[] args) {
try {
[Link]("Outer try block");
try {
int a = 10 / 0; // This causes ArithmeticException
} catch (ArithmeticException e) {
[Link]("Inner catch: " + e);
}
String str = null;
[Link]([Link]()); // This causes
NullPointerException
} catch (NullPointerException e) {
[Link]("Outer catch: " + e);
}
}
}
finally Block
The finally block is used to execute important code regardless of
whether an exception occurs or not.
Note: finally block is always executes after the try-catch block. It is
also used for resource cleanup.
try {
// Code that may throw an exception
} catch (ExceptionType e) {
// Code to handle the exception
}finally{
// cleanup code
}
Handling Multiple Exception
We can handle multiple type of exceptions in Java by using multiple
catch blocks, each catching a different type of exception.
try {
// Code that may throw an exception
} catch (ArithmeticException e) {
// Code to handle the exception
} catch(ArrayIndexOutOfBoundsException e){
//Code to handle the anothert exception
}catch(NumberFormatException e){
//Code to handle the anothert exception
}
How Does JVM Handle an Exception?
When an Exception occurs, the JVM creates an exception object
containing the error name, description and program state. Creating
the exception object and handling it in the run-time system is called
throwing an exception. There might be a list of the methods that had
been called to get to the method where an exception occurred. This
ordered list of methods is called call stack. Now the following
procedure will happen:
The run-time system searches the call stack for an exception
handler
It starts searching from the method where the exception
occurred and proceeds backward through the call stack.
If a handler is found, the exception is passed to it.
If no handler is found, the default exception handler terminates
the program and prints the stack trace.
Exception in thread "abc" Name of Exception : Description
// Call Stack
Look at the below diagram to understand the flow of the call stack:
Exception flow
Illustration:
class Geeks{
public static void main(String args[])
{
// Taking an empty string
String s = null;
// Getting length of a string
[Link]([Link]());
}
}
Output:
output
Let us see an example that illustrates how a run-time system
searches for appropriate exception handling code on the call stack.
Example:
class Geeks {
// It throws the Exception(ArithmeticException)
static int divideByZero(int a, int b)
{
// this statement will cause ArithmeticException (/by zero)
int i = a / b;
return i;
}
static int computeDivision(int a, int b)
{
int res = 0;
// Try block to check for exceptions
try {
res = divideByZero(a, b);
}
// Catch block to handle NumberFormatException
catch (NumberFormatException ex) {
[Link](
"NumberFormatException is occurred");
}
return res;
}
public static void main(String args[])
{
int a = 1;
int b = 0;
// Try block to check for exceptions
try {
int i = computeDivision(a, b);
}
// Catch block to handle ArithmeticException exceptions
catch (ArithmeticException ex) {
// getMessage() will print description of exception(here / by
zero)
[Link]([Link]());
}
}
}
Output
/ by zero
How Programmer Handle an Exception?
Java exception handling uses five keywords such as try, catch, throw
and throws and finally.
Code that might cause an exception goes in the try block.
If an exception occurs, it is caught using catch.
We can throw exceptions manually with throw and methods
must declare exceptions they can throw using throws.
The finally block is used for code that must run after try,
whether an exception occurs or not.
Tip: One must go through control flow in try catch finally block for
better understanding.
Need for try-catch clause (Customized Exception Handling)
Consider the below program in order to get a better understanding of
the try-catch clause.
Example: Java Program to Demonstrate Need of try-catch Clause
class Geeks {
public static void main(String[] args) {
// Taking an array of size 4
int[] arr = new int[4];
// Now this statement will cause an exception
int i = arr[4];
// This statement will never execute as above we caught with an
exception
[Link]("Hi, I want to execute");
}
}
Output:
output
Advantages of Exception Handling
Provision to complete program execution.
Easy identification of program code and error-handling code.
Propagation of errors.
Meaningful error reporting.
Identifying error types.
Difference Between Exception and Error
Error Exception
An Error indicates a serious
Exception indicates conditions
problem that a reasonable
that a reasonable application
application should not try to
might try to catch
catch.
This is caused by conditions in
This is caused by issues with the
the program such as invalid
JVM or hardware.
input or logic errors.
Examples: OutOfMemoryError, Examples: IOException,
StackOverFlowError NullPointerException
Multiple Catch Clauses
In Java, a try block can be followed by one or more catch blocks. This
allows for handling different types of exceptions that might occur
within the try block separately. When an exception is thrown, the
JVM attempts to match it with the catch blocks in order. The
first catch block whose exception type matches or is a superclass of
the thrown exception will be executed.
Since Java 7, it is possible to catch multiple exception types in a
single catch block using the | (pipe) symbol, which can reduce code
repetition.
Java
try {
// Code that may throw exceptions
} catch (ArithmeticException e) {
// Handle ArithmeticException
} catch (ArrayIndexOutOfBoundsException e) {
// Handle ArrayIndexOutOfBoundsException
} catch (IOException | SQLException e) { // Catching multiple
exceptions in one block
// Handle IOException or SQLException
}
Class Throwable
The [Link] class is the superclass of all errors and
exceptions in Java. Only objects that are instances of Throwable or its
subclasses can be thrown by the Java Virtual Machine (JVM) or
explicitly thrown using the throw keyword. Throwable has two direct
subclasses: Error and Exception.
Unchecked Exceptions
Unchecked exceptions are subclasses of RuntimeException. They are
not required to be handled or declared in the method signature
using throws. The Java compiler does not enforce their
handling. Unchecked exceptions typically indicate programming
errors or logical flaws, such
as NullPointerException, ArrayIndexOutOfBoundsException,
or ArithmeticException. While not mandatory, it is good practice to
prevent them through proper code design and validation.
Checked Exceptions
Checked exceptions are subclasses of Exception but
not RuntimeException. These exceptions are checked at compile
time, and the Java compiler mandates their handling. If a method
might throw a checked exception, it must either handle the exception
using a try-catch block or declare that it throws the exception using
the throws keyword in the method signature. Examples
include IOException, SQLException,
and FileNotFoundException. Checked exceptions represent external
problems that the program can anticipate and potentially recover
from.
Java I/O and File:
I/O Streams in Java:
Streams are sequences of data. In Java, they represent either a
source of input or a destination for output.
Types of Streams:
Byte Streams:
Handle raw binary data, reading and writing 8-bit bytes.
Primarily used for non-textual data like images, audio, or
executable files.
Abstract base classes: InputStream (for reading)
and OutputStream (for writing).
Examples: FileInputStream, FileOutputStream, BufferedIn
putStream, BufferedOutputStream.
Character Streams:
Handle character data, automatically managing character
encoding and decoding (e.g., Unicode).
Primarily used for textual data like text files.
Abstract base classes: Reader (for reading) and Writer (for
writing).
Examples: FileReader, FileWriter, BufferedReader, Buffered
Writer.
Standard I/O Streams:
Java provides three pre-defined standard streams, accessible via
the System class:
[Link]: Standard input stream, typically connected to the
keyboard. It's an InputStream.
[Link]: Standard output stream, typically connected to the
console. It's a PrintStream (a type of OutputStream).
[Link]: Standard error stream, typically connected to the
console for error messages. It's also a PrintStream.
Scanner Class:
The Scanner class (from [Link]) is a versatile utility for parsing
primitive types and strings from various input sources,
including [Link], files, and strings. It simplifies reading formatted
input compared to working directly
with InputStream or Reader classes for basic data types.
Files in Java:
The [Link] class represents a file or directory path in the file
system. It does not handle the actual reading or writing of data but
provides methods for:
Creating, deleting, and renaming files and directories.
Checking file existence, permissions, and last modification time.
Listing directory contents.
To read from or write to a file, you would typically use
a FileInputStream/FileOutputStream (for byte data)
or FileReader/FileWriter (for character data) in conjunction with
a File object or a file path string.
Choosing a Stream Type:
Byte Streams:
Use when dealing with binary data (e.g., images, audio, serialized
objects).
Character Streams:
Use when dealing with human-readable text data, as they handle
character encoding correctly.
Unit V:
String Handling in Java:
Introduction to Strings in Java
In Java, strings are sequences of characters that are widely used in
programming. Unlike primitive data types, strings in Java are
objects. The Java platform provides the String class to facilitate the
creation and manipulation of these string objects.
The CharSequence Interface
The CharSequence interface represents a readable sequence
of char values. It provides a uniform, read-only access to various
kinds of character sequences. This interface defines fundamental
methods such as length(), charAt(int index), subSequence(int start,
int end), and toString(). The String class, along
with StringBuffer and StringBuilder, implements
the CharSequence interface.
The String Class
The String class in Java is a fundamental class for representing and
manipulating immutable sequences of characters. "Immutable"
means that once a String object is created, its value cannot be
changed. Any operation that appears to modify a String actually
results in the creation of a new String object.
Methods for Extracting Characters from a String
Several methods are available in the String class for extracting
individual characters or character sequences: charAt(int index).
This method returns the char value at the specified index. The index
is zero-based, meaning the first character is at index 0.
Java
String str = "Hello";
char ch = [Link](1); // ch will be 'e'
toCharArray().
This method converts the String into a new char array, allowing
access to individual characters via array indexing.
Java
String str = "World";
char[] charArray = [Link]();
char firstChar = charArray[0]; // firstChar will be 'W'
getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin):
This method copies characters from a specified range within
the String into a destination char array.
Java
String str = "Example";
char[] buffer = new char[3];
[Link](1, 4, buffer, 0); // buffer will contain {'x', 'a', 'm'}
codePointAt(int index).
This method returns the Unicode code point value of the character at
the specified index. This is particularly useful for handling characters
outside the Basic Multilingual Plane (BMP).
Java
String str = "Java";
int codePoint = [Link](0); // codePoint will be the Unicode
value of 'J'
Comparison:
While StringBuffer does not directly override the equals() method for
content comparison like String, you can compare StringBuffer objects
by converting them to String first using toString() and then
using String's equals() or compareTo() methods.
Java
StringBuffer sb1 = new StringBuffer("hello");
StringBuffer sb2 = new StringBuffer("hello");
// For content comparison
if ([Link]().equals([Link]())) {
[Link]("Content is equal");
}
Modifying:
StringBuffer is designed for modification. Key modification methods
include:
append(String str): Appends the specified string to the end of
the StringBuffer.
insert(int offset, String str): Inserts the specified string at the
given offset.
delete(int start, int end): Deletes characters from the specified
start index (inclusive) to the end index (exclusive).
replace(int start, int end, String str): Replaces characters in the
specified range with the given string.
setCharAt(int index, char ch): Sets the character at the specified
index to a new character.
reverse(): Reverses the sequence of characters in
the StringBuffer.
Java
StringBuffer sb = new StringBuffer("Java");
[Link](" Programming"); // "Java Programming"
[Link](4, " is fun"); // "Java is fun Programming"
[Link](0, 4); // " is fun Programming"
[Link](0, 7, "Coding"); // "Coding Programming"
[Link](0, 'C'); // "Coding Programming" (already 'C')
[Link](); // "gnimmargorP gnidoC"
Searching:
StringBuffer provides methods for searching for characters or
substrings:
indexOf(String str): Returns the index within this string of the
first occurrence of the specified substring.
lastIndexOf(String str): Returns the index within this string of
the last occurrence of the specified substring.
charAt(int index): Returns the character at the specified index.
Java
StringBuffer sb = new StringBuffer("Hello World");
int index = [Link]("World"); // Returns 6
char ch = [Link](1); // Returns 'e'
Note: StringBuffer is thread-safe due to its synchronized methods,
making it suitable for multi-threaded environments where multiple
threads might access and modify the same StringBuffer instance. For
single-threaded scenarios, StringBuilder offers better performance as
it lacks synchronization overhead.
Multithreaded Programming:
Introduction to Multithreaded Programming in Java
Multithreading in Java is a feature that allows the concurrent
execution of multiple parts of a program, known as threads, within a
single process. Each thread represents an independent path of
execution, sharing the same memory space and resources of the
parent process. This approach enables applications to perform
multiple tasks simultaneously, enhancing responsiveness, efficiency,
and overall performance.
Need for Multiple Threads (Multithreaded Programming for Multi-
core Processors)
The necessity of multithreading, particularly for multi-core
processors, arises from several key factors:
Leveraging Multi-core Architectures:
Modern computers commonly feature multi-core processors,
meaning they have multiple independent processing units (cores)
that can execute instructions simultaneously. Multithreading allows
applications to distribute tasks across these available cores, achieving
true parallelism and significantly improving performance for
computationally intensive operations. Without multithreading, a
single-threaded application would only utilize one core, leaving the
others idle.
Improved Responsiveness:
In applications with graphical user interfaces (GUIs) or those
requiring background processing, multithreading prevents the main
thread from becoming blocked by long-running tasks. For example, a
GUI application can remain responsive to user input while a separate
thread handles a time-consuming file download or data processing
operation.
Efficient Resource Utilization:
When one thread is waiting for an external resource (e.g., network
I/O, database access), other threads can utilize the CPU to perform
different computations, preventing the CPU from sitting idle. This
maximizes the utilization of available system resources.
Enhanced Scalability:
Multithreaded applications can more easily scale to handle increasing
workloads. By distributing tasks among multiple threads, the
application can effectively utilize additional processing power as
needed, such as when handling numerous concurrent client requests
in a server application.
Simplified Program Design for Concurrency:
For certain problem domains, such as simulations or parallel
algorithms, structuring the solution using multiple threads can lead
to a more natural and intuitive program design, reflecting the
inherent concurrency of the problem itself.
In essence, multithreaded programming in Java, especially when
targeting multi-core processors, is a fundamental technique for
building high-performance, responsive, and scalable applications that
fully utilize modern hardware capabilities.
1. The Thread Class:
The [Link] class is the fundamental building block for
creating and managing threads in Java. It provides constructors and
methods to create, control, and interact with threads.
2. Main Thread and Creation of New Threads:
Main Thread:
When a Java Virtual Machine (JVM) starts, it creates a special thread
known as the main thread. This thread is responsible for executing
the main() method of your application.
Creating New Threads:
New threads can be created in Java using two primary methods:
Extending the Thread Class: Create a class that
extends Thread and overrides its run() method. The code
to be executed by the new thread is placed within
the run() method.
Implementing the Runnable Interface: Create a class that
implements the Runnable interface and overrides
its run() method. An instance of this Runnable class is
then passed to the constructor of a Thread object. This
approach is generally preferred as it allows for greater
flexibility (e.g., a class can implement multiple interfaces
but only extend one class).
Starting a Thread:
After creating a Thread object, call its start() method. This method
allocates system resources for the new thread and calls
the run() method in a separate execution flow.
3. Thread States:
A thread in Java can exist in various states during its lifecycle:
NEW: A thread that has been created but not yet started.
RUNNABLE: A thread that is either currently executing or ready
to execute and waiting for the CPU.
BLOCKED: A thread that is temporarily inactive because it is
waiting for a monitor lock to enter a synchronized
block/method.
WAITING: A thread that is indefinitely waiting for another
thread to perform a particular action (e.g., using [Link]()).
TIMED_WAITING: A thread that is waiting for a specified period
of time (e.g., using [Link]() or [Link](long
timeout)).
TERMINATED: A thread that has completed its execution or has
been terminated.
4. Thread Priority:
Each thread in Java has a priority, an integer value ranging
from Thread.MIN_PRIORITY (1) to Thread.MAX_PRIORITY (10),
with Thread.NORM_PRIORITY (5) as the default.
The thread scheduler uses priorities as a hint to determine
which thread should be given preference for execution. Higher-
priority threads are generally executed before lower-priority
threads, though this is not guaranteed and depends on the
operating system's scheduling policies.
Priorities can be set using setPriority() and retrieved
using getPriority().
5. Synchronization:
Synchronization in Java is used to control access to shared
resources by multiple threads, preventing data inconsistency
and race conditions.
synchronized Keyword: The primary mechanism for
synchronization in Java is the synchronized keyword, which can
be applied to methods or blocks of code.
o Synchronized Methods: When a method is
declared synchronized, only one thread can execute that
method on a given object at a time. The thread acquires a
lock on the object before entering the method and
releases it upon exiting.
o Synchronized Blocks: A synchronized block allows for
more fine-grained control, synchronizing only a specific
section of code on a given object or class.
Mutual Exclusion: Synchronization ensures mutual exclusion,
meaning only one thread can access a critical section of code at
any given time, thereby protecting shared data from concurrent
modification.
Deadlock and race conditions
Deadlock
A deadlock occurs when two or more threads are blocked forever,
each waiting for a resource held by the other. A classic example is
when Thread 1 holds a lock on resource A and waits for a lock on
resource B, while Thread 2 holds the lock on resource B and waits for
the lock on resource A.
Four necessary conditions for a deadlock:
Mutual exclusion: Resources are non-shareable and can be
used by only one thread at a time.
Hold and wait: A thread is holding at least one resource and is
waiting to acquire additional resources held by other threads.
No preemption: Resources cannot be forcibly taken away from
a thread; they must be voluntarily released.
Circular wait: A circular chain of threads exists, where each
thread waits for a resource held by the next thread in the
chain.
Deadlock avoidance strategies:
Avoid nested locks: Do not acquire multiple locks on different
resources within the same code block if possible.
Consistent lock ordering: Ensure all threads acquire the locks
on multiple resources in the same predefined order.
Use [Link] utilities: The modern concurrency API
offers higher-level synchronization constructs that are less
prone to deadlocks.
Race condition
A race condition occurs when two or more threads access shared
data concurrently, and the final outcome depends on the
unpredictable timing and interleaving of their execution. Without
proper synchronization, this can lead to data corruption and
inconsistent results.
Race condition example:
Consider a shared integer counter. If two threads increment the
counter simultaneously without synchronization, the final count may
be incorrect.
Thread 1 reads the value (e.g., 0).
Thread 2 reads the value (e.g., 0).
Thread 1 increments its local value and writes it back (0+1=1).
Thread 2 increments its local value and writes it back (0+1=1).
The expected final value should be 2, but due to the race
condition, it becomes 1.
Avoiding race conditions:
Synchronization: Use the synchronized keyword to protect
shared data. This ensures that only one thread can access a
synchronized method or block at a time.
Atomic classes: Use atomic variables from
the [Link] package for single variable
updates, such as AtomicInteger.
Thread-safe data structures: Use thread-safe collections
like ConcurrentHashMap instead of their non-synchronized
counterparts.
Inter-thread communication
Inter-thread communication allows multiple threads to coordinate
their actions and cooperate in solving a task. The primary mechanism
for this is the wait(), notify(), and notifyAll() methods, which are part
of the Object class. These methods must be called from within
a synchronized block.
wait(): Causes the current thread to pause execution and
release the lock on the object it is synchronized on. The thread
will wait until another thread calls notify() or notifyAll() for the
same object.
notify(): Wakes up a single thread that is waiting on the object's
monitor.
notifyAll(): Wakes up all threads that are waiting on the object's
monitor.
Example: The producer-consumer problem
This classic example demonstrates inter-thread communication.
A producer thread adds items to a shared queue.
A consumer thread removes items from the queue.
They coordinate to prevent the producer from adding to a full
queue and the consumer from taking from an empty one.
java
class MessageQueue {
private String message;
private boolean hasMessage = false;
public synchronized void put(String msg) {
while (hasMessage) {
try {
wait(); // Wait for consumer to take the message
} catch (InterruptedException e) {}
}
[Link] = msg;
[Link] = true;
notifyAll(); // Notify waiting consumers
}
public synchronized String take() {
while (!hasMessage) {
try {
wait(); // Wait for producer to put a message
} catch (InterruptedException e) {}
}
hasMessage = false;
notifyAll(); // Notify waiting producers
return message;
}
}
Suspending, resuming, and stopping threads
The original [Link](), [Link](),
and [Link]() methods are deprecated and should not be used.
suspend() and resume(): Deprecated because they are highly
prone to deadlocks. If a thread is suspended while holding a
lock on a critical resource, other threads can become
permanently blocked.
stop(): Deprecated because it is unsafe. It forces a thread to
immediately unlock all its synchronized resources, potentially
leaving shared data in an inconsistent state.
Modern approach using a flag
The recommended way to control a thread's execution is to use a
volatile boolean flag to signal the thread to suspend or terminate
itself gracefully.
java
class ControllableThread extends Thread {
private volatile boolean suspended = false;
private volatile boolean stopped = false;
public void run() {
while (!stopped) {
synchronized (this) {
while (suspended) {
try {
wait();
} catch (InterruptedException e) {}
}
}
// Thread's main task
[Link]("Thread is running...");
try {
[Link](1000);
} catch (InterruptedException e) {}
}
}
public synchronized void mySuspend() {
suspended = true;
}
public synchronized void myResume() {
suspended = false;
notify(); // Or notifyAll()
}
public void myStop() {
stopped = true;
myResume(); // In case the thread is suspended
}
}
Java Database Connectivity:
Introduction to JDBC
JDBC (Java Database Connectivity) is a Java API that provides a
standard way for Java applications to interact with relational
databases. It defines a set of interfaces and classes for connecting to
databases, executing SQL queries, and processing results. JDBC acts
as a bridge between Java applications and various database
management systems (DBMS), allowing for database-agnostic code.
JDBC Architecture
The JDBC architecture consists of four main components:
Application:
The Java program that uses the JDBC API to interact with a database.
JDBC API:
The set of interfaces and classes in the [Link] and [Link] packages
that provide methods for database connectivity.
DriverManager:
A class that manages a set of JDBC drivers and establishes
connections between the application and the appropriate driver.
JDBC Drivers:
Vendor-specific software components that implement the JDBC API
for a particular database. For instance, the MySQL Connector/J is the
JDBC driver for MySQL.
Installing MySQL and MySQL Connector/J
Installing MySQL:
Download and install MySQL Server from the official MySQL website
according to your operating system's instructions.
Installing MySQL Connector/J:
Navigate to the official MySQL website and locate the
"Downloads" section.
Find "Connector/J" under "MySQL Connectors."
Download the platform-independent ZIP archive or the
appropriate installer for your system.
Extract the contents of the ZIP file. The mysql-connector-
[Link] file is the JDBC driver.
JDBC Environment Setup
To use JDBC in your Java project:
Add the JDBC Driver to your Project's Classpath:
o IDE (e.g., IntelliJ IDEA, Eclipse): Add the mysql-connector-
[Link] file as a dependency or library to your
project's build path.
o Manual Compilation/Execution: Include the JAR file in the
classpath when compiling and running your Java
application using the -cp or -classpath flag.
Code
javac -cp ".;path/to/[Link]" [Link]
java -cp ".;path/to/[Link]" YourApp
(Replace . with : for Linux/macOS)
Establishing JDBC Database Connections
The steps to establish a JDBC connection are:
Load/Register the Driver: Although often implicitly handled in
modern JDBC drivers, you might explicitly load the driver class.
Java
[Link]("[Link]");
Establish the
Connection: Use [Link]() to obtain
a Connection object.
Java
String url = "jdbc:mysql://localhost:3306/your_database_name";
String user = "your_username";
String password = "your_password";
Connection connection = [Link](url, user,
password);
ResultSet Interface in Java
The ResultSet interface represents a table of data generated by
executing a SQL query. It provides methods to navigate through the
rows and retrieve data from columns.
Navigation:
Methods like next(), previous(), first(), last(), absolute(int
row), relative(int rows) allow moving the cursor within the result set.
Data Retrieval:
getXXX() methods (e.g., getString(String columnName), getInt(int
columnIndex)) retrieve data from specific columns.
Closing:
It is crucial to close the ResultSet using close() when no longer
needed to release resources.
Example of using ResultSet:
Java
Statement statement = [Link]();
ResultSet resultSet = [Link]("SELECT id, name
FROM users");
while ([Link]()) {
int id = [Link]("id");
String name = [Link]("name");
[Link]("ID: " + id + ", Name: " + name);
}
[Link]();
[Link]();
JDBC is an API that helps applications to communicate with
databases. It allows Java programs to connect to a database, run
queries, retrieve and manipulate data. Because of JDBC, Java
applications can easily work with different relational databases like
MySQL, Oracle, PostgreSQL and more.
JDBC Architecture
Components of JDBC
1. Application: It can be a Java application or servlet that
communicates with a data source.
2. JDBC API: It allows Java programs to execute SQL queries and get
results from the database. Some key components of JDBC API include
Interfaces like Driver, ResultSet, RowSet, PreparedStatement
and Connection that helps managing different database tasks.
Classes like DriverManager, Types, Blob and Clob that helps
managing database connections.
3. DriverManager: It plays an important role in the JDBC architecture.
It uses some database-specific drivers to effectively connect
enterprise applications to databases.
4. JDBC drivers: These drivers handle interactions between the
application and the database.
JDBC Processing Models
The JDBC architecture consists of two-tier and three-tier processing
models to access a database. They are as described below:
1. Two-Tier Architecture
A Java Application communicates directly with the database using a
JDBC driver. It sends queries to the database and then the result is
sent back to the application. For example, in a client/server setup,
the user's system acts as a client that communicates with a remote
database server.
Structure:
Client Application (Java) -> JDBC Driver -> Database
2. Three-Tier Architecture
In this, user queries are sent to a middle-tier services, which interacts
with the database. The database results are processed by the middle
tier and then sent back to the user.
Structure:
Client Application -> Application Server -> JDBC Driver -> Database
JDBC Drivers
JDBC drivers are client-side adapters (installed on the client machine,
not on the server) that convert requests from Java programs to a
protocol that the DBMS can understand. There are 4 types of JDBC
drivers:
1. Type-1 driver or JDBC-ODBC bridge driver
2. Type-2 driver or Native-API driver (partially java driver)
3. Type-3 driver or Network Protocol driver (fully java driver)
4. Type-4 driver or Thin driver (fully java driver) - It is a widely
used driver. The older drivers like (JDBC-ODBC) bridge driver
have been deprecated and no longer supported in modern
versions of Java.
JDBC Classes and Interfaces
Class/Interfaces Description
Manages JDBC drivers and establishes
DriverManager
database connections.
Represents a session with a specific
Connection
database.
Statement Used to execute static SQL queries.
Precompiled SQL statement, used for
PreparedStatement
dynamic queries with parameters.
Used to execute stored procedures in the
CallableStatement
database.
Represents the result set of a query,
ResultSet
allowing navigation through the rows.
Handles SQL-related exceptions during
SQLException
database operations.
Java FX GUI:
1. JavaFX Scene Builder:
Scene Builder is a visual tool for designing JavaFX user interfaces. It
allows for drag-and-drop creation of UI elements (controls, layouts,
shapes, etc.) and generates FXML, an XML-based markup language
that defines the UI structure separately from the application
logic. This enables a clear separation of concerns and facilitates rapid
prototyping.
2. JavaFX Application Window Structure:
A JavaFX application typically consists of:
Stage:
The top-level container, representing the application window. It's
managed by the operating system.
Scene:
The content area within the Stage. A Stage can hold only one Scene
at a time.
Scene Graph (Nodes):
The hierarchical tree structure within a Scene that contains all the
visual elements (nodes) of your application, such as buttons, text,
images, and layout containers.
3. Displaying Text and Image:
Text:
Use the Text class to display textual content. You can set properties
like font, color, and position.
Image:
Use the ImageView class to display images. It takes an Image object
(loaded from a file or URL) as its source.
4. Event Handling:
JavaFX employs an event-driven model.
Events:
User interactions (e.g., button clicks, mouse movements, key presses)
or system events trigger events.
Event Handlers:
Objects that respond to specific events. You register event handlers
with nodes or the scene using methods like addEventHandler() or by
setting properties like onAction for controls.
Event Types:
Specific event types (e.g., MouseEvent, ActionEvent, KeyEvent) are
used to categorize events.
5. Laying Out Nodes in Scene Graph:
JavaFX provides various layout panes to arrange nodes within the
scene graph:
HBox/VBox: Arrange nodes horizontally or vertically.
BorderPane: Divides the layout into five regions: top, bottom,
left, right, and center.
GridPane: Arranges nodes in a grid-like structure.
StackPane: Stacks nodes on top of each other.
AnchorPane: Allows anchoring nodes to the edges of the pane.
6. Mouse Events:
Mouse events are a specific type of event handled in JavaFX.
Types:
Common mouse events
include MouseEvent.MOUSE_CLICKED, MouseEvent.MOUSE_PRESSE
D, MouseEvent.MOUSE_RELEASED, MouseEvent.MOUSE_MOVED, M
ouseEvent.MOUSE_DRAGGED, MouseEvent.MOUSE_ENTERED,
and MouseEvent.MOUSE_EXITED.
Handling:
You can attach event handlers to nodes to respond to these events,
for example, to change a node's appearance on hover or to enable
drag-and-drop functionality.