0% found this document useful (0 votes)
5 views50 pages

OOPS With Java Exam Help

This comprehensive Java OOP exam study guide covers essential topics including Java architecture (JVM, JRE, JDK), classes and objects, and constructors, providing detailed explanations and practical code examples. It emphasizes the importance of understanding these concepts for effective Java programming and exam preparation. The guide aims to facilitate rapid comprehension of complex topics through relatable analogies and structured content tailored for exam success.

Uploaded by

gurkeeratbambra
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
5 views50 pages

OOPS With Java Exam Help

This comprehensive Java OOP exam study guide covers essential topics including Java architecture (JVM, JRE, JDK), classes and objects, and constructors, providing detailed explanations and practical code examples. It emphasizes the importance of understanding these concepts for effective Java programming and exam preparation. The guide aims to facilitate rapid comprehension of complex topics through relatable analogies and structured content tailored for exam success.

Uploaded by

gurkeeratbambra
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 50

Comprehensive Java OOP Exam Study

Guide: Your Last-Minute Companion


This study guide is meticulously crafted to serve as an essential companion for an upcoming
Java OOP exam. Each topic from the syllabus is explained in detail, accompanied by practical
code examples and relatable real-world analogies, designed to facilitate rapid and effective
comprehension of complex concepts. The explanations are tailored to provide sufficient depth
for 8-mark questions, ensuring thorough preparation for the exam's structure.

Unit I: Introduction & Fundamentals of Java


1. Java Architecture: JVM, JRE, JDK
Java's "Write Once, Run Anywhere" (WORA) capability is a cornerstone of its popularity, directly
stemming from its unique architecture comprising the Java Virtual Machine (JVM), Java
Runtime Environment (JRE), and Java Development Kit (JDK). Understanding their distinct
roles and interdependencies is fundamental for any Java developer.
●​ JVM (Java Virtual Machine): The Execution Engine The JVM is an abstract machine
that provides a runtime environment for Java bytecode. It is responsible for converting
platform-independent Java bytecode (.class files) into machine-specific native code,
enabling Java programs to run on any platform equipped with a compatible JVM. Beyond
execution, the JVM handles critical functions such as memory management, garbage
collection, and security. While Java bytecode exhibits platform independence, the JVM
itself is inherently platform-dependent, requiring specific implementations for different
operating systems.The JVM's internal structure includes several key components. The
Class Loader is responsible for loading compiled Java program class files into memory
during execution and verifying their correctness. The Bytecode Verifier ensures that the
loaded bytecode adheres to Java language's security constraints, safeguarding the
system from malicious code. The Execution Engine, the core of the JVM, executes the
bytecode either by interpreting it line by line or by using Just-In-Time (JIT) compilation
to convert frequently executed bytecode into native machine code for faster execution.
This JIT compilation is a crucial performance optimization. Initially, Java's interpreted
nature was perceived as slow; however, JIT compilation addresses this by compiling "hot
spots" of bytecode into native machine code during runtime and caching them,
significantly boosting performance and bridging the gap between interpreted and
compiled languages. This internal mechanism is vital for Java's efficiency.A relatable
analogy for the JVM is a coffee machine in a coffee shop business. It takes raw
ingredients (bytecode) and processes them to produce the final coffee (executed
program). The machine operates regardless of where the coffee beans (bytecode)
originate, as long as they meet the required specifications, much like Java programs are
portable across different computing platforms. The JVM's primary significance lies in
ensuring program portability across diverse platforms by abstracting the underlying
operating system and translating bytecode into machine-specific instructions. This
enables a Java program compiled on one operating system to run seamlessly on another
without modification. Furthermore, the JVM's roles in automatic memory management
(garbage collection) prevent common programming errors like memory leaks, simplifying
development. Bytecode verification is a crucial security feature, preventing malicious code
from running by ensuring adherence to Java's safety rules. These features are not merely
conveniences but fundamental aspects of Java's robustness and secure execution
environment, critical for enterprise-level applications.Code Example (Conceptual - JVM
executes bytecode, not direct source):​
// HelloWorld.java (Source Code)​
public class HelloWorld {​
public static void main(String args) {​
System.out.println("Hello, JVM!");​
}​
}​
// After compilation (javac HelloWorld.java), HelloWorld.class
(bytecode) is generated.​
// The JVM then executes this bytecode.​
// Command to run: java HelloWorld​

●​ JRE (Java Runtime Environment): The Execution Platform The JRE is a software
package that provides the necessary environment to run Java applications. It bundles the
JVM along with essential class libraries (such as rt.jar) and supporting files required for
program execution. Unlike the JDK, the JRE does not include development tools like
compilers or debuggers, making it suitable for end-users who only need to run Java
programs without developing them.Extending the coffee shop analogy, if the JVM is the
coffee machine, the JRE represents the entire setup of the coffee shop. This
encompasses the coffee machine (JVM), along with cups, tables, and baristas (libraries
and runtime components). It is everything required to serve coffee to customers (users).
The JRE's significance lies in extending the JVM's functionality by providing a complete
runtime environment with essential libraries and resources, thereby ensuring the usability
of Java applications.
●​ JDK (Java Development Kit): The Development Suite The JDK is a comprehensive
software development kit that provides all the tools and libraries required for developing,
compiling, and debugging Java applications. It is a superset of the JRE, meaning it
includes the JRE along with additional development tools such as the Java compiler
(javac), debugger (jdb), and other utilities like jar (for packaging Java applications) and
javadoc (for generating documentation). The JDK, like the JRE and JVM, is
platform-specific, requiring separate installers for different operating systems.In the coffee
shop analogy, the JDK is the warehouse and workshop of the business. This is where
coffee beans (source code) are stored, roasters (compiler) are used, packaging materials
(debugger, tools) are kept, and even an extra coffee machine for testing (JRE) is
available. It is the place where the "magic of creating new coffee blends (Java programs)
happens". The JDK's primary significance is its role in empowering developer productivity
by providing the full toolkit necessary to develop and test Java programs from scratch. It
is an indispensable tool for anyone who intends to write, compile, and execute Java
code.The hierarchical relationship where the JDK includes the JRE, and the JRE includes
the JVM, signifies a layered abstraction model. This architecture is central to Java's core
strength: WORA. The JVM provides the lowest level of abstraction, abstracting the
underlying hardware. The JRE builds upon this by adding core libraries, abstracting
common system functionalities. The JDK then adds development tools, abstracting the
compilation and debugging process. This layering is a fundamental design principle in
software engineering, enabling Java to be highly portable and efficient for both
development and deployment.
●​ Table: Comparison of JDK, JRE, and JVM | Aspect | JDK (Java Development Kit) | JRE
(Java Runtime Environment) | JVM (Java Virtual Machine) | | :-------------------- |
:---------------------------------------------------------------------------------------- |
:-------------------------------------------------------------------------------------------- |
:-------------------------------------------------------------------------------------------- | | Purpose |
Develop Java applications (write, compile, debug) | Run Java applications | Execute Java
bytecode | | Includes | JRE + Development Tools (javac, jdb, jar, javadoc) | JVM + Class
Libraries (rt.jar) + Supporting Files | ClassLoader, Bytecode Verifier, Execution Engine
(Interpreter + JIT), Garbage Collector | | Platform Dependency | Platform-dependent
(OS-specific installers) | Platform-dependent (OS-specific) | Platform-dependent, but
bytecode is platform-independent | | Use Case | Java developers (writing, compiling,
debugging code) | End-users (running Java applications) | Core engine for JRE/JDK
(automatically included, not installed separately) | | Analogy | Warehouse and workshop
(development + runtime) | Entire coffee shop setup (runtime environment) | Coffee
machine (execution engine) |

2. Classes and Objects


Classes and objects are the foundational building blocks of Object-Oriented Programming
(OOP) in Java, enabling developers to model real-world entities and their interactions within a
software system.
●​ Classes: The Blueprints A class is a blueprint or a template for creating objects. It
defines a new data type that encapsulates fields (variables or attributes) to hold state and
methods (functions or behaviors) to perform actions. Classes are logical entities and do
not occupy memory when declared.The general syntax for defining a class is as follows:​
class ClassName {​
// Fields (State/Properties)​
dataType fieldName;​

// Constructors (Special methods for object initialization)​
ClassName(parameters) {​
// Initialize fields​
}​

// Methods (Behavior/Actions)​
returnType methodName(parameters) {​
// Method body​
}​
}​
A common analogy for a class is the blueprint for a house. This blueprint meticulously
details the design—including the number of walls, windows, doors, rooms, and the type of
roof—but it is not a physical house that can be inhabited.
●​ Objects: The Instances An object is a concrete instance of a class. It represents a
specific entity with its own defined state (the unique values of its fields) and behavior (the
actions performed by its methods). Objects are created using the new keyword, which
triggers memory allocation on the heap for the object's data. Each object possesses a
unique identity, allowing it to interact distinctly within the program.The syntax for creating
an object is:​
ClassName objectName = new ClassName(arguments);​
Following the house analogy, an object is the actual house built from that blueprint.
Multiple houses (objects) can be constructed from the same blueprint (class), with each
house possessing its own specific details, such as different paint colors or unique furniture
arrangements, while adhering to the same underlying design.
●​ Code Example: Class and Object​
class Car {​
// Fields (State)​
String color;​
int year;​

// Constructor​
Car(String color, int year) {​
this.color = color; // 'this' refers to the current
object's 'color' field​
this.year = year; // 'this' refers to the current
object's 'year' field​
}​

// Method (Behavior)​
void displayInfo() {​
System.out.println("Car color: " + color + ", Year: " +
year);​
}​
}​

public class Main {​
public static void main(String args) {​
// Creating objects (instances) of the Car class​
Car myCar = new Car("Red", 2020); // Object 1: A red car
from 2020​
Car yourCar = new Car("Blue", 2022); // Object 2: A blue
car from 2022​

// Accessing object methods​
myCar.displayInfo(); // Output: Car color: Red, Year:
2020​
yourCar.displayInfo(); // Output: Car color: Blue, Year:
2022​
}​
}​
The use of classes and objects offers several significant advantages in software
development. They promote modularity, enabling the breakdown of complex problems
into smaller, manageable units. This modularity facilitates reusability, as multiple objects
can be created from a single class, reducing redundant code. Encapsulation, a core
OOP principle, is achieved by bundling data (fields) and the methods that operate on that
data within a single unit, thereby hiding implementation details and protecting an object's
internal state from unauthorized external access. This leads to more robust code, as
changes to internal implementation details do not necessarily affect external code that
interacts with the object through its public interface, contributing to better maintainability
and reduced debugging effort in large-scale software development.The distinction
between a class (a logical entity that consumes no memory) and an object (a physical
entity that allocates memory on the heap) is crucial. When a class is declared, it is merely
a definition; memory is consumed only when an object, an instance of that class, is
created using the new operator. This dynamic memory allocation on the heap is managed
by the JVM's garbage collector, which automatically reclaims memory from objects that
are no longer referenced. This contrasts with languages requiring manual memory
deallocation, reducing memory-related bugs and simplifying development. Understanding
this object lifecycle is key to optimizing Java application performance and avoiding
memory leaks.The this keyword, used in constructors and methods (e.g., this.color =
color;), serves as a self-reference to the current object. Its purpose is to resolve ambiguity
when instance variable names and local variable (or parameter) names are identical,
ensuring that the constructor or method correctly initializes or manipulates the object's
own properties rather than creating new local variables. This is a subtle but important
detail for writing correct and unambiguous object-oriented code.

3. Constructors
Constructors are special methods in Java used for initializing new objects. They are
automatically invoked when an instance of a class is created using the new keyword.
●​ Key Characteristics of Constructors:
○​ Same Name as Class: A constructor must bear the exact same name as its class.
○​ No Return Type: Unlike regular methods, constructors do not have a return type,
not even void. If a return type is explicitly added, the construct is no longer
considered a constructor but a regular method.
○​ Implicit Return: They implicitly return an instance of the class upon successful
initialization.
○​ Cannot be Static, Volatile, or Final: Constructors cannot be declared with these
modifiers.
●​ Significance of Constructors: Constructors are crucial in Java programming for several
reasons. They ensure automatic object initialization, guaranteeing that an object
begins its existence in a valid and consistent state by setting initial values for its
properties. This prevents objects from being in an undefined or unusable state. They also
promote code reusability by centralizing the initialization logic, thereby reducing
redundant code throughout an application. In advanced frameworks, constructors are
utilized to enforce dependency injection and object immutability, contributing to better
encapsulation and robust design. The primary purpose of a constructor is not merely to
assign values, but to ensure that an object is created in a valid and consistent state. By
requiring necessary parameters in parameterized constructors, a class can enforce that
certain properties are always initialized, preventing the creation of "half-baked" or invalid
objects. This is crucial for application stability and preventing NullPointerException later in
the program's execution.
●​ Types of Constructors:
1.​ Default Constructor:
■​ Explanation: If a developer does not explicitly define any constructor within a
class, Java automatically provides a public, no-argument constructor. Its sole
purpose is to initialize the object. In this scenario, fields are initialized to their
default values (e.g., reference types to null, primitive number types to 0, and
primitive boolean values to false).
■​ Real-world Analogy: A basic construction crew that builds a standard
house without any specific instructions. They simply follow the default
blueprint, resulting in a generic, uncustomized structure.
■​ Code Example:​
public class House {​
String type; // Default: null​
int rooms; // Default: 0​

// Java provides a default constructor here: public
House() {}​
}​

public class Main {​
public static void main(String args) {​
House myHouse = new House(); // Calls the default
constructor​
System.out.println("Default house type: " +
myHouse.type); // Output: null​
System.out.println("Default house rooms: " +
myHouse.rooms); // Output: 0​
}​
}​

2.​ No-Args Constructor (User-Defined):


■​ Explanation: This is a constructor that a developer explicitly defines, and it
takes no arguments. If any constructor (even a user-defined no-args one) is
defined in a class, Java will not automatically provide its default constructor.
■​ Real-world Analogy: This is akin to hiring a construction crew and
instructing them, "Build a standard house, but before commencing, ensure
the foundation is perfectly level and all necessary permits are secured." This
signifies specific pre-initialization tasks, even if the resulting house is
standard.
■​ Code Example:​
public class House {​
String type;​
int rooms;​

// User-defined no-args constructor​
public House() {​
this.type = "Standard";​
this.rooms = 3;​
System.out.println("No-args constructor called:
Standard house created.");​
}​
}​

public class Main {​
public static void main(String args) {​
House myHouse = new House(); // Calls our
user-defined no-args constructor​
System.out.println("House type: " +
myHouse.type); // Output: Standard​
}​
}​

3.​ Parameterized Constructor:


■​ Explanation: This type of constructor accepts one or more arguments
(parameters). These arguments are utilized to initialize the object's properties
with specific values provided at the time of object creation. A critical rule is
that if parameterized constructors are defined and no no-args constructor is
explicitly provided by the developer, Java will not automatically generate the
default no-args constructor. This forces callers to provide the necessary data,
ensuring the object's validity from creation. However, it also means that if a
no-args constructor is still desired (e.g., for frameworks like Hibernate that
use reflection to instantiate objects, or for JavaBeans), it must be explicitly
added. This is a common point of confusion for new Java developers and
highlights the interplay between language features and design patterns.
■​ Real-world Analogy: This is comparable to hiring a construction crew and
providing them with detailed instructions: "Build a house with 4 bedrooms, 3
bathrooms, and a red roof". The parameters provided customize the house
during its construction.
■​ Code Example:​
public class House {​
String type;​
int rooms;​

// Parameterized constructor​
public House(String type, int rooms) {​
this.type = type;​
this.rooms = rooms;​
System.out.println("Parameterized constructor
called: " + type + " house with " + rooms + " rooms
created.");​
}​
}​

public class Main {​
public static void main(String args) {​
House luxuryHouse = new House("Luxury", 5); //
Calls parameterized constructor​
System.out.println("House type: " +
luxuryHouse.type + ", Rooms: " + luxuryHouse.rooms);​
}​
}​

●​ Constructor Overloading: A class can define multiple constructors, provided that each
constructor has a unique parameter list. This uniqueness can be achieved through a
different number of parameters, different types of parameters, or a different order of
parameter types. This practice is known as constructor overloading and adheres to the
same rules as method overloading.
●​ Constructor Chaining and this()/super(): Constructors possess the ability to call other
constructors within the same class or from their superclass. This mechanism is known as
constructor chaining. Using this(arguments) allows one constructor to invoke another
constructor of the same class, which is useful for reducing code duplication in initialization
logic. Similarly, super(arguments) is used as the first statement in a child class constructor
to explicitly call a specific constructor of its immediate parent class. If super() is not
explicitly called, Java implicitly invokes the parent's default (no-args) constructor. The
strict placement of this() or super() as the first statement in a constructor is a deliberate
design choice. It ensures that the object's superclass portion is fully initialized before the
subclass's constructor begins its work, preventing a subclass from attempting to use
uninitialized parts of its superclass, which could lead to errors. This strict ordering is
fundamental to the correct construction of objects in an inheritance hierarchy.

4. static Keyword
The static keyword in Java is a non-access modifier primarily used for memory management.
When applied to a member (variable, method, block, or nested class), it signifies that the
member belongs to the class itself, rather than to individual instances (objects) of that class.
●​ Key Characteristics:
○​ Class-level Ownership: Static members are associated with the class, not with its
objects.
○​ Single Copy: Only one copy of a static member exists in memory, regardless of
how many objects of the class are created.
○​ Direct Access: Static members can be accessed directly using the class name,
without the necessity of creating an instance of the class (e.g.,
ClassName.staticMethod()).
○​ Memory Efficiency: Static members are loaded into memory only once when the
class is loaded, contributing to memory savings.
○​ Access Limitations: Static methods can only directly access other static members.
They cannot directly access non-static (instance) members because non-static
members require an object instance to exist in memory before they can be
accessed.
●​ Usage of static Keyword:
1.​ Static Variables (Class Variables):
■​ Explanation: Static variables are shared among all instances of a class.
They are initialized once when the class is loaded into memory.
■​ Real-world Analogy: A universal constant like the speed limit on a
highway. This speed limit applies to all cars (objects) on that highway, not just
a single specific car. If the speed limit changes, it changes universally for all
cars. Another example is a houseCount variable in a House class, which is
shared by all House objects and tracks the total number of houses created.
■​ Code Example:​
public class University {​
static String universityName = "Tech University"; //
Static variable, shared by all students​
String studentName;​

public University(String studentName) {​
this.studentName = studentName;​
}​

public void displayStudentInfo() {​
System.out.println(studentName + " attends " +
universityName);​
}​

public static void main(String args) {​
University student1 = new University("Alice");​
University student2 = new University("Bob");​

student1.displayStudentInfo(); // Output: Alice
attends Tech University​
student2.displayStudentInfo(); // Output: Bob
attends Tech University​

// Changing the static variable affects all
instances​
University.universityName = "Global University";​
student1.displayStudentInfo(); // Output: Alice
attends Global University​
}​
}​

2.​ Static Methods (Class Methods):


■​ Explanation: Static methods can be invoked directly using the class name
without creating an instance of the class. They are restricted to accessing
only static data members and other static methods directly.
■​ Real-world Analogy: A public calculator. One does not need to own a
specific physical calculator (object) to use general calculation functions like
add() or square(). The Math class in Java serves as a prime example, with
most of its methods (e.g., Math.sqrt(), Math.PI) being static and accessible
directly via the class name.
■​ Code Example:​
public class Calculator {​
static double PI = 3.14159; // Static variable​

// Static method to calculate the area of a circle​
public static double circleArea(double radius) {​
return PI * radius * radius;​
}​

public static void main(String args) {​
// Calling static method and accessing static
variable using the class name​
double area = Calculator.circleArea(5.0);​
System.out.println("Area of circle: " + area); //
Output: Area of circle: 78.53975​
}​
}​

3.​ Static Blocks:


■​ Explanation: Static blocks are primarily used for static initializations of a
class. The code contained within a static block is executed only once,
specifically when the class is loaded into memory, and it runs even before the
main method.
■​ Real-world Analogy: A factory setting up its machinery. Before any
products (objects) can be manufactured, the machines need to be assembled
and configured once. This initial setup process occurs only once when the
factory (analogous to the class) begins its operations.
■​ Code Example:​
public class StaticBlockExample {​
static {​
System.out.println("Static block executed:
Initializing resources...");​
// This is where one-time setup tasks might be
performed,​
// such as loading a database driver or
establishing an initial connection.​
}​

public static void main(String args) {​
System.out.println("Main method executed.");​
}​
}​
// Output:​
// Static block executed: Initializing resources...​
// Main method executed.​

4.​ Static Nested Classes:


■​ Explanation: A class can only be declared static if it is a nested class;
top-level classes cannot be static. Static nested classes do not require an
instance of the outer class to be created for their use and cannot directly
access non-static members of the enclosing class.
■​ Real-world Analogy: A car manufacturer (outer class) having a separate
department specifically for designing and testing engines (static nested
class). This engine department can function independently and does not
require a specific car model (an instance of the outer class) to exist or
operate. It represents a self-contained unit that is related to the car
manufacturer but not dependent on a particular car.
■​ Code Example:​
public class OuterClass {​
static class StaticNestedClass {​
void display() {​
System.out.println("Static nested class
method called.");​
}​
}​

public static void main(String args) {​
// Creating an instance of the static nested
class directly via the outer class​
OuterClass.StaticNestedClass nestedObject = new
OuterClass.StaticNestedClass();​
nestedObject.display(); // Output: Static nested
class method called.​
}​
}​

While static members offer advantages such as memory efficiency and faster access due to
being loaded only once and not requiring object instantiation , their overuse can lead to tightly
coupled code that is difficult to test and maintain. This highlights a fundamental trade-off in
software design: optimizing for performance versus maintaining flexibility and testability.
Additionally, the shared nature of static variables introduces thread safety issues in
multi-threaded environments. Since a single copy of a static variable is shared across all
threads, concurrent modification by multiple threads can lead to race conditions and data
inconsistency. Developers must explicitly manage access to static variables using
synchronization mechanisms (such as synchronized methods or blocks) to ensure thread safety.
This connects the static keyword directly to multi-threading and synchronization concepts. Static
methods are frequently found in utility classes (e.g., the Math class) which provide helper
functions that do not depend on any instance-specific data. The static keyword is also integral to
design patterns like the Singleton pattern, where a private static constructor and a public static
method ensure that only one instance of a class is ever created.

5. final Keyword, this and super Keywords


These keywords play crucial roles in Java, often appearing in conjunction with other OOP
concepts like classes, objects, constructors, and inheritance.
●​ final Keyword: The final keyword is used to impose restrictions on the user. Its
application varies depending on the context:
○​ Variables: When applied to a variable, final makes it a constant, meaning its value
cannot be reassigned once initialized.
○​ Methods: Declaring a method as final prevents it from being overridden by any
subclass.
○​ Classes: A final class cannot be inherited (extended) by other classes. The
significance of final lies in its ability to ensure immutability (for variables), enforce
specific design decisions (for methods), and prevent unauthorized extension (for
classes), thereby contributing to the overall security and stability of an application.
For instance, public static final double PI = 3.14159; defines a constant that is
shared across all instances and cannot be modified. The combination of static and
final creates a true compile-time constant, ideal for global, unchangeable values.
The final keyword is a powerful tool for enforcing design intent and creating robust
systems. When a variable is final, its value cannot change, which is critical for
creating immutable objects (objects whose state cannot be modified after creation).
Immutable objects are inherently thread-safe and simplify concurrent programming.
When a method is final, it signals that its behavior is fixed and should not be altered
by subclasses, which can be important for security or core logic. A final class
indicates that its design is complete and not intended for extension, preventing
unintended side effects of subclassing.
●​ this Keyword: The this keyword is a reference variable that points to the current object
instance. Its primary usages include:
○​ Referring to Current Class Instance Variables: When an instance variable and a
parameter (or local variable) within a method or constructor share the same name,
this is used to explicitly refer to the instance variable, resolving the ambiguity (e.g.,
this.color = color;).
○​ Invoking Current Class Constructor (Constructor Chaining): this(arguments)
can be used as the first statement within a constructor to call another constructor of
the same class. This facilitates constructor chaining, allowing for more organized
and less redundant initialization logic.
○​ Passing the Current Object as an Argument: this can be passed as an argument
in a method call to refer to the current object. The significance of this lies in
providing clarity in code and enabling flexible constructor design by allowing one
constructor to call another, thereby promoting code reuse in object initialization.
●​ super Keyword: The super keyword is a reference variable that refers to the immediate
parent class object. Its main applications are:
○​ Referring to Immediate Parent Class Instance Variables: It is used to access a
field from the parent class when a subclass has a field with the same name.
○​ Invoking Immediate Parent Class Method: It allows a subclass to call a method
defined in its parent class, especially when that method has been overridden in the
subclass (e.g., super.makeSound();).
○​ Invoking Immediate Parent Class Constructor: super(arguments) must be the
very first statement in a child class constructor to call a specific constructor of the
parent class. If super() is not explicitly called, Java implicitly invokes the parent's
default (no-args) constructor. The super keyword is crucial for managing
inheritance, ensuring proper initialization of the parent portion of an object before
the child class's initialization begins. It also allows subclasses to extend or modify
parent behavior while still leveraging its original implementation, which is
fundamental for maintaining class hierarchies. The strict placement of this() and
super() as the first statement in a constructor is a deliberate design choice. This
ensures that the object's superclass portion is fully initialized before the subclass's
constructor begins its work, preventing a subclass from trying to use uninitialized
parts of its superclass, which could lead to errors. This strict ordering is
fundamental to the correct construction of objects in an inheritance hierarchy.

6. Arrays
Arrays in Java are fundamental data structures that allow for storing multiple values of the same
data type in a single variable. They are characterized by their fixed size, meaning their capacity
is determined at the time of creation and cannot be altered subsequently.
●​ Declaration and Initialization:
○​ Declaration: An array can be declared using dataType arrayName; or dataType
arrayName;.
○​ Initialization: An array is initialized either by specifying its size, such as arrayName
= new dataType[size];, or by directly providing initial values, as in dataType
arrayName = {value1, value2,...};.
●​ Accessing Elements: Elements within an array are accessed using a zero-based index
(e.g., arrayName refers to the first element).
●​ Multi-Dimensional Arrays: Java supports multi-dimensional arrays, which are essentially
arrays of arrays, commonly used for representing tabular data or matrices (e.g., int
matrix;).
●​ Significance: Arrays are highly efficient for storing and accessing a fixed number of
homogeneous elements. They provide fast random access (O(1) time complexity) to
elements, as the memory location of any element can be directly calculated from its index.
●​ Code Example:​
public class ArrayExample {​
public static void main(String args) {​
// Declare and initialize an array of integers with a
specified size​
int numbers = new int; // Creates an array capable of
holding 5 integers​

// Assign values to elements using their indices​
numbers = 10;​
numbers = 20;​
numbers = 30;​
numbers = 40;​
numbers = 50;​

// Access and print a specific element​
System.out.println("Element at index 0: " + numbers); //
Output: 10​

// Iterate through the array to print all elements​
System.out.print("All elements: ");​
for (int i = 0; i < numbers.length; i++) {​
System.out.print(numbers[i] + " "); // Output: 10 20
30 40 50​
}​
System.out.println();​

// Declare and initialize a string array directly with
values​
String fruits = {"Apple", "Banana", "Cherry"};​
System.out.println("First fruit: " + fruits); // Output:
Apple​
}​
}​
The fixed-size nature of arrays stands in direct contrast to dynamic collections like
ArrayList. While arrays offer excellent performance for direct element access (O(1) time
complexity) and memory efficiency (due to contiguous memory allocation), their fixed size
presents a significant limitation. This means that adding or removing elements in the
middle of an array necessitates creating a new array and copying existing elements,
which is an O(N) operation. This inherent trade-off in data structures—balancing
performance for specific operations against flexibility—is precisely why Java provides a
rich Collections Framework. This framework offers dynamic data structures like ArrayList
and LinkedList that abstract away the complexities of resizing, albeit sometimes with a
slight performance overhead for certain operations. Understanding this distinction is
crucial for selecting the appropriate data structure for a given programming task.

7. Reading Console Inputs


Reading input from the console is a fundamental operation for interactive Java programs,
allowing users to provide data to the application. System.in serves as the standard input stream.
●​ System.in: System.in is an InputStream typically connected to the keyboard or another
standard input device. It reads raw bytes from the input source. While System.in can be
used directly for byte-level input, it is often wrapped in a Reader (such as
InputStreamReader) and subsequently in a BufferedReader for more convenient
character-based or line-based input. For simpler and more versatile input parsing, such as
reading integers, strings, or doubles, the Scanner class is commonly employed.
●​ Scanner Class (for user-friendly input): The Scanner class, part of the java.util
package, is a widely used method for reading user input from various sources, including
the keyboard (System.in). It provides methods for parsing different data types, such as
nextInt(), nextLine(), and nextDouble(), simplifying the process of converting raw input into
usable data.A relatable analogy for the Scanner class is a waiter at a restaurant taking
an order. The order provided by the customer is analogous to the input a program
receives. Just as the chef requires the order to prepare the meal, a program needs user
input to perform its tasks. The Scanner class acts like this waiter, taking the customer's
order (input) and delivering it to the kitchen (program) for processing.The use of
System.in and Scanner exemplifies Java's layered approach to I/O, which abstracts away
low-level implementation details. System.in operates as a raw byte stream. Directly
reading from this stream can be cumbersome for textual input. The Scanner class wraps
this low-level stream with higher-level functionality, allowing for easy parsing of various
data types (strings, integers, etc.). This illustrates the concept of I/O abstraction:
developers do not need to concern themselves with how bytes are converted to
characters or how numbers are parsed from text, as the Scanner handles these
complexities. This simplification streamlines development and aligns with the OOP
principle of hiding complexity.
●​ Code Example (using Scanner):​
import java.util.Scanner; // Import the Scanner class to use its
functionalities​

public class ConsoleInputExample {​
public static void main(String args) {​
// Create a Scanner object to read input from the standard
input stream (keyboard)​
Scanner scanner = new Scanner(System.in);​

System.out.print("Enter your name: ");​
String name = scanner.nextLine(); // Reads an entire line
of text (String) input by the user​

System.out.print("Enter your age: ");​
int age = scanner.nextInt(); // Reads the next integer
value input by the user​

// Print a personalized greeting using the collected input​
System.out.println("Hello, " + name + "! You are " + age +
" years old.");​

scanner.close(); // It is crucial to close the scanner to
release system resources​
}​
}​

Unit II: Core OOP Concepts & Exception Handling


1. Inheritance
Inheritance is a fundamental Object-Oriented Programming (OOP) concept that enables one
class to acquire (inherit) the properties (fields) and behaviors (methods) of another class. This
mechanism establishes a parent-child or "is-a" relationship between classes, promoting
significant code reuse and enhancing extensibility within a software system.
●​ Key Concepts:
○​ Superclass (Parent Class): The class from which features are inherited.
○​ Subclass (Child Class / Derived Class): The class that inherits features from the
superclass.
○​ extends Keyword: This keyword is exclusively used to establish an inheritance
relationship between classes in Java. In Java, a class can only extend one
superclass directly, adhering to the principle of single inheritance for classes.
○​ Code Reusability: A primary benefit of inheritance is that subclasses can reuse
methods and fields defined in the superclass, significantly reducing code duplication
and promoting more efficient development.
○​ Method Overriding: Subclasses have the ability to provide a specific
implementation for a method that is already defined (and inherited) in its
superclass. This is a crucial aspect of polymorphism.
●​ Real-life Analogy: A common analogy for inheritance is a general blueprint for a car
(representing the superclass) that defines common features such as wheels, an engine,
and seats. If a developer then wishes to design a sports car (representing the subclass),
there is no need to start from scratch. Instead, the sports car blueprint can extend the
existing general car blueprint, inheriting all its fundamental features and then adding new,
specialized attributes like a turbo engine or a sporty design.
●​ Types of Inheritance:
1.​ Single Inheritance:
■​ Explanation: This is the most straightforward form of inheritance, where a
subclass inherits directly from only one superclass. Java fully supports single
inheritance for classes, meaning a class can have only one direct parent
class.
■​ Analogy: This can be likened to the tradition of passing down a family
recipe from grandparents (the superclass) to parents (the subclass).
The parents inherit the entire cookbook, complete with all its recipes and
cooking tips, directly from their predecessors.
■​ Code Example:​
// Superclass: Defines common characteristics for animals​
class Animal {​
void eat() {​
System.out.println("This animal eats food.");​
}​
}​

// Subclass: Dog inherits from Animal and adds its own
specific behavior​
class Dog extends Animal {​
void bark() {​
System.out.println("The dog barks.");​
}​
}​

public class Main {​
public static void main(String args) {​
Dog myDog = new Dog(); // Create an object of the
Dog class​
myDog.eat(); // Call the inherited 'eat' method
from Animal​
myDog.bark(); // Call the 'bark' method specific
to Dog​
}​
}​
// Output:​
// This animal eats food.​
// The dog barks.​
2.​ Multi-level Inheritance:
■​ Explanation: Multi-level inheritance involves a chain of inheritance, where a
class inherits from a class, which in turn inherits from another class (e.g.,
Class C inherits from Class B, which inherits from Class A).
■​ Analogy: This type of inheritance can be illustrated by a hierarchy of
transportation modes. For instance, a Car (Level 3) inherits characteristics
from a LandVehicle (Level 2), which itself inherits from a general Vehicle
(Level 1). An ElectricCar (Level 4) further extends Car, consequently
inheriting features and behaviors from all preceding levels in the hierarchy.
■​ Code Example (Conceptual):​
class Vehicle { /*... common vehicle properties and
methods... */ }​
class LandVehicle extends Vehicle { /*... land vehicle
specific properties and methods... */ }​
class Car extends LandVehicle { /*... car specific
properties and methods... */ }​
class ElectricCar extends Car { /*... electric car
specific properties and methods... */ }​

3.​ Hierarchical Inheritance:


■​ Explanation: Hierarchical inheritance occurs when a single base class
(superclass) is inherited by multiple distinct subclasses. In this structure, all
common features that are shared among the child classes are centralized
within the base class, which enhances code manageability and reusability.
■​ Analogy: Consider a school system as an analogy. A SchoolMember
(representing the base class) is inherited by various categories of individuals
within the school, such as Teacher, Student, and Staff (representing the
subclasses). Each subclass possesses its unique attributes and behaviors,
but they all share common properties defined in the SchoolMember base
class.
■​ Code Example (Conceptual):​
class SchoolMember { /* name, age, showDetails() */ }​
class Teacher extends SchoolMember { /*
subjectSpecialization, teach() */ }​
class Student extends SchoolMember { /* gradeLevel,
study() */ }​
class Staff extends SchoolMember { /* department, work()
*/ }​

4.​ Multiple Inheritance (via Interfaces):


■​ Explanation: While Java classes do not support multiple inheritance directly
(a class cannot extend more than one class) to avoid complexities like the
"diamond problem" , Java effectively achieves multiple inheritance of
behavior through the use of interfaces. A class can implement multiple
interfaces, thereby acquiring the behaviors defined in all of them.
Furthermore, interfaces themselves can extend multiple other interfaces.
Since Java 8, interfaces gained the ability to define default methods, which
are methods with a concrete implementation. This allows interfaces to
provide common utility methods without breaking existing implementations in
classes that already implement that interface. These default methods can be
optionally overridden by implementing classes to provide specialized
behavior.
■​ Analogy: A person can be both a Musician (implementing a Musician
interface) and an Athlete (implementing an Athlete interface). This person
inherits the playInstrument() behavior from Musician and runMarathon() from
Athlete, demonstrating the acquisition of multiple sets of behaviors from
different contracts.
■​ Code Example (Conceptual):​
interface Playable {​
default void play() {​
System.out.println("Playing something.");​
}​
}​
interface Swimmable {​
default void swim() {​
System.out.println("Swimming.");​
}​
}​
class Human implements Playable, Swimmable {​
// The Human class can use the default play() and
swim() methods,​
// or it can choose to override them for specific
human behaviors.​
@Override​
public void play() {​
System.out.println("Human plays music.");​
}​
}​

Java's restriction on multiple inheritance for classes is a deliberate design choice. The "diamond
problem" arises when a class inherits from two parent classes that both possess a method with
the same signature. If the subclass attempts to call this method, the compiler faces ambiguity in
determining which parent's method to execute. Java resolves this by allowing multiple
inheritance only through interfaces, which traditionally only declared abstract methods (without
implementation). With the introduction of default methods in Java 8, potential ambiguity is
managed by compelling the implementing class to provide its own implementation if a conflict
arises. This demonstrates Java's pragmatic approach to balancing flexibility with simplicity and
avoiding complex method resolution rules.Inheritance serves as a prerequisite for method
overriding , a key aspect of runtime polymorphism. Method overriding, a form of runtime
polymorphism, fundamentally requires an inheritance relationship. Without a
superclass-subclass hierarchy, a method cannot be "overridden" in the sense of providing a
specialized implementation for an inherited method. This establishes a causal link between the
two concepts: inheritance provides the structural framework, and overriding enables dynamic
behavior specialization within that framework. This connection is vital for a holistic
understanding of OOP.The extends keyword enables extensibility, allowing new classes to build
upon existing ones. Conversely, the final keyword (discussed in Unit I) explicitly prevents
extension or modification. This highlights a critical design decision: whether a class or method is
intended to be a flexible base for future development (extends) or a fixed, unchangeable
component (final). Understanding this tension is crucial for designing robust and maintainable
class hierarchies.

2. Polymorphism: Method Overloading vs. Method Overriding


Polymorphism, derived from Greek words meaning "many forms," is a core OOP principle that
allows objects to take on multiple forms or exhibit different behaviors. In Java, polymorphism is
primarily achieved through two mechanisms: method overloading (a form of compile-time
polymorphism) and method overriding (a form of runtime polymorphism).
●​ 1. Method Overloading (Compile-time / Static Polymorphism):
○​ Definition: Method overloading occurs when multiple methods within the same
class share the same name but possess different parameter lists (signatures). The
Java compiler determines which specific overloaded method to invoke based on the
method signature (the number, type, and order of parameters) at compile time.
○​ Rules:
■​ The method name must be identical.
■​ The parameter list must differ. This can be achieved by varying the number of
parameters, the data types of the parameters, or the order in which the
parameter types appear.
■​ The return type of the methods may or may not be the same.
■​ Access modifiers do not affect method overloading.
■​ Static methods can also be overloaded.
○​ Real-world Analogy: Imagine entering a coffee shop and simply asking the barista
for "coffee". This seemingly simple request can lead to various outcomes—an
espresso, a cappuccino, or a latte; or it could be small, regular, or large—depending
on the "ingredients" or "methods" (analogous to parameters) specified. The
fundamental process of preparing coffee remains the same, but the variations in
input lead to different, yet related, results.
○​ Significance: Method overloading enhances code flexibility and readability by
allowing a single, descriptive method name to be used for similar operations that
handle different types or numbers of inputs. This reduces code clutter, promotes
code reusability, and makes the application's interface more intuitive.
○​ Code Example:​
class Calculator {​
// Method 1: Adds two integers​
public int add(int a, int b) {​
return a + b;​
}​

// Method 2: Adds three integers (different number of
parameters)​
public int add(int a, int b, int c) {​
return a + b + c;​
}​

// Method 3: Adds two doubles (different types of
parameters)​
public double add(double a, double b) {​
return a + b;​
}​
}​

public class Main {​
public static void main(String args) {​
Calculator calc = new Calculator();​
System.out.println("Sum of 2, 3: " + calc.add(2, 3));
// Compiler resolves to Method 1​
System.out.println("Sum of 2, 3, 4: " + calc.add(2,
3, 4)); // Compiler resolves to Method 2​
System.out.println("Sum of 2.5, 3.5: " +
calc.add(2.5, 3.5)); // Compiler resolves to Method 3​
}​
}​
// Output:​
// Sum of 2, 3: 5​
// Sum of 2, 3, 4: 9​
// Sum of 2.5, 3.5: 6.0​

●​ 2. Method Overriding (Runtime / Dynamic Polymorphism):


○​ Definition: Method overriding occurs when a subclass provides a specific
implementation for a method that is already defined (and inherited) in its parent
class. The resolution of which method to call happens at runtime, based on the
actual type of the object, rather than its declared reference type. This mechanism
inherently requires an inheritance relationship between classes.
○​ Rules:
■​ The method in the subclass must have the exact same name, return type,
and parameter list as the method in the parent class.
■​ The access modifier of the overriding method cannot be more restrictive than
that of the overridden method in the parent class (e.g., a protected parent
method cannot be overridden as private).
■​ Only inherited, non-static, non-final, and non-private methods can be
overridden.
■​ The overriding method can throw the same or a narrower checked exception
than the parent method.
■​ The @Override annotation is highly recommended for clarity and to enable
compile-time checking for correct overriding.
○​ Real-world Analogy: Consider the scenario of different animals making sounds.
A general Animal class might define a makeSound() method. However, a Dog
subclass would override makeSound() to produce "Bark! Woof!", while a Cat
subclass would override it to produce "Meow!". The action (makeSound()) remains
consistent, but the specific behavior (the sound produced) dynamically differs based
on the actual type of animal object.
○​ Significance: Method overriding allows for the customization of behavior in child
classes without altering the parent class's definition. This is essential for achieving
dynamic behavior, where the specific operation performed depends on the object's
runtime type, and for adhering to core OOP principles like abstraction and
extensibility.
○​ Code Example:​
// Parent Class: Defines a general sound behavior​
class Animal {​
void makeSound() {​
System.out.println("The animal makes a generic
sound.");​
}​
}​

// Child Class: Dog extends Animal and provides its own
specific sound behavior​
class Dog extends Animal {​
@Override // Recommended annotation for clarity and
compile-time checks​
void makeSound() {​
System.out.println("The dog barks: Woof! Woof!");​
}​
}​

public class Main {​
public static void main(String args) {​
Animal myAnimal = new Animal();​
myAnimal.makeSound(); // Calls Animal's method: "The
animal makes a generic sound."​

Dog myDog = new Dog();​
myDog.makeSound(); // Calls Dog's overridden
method: "The dog barks: Woof! Woof!"​

// Demonstrating runtime polymorphism:​
// A reference of type Animal pointing to an object
of type Dog​
Animal polymorphicAnimal = new Dog();​
polymorphicAnimal.makeSound(); // Calls Dog's
overridden method at runtime​
}​
}​
// Output:​
// The animal makes a generic sound.​
// The dog barks: Woof! Woof!​
// The dog barks: Woof! Woof!​

The core difference between method overloading and method overriding lies in when the
method call is resolved. Method overloading utilizes static binding (also known as early
binding), where the compiler determines which overloaded method to invoke based on the
arguments' types and number at compile time. This is why it is referred to as compile-time
polymorphism. In contrast, method overriding employs dynamic binding (or late binding),
where the specific implementation of the method to be executed is determined at runtime,
based on the actual object type rather than its declared reference type. This distinction is
critical for understanding how polymorphic behavior is achieved in Java.Polymorphism, in
both its forms, significantly improves code design. Overloading allows for a consistent API
(e.g., add() for different data types) without the need for numerous distinct method names,
thereby enhancing readability and usability. Overriding, on the other hand, enables
specialized behavior within a general class hierarchy, making code more flexible and
adaptable to evolving requirements. This allows developers to write more intuitive and
extensible code, which is a significant advantage in large and complex software
systems.Understanding the limitations of overloading and overriding is as important as
understanding their capabilities. The rules governing these mechanisms are strict for valid
reasons. For instance, private, static, and final methods cannot be overridden. private
methods are not inherited, static methods belong to the class (not an object instance),
and final methods are explicitly marked as unchangeable. Comprehending these
limitations helps prevent common programming errors and aids in designing correct and
robust class hierarchies. For example, attempting to "override" a static method does not
result in true overriding but rather "method hiding," which has different implications for
polymorphic behavior.
●​ Table: Method Overloading vs. Method Overriding | Feature | Method Overloading |
Method Overriding | | :----------------- | :----------------------------------------------------------- |
:----------------------------------------------------------- | | Definition | Multiple methods with the
same name but different parameter lists within the same class. | Redefining a method in a
subclass that already exists in the parent class. | | Class Scope | Occurs within a single
class. | Occurs across a parent class and its child class. | | Parameter List | Must differ
(by number, type, or order). | Must be exactly the same (same name, return type, and
parameters). | | Return Type | May or may not be the same. | Must be the same (or a
covariant return type in later Java versions). | | Access Modifier| No restrictions. | Cannot
be more restrictive than the overridden method. | | Polymorphism Type| Compile-time
polymorphism (Static Binding). | Runtime polymorphism (Dynamic Binding). | |
Inheritance | Not required. | Mandatory. | | static methods| Can be overloaded. | Cannot
be overridden. | | final methods| Can be overloaded. | Cannot be overridden. | | private
methods| Can be overloaded. | Cannot be overridden. |

3. Abstraction: Abstract Classes vs. Interfaces


Abstraction is a fundamental Object-Oriented Programming (OOP) principle that involves hiding
complex implementation details and exposing only the essential features to the user. In Java,
abstraction is primarily achieved through the use of abstract classes and interfaces. These two
mechanisms offer different degrees of abstraction, serving distinct design purposes.
●​ 1. Abstract Classes:
○​ Definition: An abstract class is a class that cannot be instantiated directly; that is,
an object cannot be created from it using the new keyword. Instead, it is designed
to be subclassed (extended) by other classes, which then provide concrete
implementations for its abstract methods.
○​ Characteristics:
■​ An abstract class is declared using the abstract keyword.
■​ It can contain both abstract methods (methods declared without a body, only
a signature) and concrete (regular) methods (methods with a full
implementation).
■​ If a class contains at least one abstract method, the class itself must be
declared abstract.
■​ Any concrete subclass that extends an abstract class must provide
implementations for all inherited abstract methods, or it too must be declared
abstract.
■​ Abstract classes can have constructors, which are typically invoked by their
concrete subclasses using the super() keyword.
■​ They can contain instance variables (fields) with various access modifiers
(e.g., public, private, protected).
■​ In Java, a class can only extend one abstract class, adhering to the principle
of single inheritance for classes.
○​ Real-world Analogy: An abstract class can be compared to a partially designed
vehicle blueprint. This blueprint defines common features that all vehicles will
possess (e.g., start() and stop() methods) but intentionally leaves some details
undefined (e.g., refuel() for a gasoline car or charge() for an electric car). One
cannot construct a generic "Vehicle" directly from this abstract blueprint; rather, it
serves as a foundation for building concrete types of vehicles, such as a GasCar or
an ElectricCar, each completing the specific, undefined aspects of the blueprint.
○​ Significance: Abstract classes provide a common base class with shared
functionality, enforcing that subclasses provide specific implementations for certain
behaviors. They are particularly useful when there is a need to provide some
default behavior through concrete methods while leaving other behaviors for
subclasses to define, thus achieving partial abstraction.
●​ 2. Interfaces:
○​ Definition: An interface in Java is a blueprint of a behavior. It defines a contract
that classes can implement, specifying a set of methods that implementing classes
must provide.
○​ Characteristics:
■​ An interface is declared using the interface keyword.
■​ Prior to Java 8, all methods in an interface were implicitly public and abstract
(meaning they had no method body).
■​ From Java 8 onwards, interfaces can include default methods (methods with
a default implementation) and static methods.
■​ From Java 9 onwards, interfaces can also contain private methods, which
can only be called by default or static methods within the same interface.
■​ All variables declared within an interface are implicitly public, static, and final
(effectively making them constants).
■​ A class implements an interface using the implements keyword, committing to
provide implementations for all its abstract methods.
■​ A class can implement multiple interfaces, thereby achieving multiple
inheritance of behavior in Java.
■​ An interface can extend multiple other interfaces, creating a hierarchy of
contracts.
■​ Interfaces cannot have constructors.
■​ Interfaces cannot be instantiated directly.
○​ Real-world Analogy: A powerful analogy for an interface is a restaurant menu.
The menu (interface) lists the dishes that customers can order (representing
methods like calculate() or prepareDish()), but it does not detail how those dishes
are prepared. Different chefs (analogous to implementing classes) can use their
own unique recipes and methods to prepare the dishes listed on the menu. This
separation allows the restaurant to update its menu or change chefs without
disrupting the overall dining experience, as long as the new chefs adhere to the
menu's defined dishes.
○​ Significance: Interfaces primarily achieve 100% abstraction (before Java 8). They
enable multiple inheritance of behavior, which is not directly supported by Java
classes. Interfaces promote loose coupling, where classes depend on defined
behaviors rather than specific implementations, making the system more flexible
and maintainable. They serve as a mechanism to define a contract for behavior that
multiple classes can adhere to, ensuring consistency across different
implementations.
●​ Code Example: Abstract Class and Interface​
// Abstract Class: Defines a general shape with a color and an
abstract area calculation​
abstract class Shape {​
String color;​

// Constructor for the abstract class​
public Shape(String color) {​
this.color = color;​
}​

// Concrete method: Provides a default implementation that all
subclasses can use​
public void displayColor() {​
System.out.println("Shape color: " + color);​
}​

// Abstract method: Must be implemented by any concrete
subclass​
public abstract double calculateArea();​
}​

// Interface: Defines a contract for drawing and resizing objects​
interface Drawable {​
// All methods in an interface are public abstract by default
(pre-Java 8)​
void draw();​

// Default method (Java 8+): Provides a default implementation
that can be overridden​
default void resize() {​
System.out.println("Resizing the drawable object.");​
}​
}​

// Concrete class: Extends an abstract class and implements an
interface​
class Circle extends Shape implements Drawable {​
double radius;​

public Circle(String color, double radius) {​
super(color); // Call the constructor of the abstract
Shape class​
this.radius = radius;​
}​

@Override​
public double calculateArea() {​
return Math.PI * radius * radius; // Implementation of the
abstract method​
}​

@Override​
public void draw() {​
System.out.println("Drawing a circle of color " + color +
" with radius " + radius);​
}​
}​

public class Main {​
public static void main(String args) {​
Circle myCircle = new Circle("Green", 5.0);​
myCircle.displayColor(); // Calls concrete method
inherited from Shape​
myCircle.draw(); // Calls implemented method
from Drawable interface​
myCircle.resize(); // Calls default method from
Drawable interface​
System.out.println("Area: " + myCircle.calculateArea());
// Calls implemented abstract method from Shape​

// The following lines would cause compilation errors:​
// Shape s = new Shape("Red"); // Cannot instantiate an
abstract class​
// Drawable d = new Drawable(); // Cannot instantiate an
interface​
}​
}​
// Output:​
// Shape color: Green​
// Drawing a circle of color Green with radius 5.0​
// Resizing the drawable object.​
// Area: 78.53981633974483​
Abstract classes and interfaces represent different degrees of abstraction, serving distinct
design purposes. Abstract classes are typically used for "is-a" relationships where some
common implementation can be shared, but specific behaviors need to be defined by
subclasses (partial abstraction). Interfaces, conversely, are designed for "has-a" or
"can-do" relationships, defining a contract for behavior without providing any
implementation (100% abstraction prior to Java 8). The introduction of default methods in
Java 8 somewhat blurred this distinction by allowing interfaces to provide common utility
methods without breaking existing implementations, but their primary role as behavioral
contracts remains paramount. This nuanced understanding is crucial for designing
extensible and maintainable software systems.Interfaces are Java's elegant solution to
the multiple inheritance dilemma. As discussed earlier, Java avoids the diamond problem
by disallowing multiple inheritance for classes. Interfaces provide a workaround by
allowing a class to implement multiple contracts, thereby inheriting multiple sets of
behaviors. This design choice prioritizes clarity and avoids the complexities of method
resolution that plague languages with direct multiple inheritance.Both abstract classes
and interfaces promote loose coupling and are foundational for polymorphism. By
programming to an interface or an abstract class (i.e., using a reference of the abstract
type to refer to a concrete object), developers decouple the client code from specific
implementations. This means that different concrete implementations can be easily
swapped out without altering the client code, as long as they adhere to the defined
contract. This flexibility is a hallmark of robust, maintainable software and is a direct
consequence of abstraction and polymorphism.
●​ Table: Abstract Class vs. Interface | Feature | Abstract Class | Interface | | :-----------------
| :----------------------------------------------------------- |
:----------------------------------------------------------- | | Keyword | abstract class | interface | |
Instantiation | Cannot be instantiated directly. | Cannot be instantiated directly. | |
Methods | Can have both abstract (no body) and concrete (with body) methods. | Prior to
Java 8, all methods were abstract. From Java 8, can have default and static methods.
From Java 9, private methods. | | Variables | Can have instance, static, and final
variables with any access modifier. | All variables are implicitly public static final
(constants). | | Constructors | Can have constructors. | Cannot have constructors. | |
Inheritance | A class extends only one abstract class. | A class implements multiple
interfaces. An interface extends multiple interfaces. | | Abstraction Level| Partial
abstraction (0-100%). | Full abstraction (100% before Java 8, can be less with default
methods). | | Purpose | To provide a common base for related classes, sharing some
implementation. | To define a contract for behavior that unrelated classes can implement. |

4. Exception Handling
Exception handling is a crucial mechanism in Java for managing unexpected events
(exceptions) that disrupt the normal flow of a program's execution. It allows programs to
gracefully recover from errors, prevent crashes, and provide meaningful feedback to users and
developers.
●​ What is an Exception? An exception is an event that occurs during the execution of a
program that disrupts the normal instruction flow. Unlike errors (which represent serious,
unrecoverable system-level issues like OutOfMemoryError), exceptions are typically
recoverable and can be handled programmatically. When an exception occurs, the normal
execution of the program is interrupted, and control is transferred to the corresponding
exception handler, if one exists.A relatable analogy for an exception is being in a kitchen
preparing a meal following a recipe. If one suddenly runs out of salt, this situation is akin
to an exception—an unexpected interruption in the plan. This can be handled by finding a
substitute (like pepper) or by going to the store to acquire more salt. Just as the issue can
be resolved in the kitchen, exceptions in Java can be handled to ensure the program
continues running smoothly. Another analogy involves a leave request moving up a chain
of command in an organization, where each handler in the chain can either process the
request or escalate it higher until an appropriate handler is found.
●​ Types of Exceptions:
1.​ Checked Exceptions:
■​ Explanation: These exceptions are rigorously checked at compile-time by
the Java compiler. The compiler forces the programmer to either handle them
(by enclosing the potentially problematic code in a try-catch block) or declare
them (by adding the throws keyword to the method signature). Checked
exceptions represent recoverable problems that a well-written application
should anticipate and handle gracefully. Common examples include
IOException (for issues with file or network access) and SQLException (for
database access errors).
■​ Significance: This type of exception handling enforces robust error
management, ensuring that developers explicitly acknowledge and address
potential issues that are outside the program's direct control but are
recoverable. The distinction between checked and unchecked exceptions
reflects different types of problems. Checked exceptions are for recoverable
conditions that external factors (e.g., network issues, file system problems,
invalid user input) might cause, and the application should be able to handle
them gracefully.
2.​ Unchecked Exceptions (Runtime Exceptions):
■​ Explanation: These exceptions are not checked at compile-time and
manifest only during program execution (runtime). They are typically caused
by programming errors or logical flaws within the code. Common examples
include NullPointerException (attempting to use an object reference that is
null), ArrayIndexOutOfBoundsException (accessing an array index outside its
valid range), ArithmeticException (e.g., division by zero), and
IllegalArgumentException (passing an invalid argument to a method). The
compiler does not mandate their handling, as they often indicate a
fundamental bug that should be fixed in the code rather than merely handled
at runtime.
■​ Significance: Unchecked exceptions serve to alert developers to bugs in
their code that require rectification. While they can be caught, it is generally
not recommended unless for very specific, localized recovery scenarios.
Unchecked exceptions are for programming errors (bugs) that indicate a flaw
in the code (e.g., dereferencing a null pointer). These are typically not
recoverable at runtime and should be fixed by modifying the code. This
distinction guides developers on when to catch and when to fix errors.
3.​ User-Defined Exceptions (Custom Exceptions):
■​ Explanation: Developers have the capability to create their own custom
exception classes by extending either the Exception class (to create a
checked exception) or the RuntimeException class (to create an unchecked
exception).
■​ Significance: Custom exceptions provide more specific and meaningful error
messages that are relevant to the application's particular domain, allowing for
more precise error handling than generic built-in exceptions. This practice
enhances code readability and maintainability by clearly indicating specific
failure conditions within the application's logic.
■​ Code Example (Custom Exception):​
// Custom Checked Exception for a specific application
scenario​
class InsufficientFundsException extends Exception {​
public InsufficientFundsException(String message) {​
super(message); // Calls the constructor of the
parent Exception class​
}​
}​

●​ Exception Handling Constructs:


1.​ try-catch Block:
■​ Explanation: The try block encloses the segment of code that is anticipated
to potentially throw an exception. If an exception occurs within the try block,
the normal flow of execution is immediately interrupted, and control transfers
to the appropriate catch block. A catch block is defined to specify the type of
exception it is designed to handle. Multiple catch blocks can be used
sequentially, and it is best practice to order them from the most specific
exception type to the most general.
■​ Significance: The try-catch block is fundamental for preventing program
crashes, enabling graceful error recovery, and providing a structured
mechanism to handle anticipated problems within an application.
2.​ finally Block:
■​ Explanation: The finally block contains code that is guaranteed to be
executed, regardless of whether an exception occurred in the try block, was
caught by a catch block, or even if a return statement is encountered.
■​ Significance: The finally block is primarily used for crucial cleanup tasks,
such as closing database connections, file streams, or releasing other system
resources. This ensures that resources are properly deallocated and prevents
resource leaks, which can degrade application performance and stability over
time.
■​ Scenario where finally is NOT executed: The finally block might not
execute in very rare circumstances, such as if the Java Virtual Machine (JVM)
itself shuts down (e.g., due to System.exit() being called or a fatal crash).
■​ The importance of the finally block led to the introduction of the
try-with-resources statement in Java 7. This construct automatically closes
resources that implement the AutoCloseable interface, significantly
simplifying resource management and reducing boilerplate code compared to
manual finally block management. This represents an evolution in Java's
approach to safe resource handling.
3.​ throw Keyword:
■​ Explanation: The throw statement is used to explicitly generate (or "throw")
an instance of an exception. This action is typically performed when a method
encounters an error condition that it cannot handle internally.
■​ Significance: The throw keyword allows developers to programmatically
signal an error condition, transferring control to an appropriate exception
handler further up the call stack.
4.​ throws Keyword:
■​ Explanation: The throws keyword is used in a method signature to declare
that the method might throw one or more specified types of checked
exceptions. This declaration serves as a warning to any calling code,
informing it that it must either handle these exceptions using a try-catch block
or re-declare them in its own signature.
■​ Significance: The throws keyword enforces compile-time checking for
checked exceptions, ensuring that potential error scenarios are explicitly
addressed by the calling code. This promotes robust error handling and helps
maintain the integrity of the application's execution flow.
●​ Code Example: Exception Handling (Payment System Analogy)​
import java.util.logging.Logger; // Used for logging,
demonstrating a common practice​

// Custom Checked Exception for insufficient funds​
class InsufficientFundsException extends Exception {​
public InsufficientFundsException(String message) {​
super(message); // Pass the message to the parent
Exception class​
}​
}​

class PaymentService {​
private static final Logger logger =
Logger.getLogger(PaymentService.class.getName());​
private double balance = 500.0; // Initial account balance​

// Method that processes payments and declares potential
checked exceptions​
public void makePayment(double amount) throws
InsufficientFundsException, IllegalArgumentException {​
logger.info("Processing payment of $" + amount);​

if (amount <= 0) {​
// Throw an unchecked exception for invalid input (a
programming error)​
throw new IllegalArgumentException("Payment amount
must be greater than zero.");​
}​

if (amount > balance) {​
// Throw a custom checked exception if balance is
insufficient​
throw new InsufficientFundsException("Available
balance: $" + balance);​
}​

balance -= amount; // Deduct amount if payment is valid​
System.out.println("Payment of $" + amount + " successful.
Remaining balance: $" + balance);​
}​
}​

public class PaymentApp {​
public static void main(String args) {​
PaymentService paymentService = new PaymentService();​

try {​
System.out.println("\n--- Attempting valid payment
---");​
paymentService.makePayment(100); // This call should
succeed​

System.out.println("\n--- Attempting payment exceeding
balance ---");​
paymentService.makePayment(600); // This call will
throw InsufficientFundsException​

System.out.println("\n--- Attempting invalid payment
amount (negative) ---");​
paymentService.makePayment(-50); // This call will
throw IllegalArgumentException​

} catch (InsufficientFundsException e) { // Catches the
custom checked exception​
System.err.println("Error: Insufficient Funds! " +
e.getMessage());​
} catch (IllegalArgumentException e) { // Catches the
unchecked exception​
System.err.println("Error: Invalid Input! " +
e.getMessage());​
} catch (Exception e) { // A general catch-all for any
other unexpected exceptions​
System.err.println("Error: An unexpected error
occurred! " + e.getMessage());​
} finally {​
// This block always executes, regardless of whether
an exception occurred or was caught.​
// It's ideal for cleanup tasks like closing
resources.​
System.out.println("Transaction attempt completed.");​
}​
System.out.println("Application continues after exception
handling.");​
}​
}​
// Expected Output (logging messages may vary in order):​
// --- Attempting valid payment ---​
// Payment of $100.0 successful. Remaining balance: $400.0​
//​
// --- Attempting payment exceeding balance ---​
// Error: Insufficient Funds! Available balance: $400.0​
// Transaction attempt completed.​
// Application continues after exception handling.​
// (Note: The call to makePayment(-50) is not reached because the
previous exception​
// leads to the catch block and then finally block, then main
continues)​
In complex applications, a high-level exception might be thrown, but its root cause could
be a low-level issue (e.g., a PaymentFailedException caused by an underlying
SQLException). Exception chaining (using initCause() or passing the cause in the
constructor) allows the original exception to be wrapped inside a new one, preserving the
entire stack trace and providing a complete diagnostic trail. This is invaluable for
debugging and understanding the flow of errors in multi-layered systems.
●​ Table: Checked vs. Unchecked Exceptions | Feature | Checked Exceptions |
Unchecked Exceptions (Runtime Exceptions) | | :----------------- |
:----------------------------------------------------------- |
:----------------------------------------------------------- | | Compiler Check | Checked at
compile-time. | Not checked at compile-time; occur at runtime. | | Handling Requirement|
Must be handled (try-catch) or declared (throws). | No mandatory handling; typically
indicate bugs. | | Root Class | Subclasses of Exception (excluding RuntimeException and
its subclasses). | Subclasses of RuntimeException. | | Nature of Problem| Recoverable
external problems (e.g., I/O issues, network problems). | Programming errors/logic flaws
(bugs) (e.g., NullPointerException). | | Common Examples| IOException, SQLException,
ClassNotFoundException. | NullPointerException, ArithmeticException,
ArrayIndexOutOfBoundsException, IllegalArgumentException. | | Developer Action|
Anticipate and handle gracefully. | Fix the underlying code bug. |

Unit III: Multi-threading & Java I/O


1. Multi-threading
Multithreading in Java enables a program to perform multiple operations concurrently within a
single process, significantly enhancing application efficiency and responsiveness. A thread
represents the smallest, most lightweight unit of execution within a program.
●​ Thread vs. Process:
○​ Process: A process is an independent execution unit that possesses its own
dedicated memory space and resources, operating in isolation from other
processes.
○​ Thread: A thread is a lightweight unit of execution that operates within a process.
Threads share the same memory space and resources of their parent process,
making them efficient for concurrent tasks that require shared data access.
○​ Real-world Analogy: A useful analogy to differentiate threads from processes is to
consider a company as a process. The company has its own budget,
departments, and resources, operating independently from other companies. Within
this company, employees are analogous to threads. They perform individual
tasks but share the company's common resources, such as office space and
equipment. Communication between employees (threads) is faster and more direct
due to shared resources, but a significant mistake by one employee could
potentially impact the entire company (process). Another analogy is that of multiple
cooks (threads) working in the same kitchen (process), sharing ingredients and
tools to prepare different dishes.
●​ Creating Threads in Java: Threads in Java can be created using two primary methods,
both of which involve defining the task to be performed within the run() method.
1.​ Extending the Thread Class:
■​ Explanation: To create a thread by extending the Thread class, a new class
is defined that extends java.lang.Thread. The run() method of this new class
is then overridden to encapsulate the specific task that the thread will
execute. An instance of this custom Thread class is created, and its start()
method is invoked. The start() method internally calls the run() method in a
newly created, separate thread of execution.
■​ Significance: This approach is straightforward for implementing basic
threading functionalities. However, it is important to note that due to Java's
single inheritance model for classes, a class that extends Thread cannot
extend any other class. This limits design flexibility in scenarios requiring
multiple inheritance of implementation.
■​ Code Example:​
// Define a custom thread by extending the Thread class​
class MyThread extends Thread {​
@Override // Override the run method to define the
thread's task​
public void run() {​
for (int i = 1; i <= 3; i++) {​
System.out.println("Thread extending Thread
class: " + i);​
try {​
Thread.sleep(100); // Simulate some work
with a small delay​
} catch (InterruptedException e) {​
System.out.println("Thread
interrupted.");​
}​
}​
}​
}​

public class Main {​
public static void main(String args) {​
MyThread t1 = new MyThread(); // Create an
instance of MyThread​
t1.start(); // Start the thread, which invokes
its run() method concurrently​
System.out.println("Main thread continues
execution...");​
}​
}​
// Output (messages from main thread and MyThread will be
interleaved due to concurrency):​
// Main thread continues execution...​
// Thread extending Thread class: 1​
// Thread extending Thread class: 2​
// Thread extending Thread class: 3​

2.​ Implementing the Runnable Interface:


■​ Explanation: This method involves creating a class that implements the
java.lang.Runnable interface. The task to be executed by the thread is
defined within the run() method, which is the sole abstract method of the
Runnable interface. An object of this Runnable class is then created and
passed as an argument to the constructor of a Thread object. Finally, the
start() method of this Thread object is called to initiate the thread's execution.
■​ Significance: This is generally the preferred approach for creating threads in
Java. It offers greater design flexibility because the class implementing
Runnable is still free to extend another class, as Java supports multiple
interface implementation. This approach also promotes a better separation of
concerns, as the task definition (in the Runnable implementation) is
decoupled from the thread management (in the Thread class). This is
particularly beneficial in more complex applications, especially when working
with thread pools or shared tasks.
■​ Code Example:​
// Define a custom task by implementing the Runnable
interface​
class MyRunnable implements Runnable {​
@Override // Override the run method from the
Runnable interface​
public void run() {​
for (int i = 1; i <= 3; i++) {​
System.out.println("Thread implementing
Runnable interface: " + i);​
try {​
Thread.sleep(100); // Simulate some work​
} catch (InterruptedException e) {​
System.out.println("Runnable thread
interrupted.");​
}​
}​
}​
}​

public class Main {​
public static void main(String args) {​
MyRunnable task = new MyRunnable(); // Create an
instance of the Runnable task​
Thread t2 = new Thread(task); // Create a Thread
object and associate it with the task​
t2.start(); // Start the thread, executing the
run() method of MyRunnable​
System.out.println("Main thread continues
execution...");​
}​
}​
// Output (messages will be interleaved due to
concurrency):​
// Main thread continues execution...​
// Thread implementing Runnable interface: 1​
// Thread implementing Runnable interface: 2​
// Thread implementing Runnable interface: 3​

Multithreading offers significant benefits for application responsiveness and efficiency. For
instance, in an online food ordering system, multithreading can handle concurrent tasks such as
fetching menu items, processing payments, and tracking delivery status simultaneously,
enhancing the user experience. Similarly, web servers utilize threads to handle multiple client
requests concurrently, allowing hundreds or thousands of users to interact with the server
without waiting for others. In real-time gaming, multithreading is crucial for concurrently
managing rendering, physics calculations, and user input, ensuring smooth gameplay. The
ability of threads to share resources within the same process leads to faster communication
compared to separate processes. However, this shared resource access also introduces
potential challenges, such as race conditions and data inconsistency, if not managed carefully.
This highlights the critical need for thread synchronization mechanisms, which will be discussed
next.

2. Thread Synchronization
Thread synchronization in Java is a mechanism employed to control the access of multiple
threads to shared resources, thereby ensuring data consistency and preventing issues like race
conditions in multithreaded environments. It guarantees that only one thread can access a
critical section (a block of code or a shared resource) at any given time.
●​ Need for Thread Synchronization in Java: In a multithreaded environment, multiple
threads often attempt to access and modify shared resources (such as files, memory, or
database records) simultaneously. Without proper synchronization, this concurrent access
can lead to several critical problems:
○​ Data Inconsistency: When threads modify shared resources without coordination,
their actions can result in inconsistent or corrupted data. Synchronization ensures
data integrity.
○​ Race Conditions: These occur when the outcome of a program depends on the
unpredictable sequence or timing of operations performed by multiple threads on
shared data. Synchronization prevents this by allowing only one thread to access
the critical section at a time.
○​ Thread Safety: Synchronization is essential for ensuring that shared resources are
used safely in a multi-threaded environment, preventing unexpected behavior or
application crashes.
○​ Resource Integrity: Shared resources, like bank account balances or ticket
counts, must be accessed in a controlled manner to prevent data corruption or
incomplete transactions.
○​ Consistent Output: Synchronization ensures predictable and correct behavior by
controlling the sequence of thread execution, especially when shared resources are
involved.
●​ Real-World Analogy: Imagine multiple people attempting to withdraw money from a
single bank account simultaneously. Without synchronization, it would be possible for
them to withdraw more money than is actually available, leading to inconsistencies in the
account balance. In this scenario, synchronization acts like a queue system, allowing
only one person (thread) at a time to safely access and modify the bank account (shared
resource). Similarly, if two computers send print jobs to a single printer at the exact same
time without coordination, the printer might mix their outputs, resulting in invalid
documents.
●​ Mechanisms for Thread Synchronization: Thread synchronization fundamentally
means that only one thread executes a critical section at a time, while other threads
attempting to access that section are held in a waiting state. This process prevents thread
interference and inconsistency problems. In Java, synchronization is built using locks or
monitors. A monitor is an object that serves as a mutually exclusive lock; only a single
thread can own a monitor at a time. When a thread acquires a lock, all other threads
attempting to acquire the same locked monitor are suspended until the first thread
releases the monitor.Java primarily achieves mutual exclusion (ensuring only one thread
accesses a critical section at a time) using the synchronized keyword.
1.​ Synchronized Methods:
■​ Explanation: Declaring a method as synchronized using the synchronized
keyword ensures that only one thread can execute that method on a given
object at any time. When a thread enters a synchronized method, it acquires
a lock on the object, and no other thread can access any synchronized
method of that same object until the lock is released.
■​ Code Example:​
class SharedResource {​
// A synchronized method ensures only one thread can
execute it at a time on this object​
synchronized void printNumbers(String threadName) {​
System.out.println(threadName + " has acquired
the lock.");​
for (int i = 1; i <= 5; i++) {​
System.out.println(threadName + ": " + i);​
try {​
Thread.sleep(500); // Simulate work​
} catch (InterruptedException e) {​
System.out.println(e);​
}​
}​
System.out.println(threadName + " has released
the lock.");​
}​
}​

class MyThread extends Thread {​
SharedResource resource;​
String threadName;​

MyThread(SharedResource resource, String threadName)
{​
this.resource = resource;​
this.threadName = threadName;​
}​

@Override​
public void run() {​
resource.printNumbers(threadName);​
}​
}​

public class Main {​
public static void main(String args) {​
SharedResource resource = new SharedResource();
// A single shared resource object​
MyThread t1 = new MyThread(resource, "Thread-1");​
MyThread t2 = new MyThread(resource, "Thread-2");​
t1.start();​
t2.start();​
}​
}​
// Output (sequential execution due to synchronization):​
// Thread-1 has acquired the lock.​
// Thread-1: 1​
// Thread-1: 2​
// Thread-1: 3​
// Thread-1: 4​
// Thread-1: 5​
// Thread-1 has released the lock.​
// Thread-2 has acquired the lock.​
// Thread-2: 1​
// Thread-2: 2​
// Thread-2: 3​
// Thread-2: 4​
// Thread-2: 5​
// Thread-2 has released the lock.​

2.​ Synchronized Blocks:


■​ Explanation: A synchronized block allows for finer-grained control over
synchronization. Instead of locking an entire method, it locks only a specific
section of code within a method. This is achieved by specifying an object as a
monitor. Only the thread that acquires the lock on that object can execute the
code within the synchronized block. This approach can offer better
performance by limiting the scope of synchronization to only the critical
section that accesses shared resources.
■​ Syntax:​
synchronized (object_reference) {​
// Critical section: code that accesses shared
resources​
}​

■​ Code Example (Conceptual - similar output to synchronized method but


with finer control):​
class SharedResource {​
void printNumbers(String threadName) {​
// Non-synchronized code can run concurrently​
System.out.println(threadName + " is
preparing...");​
synchronized (this) { // Synchronized block:
acquires lock on 'this' object​
System.out.println(threadName + " has
acquired the lock (block).");​
for (int i = 1; i <= 3; i++) {​
System.out.println(threadName + ": " +
i);​
try { Thread.sleep(200); } catch
(InterruptedException e) {}​
}​
System.out.println(threadName + " has
released the lock (block).");​
}​
// More non-synchronized code​
System.out.println(threadName + " is
finishing.");​
}​
}​
// Main class and MyThread setup would be similar to the
synchronized method example,​
// demonstrating sequential execution of the synchronized
block.​

3.​ Static Synchronization:


■​ Explanation: When a synchronized method is declared as static, the lock or
monitor is applied to the class itself, not on an object instance. This ensures
that only one thread can access any static synchronized method of that class
at a time, regardless of how many instances of the class exist.
■​ Code Example (Conceptual):​
class PrintTest {​
// Static synchronized method locks the class
PrintTest​
synchronized public static void printThread(int n) {​
for (int i = 1; i <= 3; i++) {​
System.out.println("Thread " + n + " is
working...");​
try { Thread.sleep(300); } catch (Exception
ex) {}​
}​
}​
}​

class Thread1 extends Thread {​
@Override public void run() {
PrintTest.printThread(1); } // Call static method​
}​

class Thread2 extends Thread {​
@Override public void run() {
PrintTest.printThread(2); } // Call static method​
}​

public class Main {​
public static void main(String args) {​
Thread1 t1 = new Thread1();​
Thread2 t2 = new Thread2();​
t1.start();​
t2.start();​
}​
}​
// Output: Thread 1 completes all its work, then Thread 2
starts,​
// because the lock is on the class itself.​

Beyond mutual exclusion, thread synchronization also encompasses Coordination


Synchronization (or Thread Communication). This mechanism allows threads to cooperate and
communicate with each other to achieve a common goal, enabling one thread to notify another
about a specific condition or event. Java provides methods like wait(), notify(), and notifyAll() in
the Object class for this purpose. These methods allow a thread to pause execution and release
its lock until another thread signals it to resume, facilitating smooth coordination, as seen in the
classic Producer-Consumer problem.

3. Java I/O: Use of InputStream, OutputStream, Reader and Writer


classes for reading from and writing data into disk files.
Java's Input/Output (I/O) system is built upon a stream-based approach, which provides a
flexible and abstract way to handle data flow between a program and external sources or
destinations. A stream is essentially an ordered sequence of data.
●​ Streams:
○​ InputStream: Used for reading data (input) from a source into the Java program.
○​ OutputStream: Used for writing data (output) from the Java program to a
destination. Both InputStream and OutputStream are abstract classes located in the
java.io package, serving as blueprints for more specific stream implementations.
A clear analogy for streams is a stream of water. An input stream is like a siphon that
sucks water into a container, while an output stream is like a hose that sprays water out.
Siphons can be connected to hoses to move water from one place to another, just as
input and output streams can be chained to process data. The power of this stream
metaphor lies in its abstraction: the same methods are used to read from a file, the
console, or a network connection, and to write to a file or a byte array, regardless of the
underlying device. This simplifies I/O programming significantly.Java's I/O system
distinguishes between two primary types of streams based on the data they handle: byte
streams and character streams. The choice between them depends on the nature of the
data being processed.
●​ 1. Byte Streams (InputStream and OutputStream):
○​ Explanation: Byte streams are designed to read and write raw binary data,
processing data byte by byte (8 bits). They do not perform any character encoding
or decoding, making them ideal for handling non-textual data such as images, audio
files, video files, or any other binary information where maintaining the original byte
structure is crucial.
○​ Base Classes: InputStream and OutputStream are the abstract base classes for all
byte input and output streams, respectively.
○​ Common Classes:
■​ FileInputStream: Reads data from a file as bytes.
■​ FileOutputStream: Writes data to a file as bytes.
■​ BufferedInputStream: Improves performance by buffering input bytes.
■​ BufferedOutputStream: Improves performance by buffering output bytes.
○​ Significance: Byte streams offer greater precision and control when processing
files or network communications that require strict adherence to binary formats.
They are more efficient for large binary files as they avoid the overhead of character
encoding and decoding.
●​ 2. Character Streams (Reader and Writer):
○​ Explanation: Character streams are specifically designed for handling textual data,
which in Java is stored using Unicode conventions (16-bit characters). They
automatically handle the conversion between bytes and characters using a
specified character encoding (e.g., UTF-8 or UTF-16), making them suitable for
working with text files like .txt, .csv, or .xml.
○​ Base Classes: Reader and Writer are the abstract base classes for all character
input and output streams, respectively.
○​ Common Classes:
■​ FileReader: Reads character data from a file.
■​ FileWriter: Writes character data to a file.
■​ BufferedReader: Enhances reading efficiency by buffering characters,
allowing reading line by line.
■​ BufferedWriter: Enhances writing efficiency by buffering characters before
writing to the underlying stream.
■​ InputStreamReader: Bridges byte streams to character streams, translating
bytes to characters.
■​ OutputStreamWriter: Bridges character streams to byte streams, translating
characters to bytes.
○​ Significance: Character streams are more convenient and easier to work with
when dealing with text data because they abstract away the complexities of
character encoding and decoding. They ensure proper handling of special
characters and support internationalization.
●​ Comparison: Byte Streams vs. Character Streams The choice between byte streams
and character streams depends critically on the type of data being processed. Using a
character stream for binary data is not recommended and can lead to file corruption, as
character streams interpret data as characters and may inadvertently alter the byte
sequence during encoding or decoding. Conversely, using byte streams for character data
may result in corrupted output if encoding is not handled correctly.
Feature Byte Stream Character Stream
Data Type Handles 8-bit bytes (raw binary Handles 16-bit Unicode
data). characters (textual data).
Base Classes InputStream, OutputStream. Reader, Writer.
Used For Binary data (images, audio, Textual data (text files, .csv,
video, executables). .xml).
Encoding Handling No automatic Automatically handles
encoding/decoding; manual character encoding/decoding
handling if needed. (e.g., UTF-8).
Performance More efficient for binary data; Optimized for text; slight
less overhead. overhead for
encoding/decoding.
Corruption Risk Less risk for binary data; no High risk for binary data if used
interpretation applied. incorrectly; can alter byte
sequence.
Unicode Handling Does not handle Unicode by Handles 16-bit Unicode
default. characters automatically.
Platform Independence Platform-independent as they Designed for
deal with raw bytes. platform-independent character
data handling.
The importance of abstraction in I/O cannot be overstated. Java's I/O libraries are designed in
an abstract way, enabling reading from external data sources and writing to external targets
regardless of the specific type of source or destination. This means the same methods can be
used to read from a file, the console, or a network connection. This powerful abstraction
simplifies development by eliminating the need to learn a new API for every different device,
leading to more flexible and reusable code.
●​ Code Example: Reading and Writing to Disk Files (Byte Streams)​
import java.io.FileInputStream;​
import java.io.FileOutputStream;​
import java.io.IOException;​

public class ByteStreamFileExample {​
public static void main(String args) {​
// Define file paths​
String sourceFilePath = "source.txt"; // Ensure this file
exists with some content​
String destinationFilePath = "destination_byte.txt";​

// Using try-with-resources to ensure streams are
automatically closed​
try (FileInputStream fis = new
FileInputStream(sourceFilePath);​
FileOutputStream fos = new
FileOutputStream(destinationFilePath)) {​

int byteRead;​
// Read byte by byte from source file until end of
stream (-1)​
while ((byteRead = fis.read())!= -1) {​
fos.write(byteRead); // Write the read byte to the
destination file​
}​
System.out.println("Content copied from " +
sourceFilePath + " to " + destinationFilePath + " using Byte
Streams.");​

} catch (IOException e) {​
System.err.println("An I/O error occurred: " +
e.getMessage());​
e.printStackTrace();​
}​
}​
}​

●​ Code Example: Reading and Writing to Disk Files (Character Streams)​


import java.io.FileReader;​
import java.io.FileWriter;​
import java.io.IOException;​

public class CharacterStreamFileExample {​
public static void main(String args) {​
// Define file paths​
String sourceFilePath = "source.txt"; // Ensure this file
exists with some content​
String destinationFilePath = "destination_char.txt";​

// Using try-with-resources to ensure streams are
automatically closed​
try (FileReader fr = new FileReader(sourceFilePath);​
FileWriter fw = new FileWriter(destinationFilePath))
{​

int charRead;​
// Read character by character from source file until
end of stream (-1)​
while ((charRead = fr.read())!= -1) {​
fw.write(charRead); // Write the read character to
the destination file​
}​
System.out.println("Content copied from " +
sourceFilePath + " to " + destinationFilePath + " using Character
Streams.");​

} catch (IOException e) {​
System.err.println("An I/O error occurred: " +
e.getMessage());​
e.printStackTrace();​
}​
}​
}​

Unit IV: AWT & SWING; Event Handling; JDBC;


Applets; Java Networking
1. AWT & SWING: Frame, Panel, Dialog, CheckBox, Choice, List,
JComboBox, JFrame, JPanel, JRadioButton, JScrollPane,
JTabbedPane
Java provides two primary toolkits for building Graphical User Interfaces (GUIs): AWT (Abstract
Window Toolkit) and Swing. While AWT is an older, platform-dependent toolkit, Swing is a more
modern, platform-independent, and feature-rich library. The syllabus lists components from both,
indicating a need to understand their roles in GUI development.
●​ AWT (Abstract Window Toolkit): AWT is Java's original GUI toolkit. Its components are
"heavyweight," meaning they rely on the underlying operating system's native GUI
components. This leads to a platform-specific look and feel.
○​ Components: Frame, Panel, Dialog, Checkbox, Choice, List.
○​ Frame: A top-level window with a title bar and borders.
○​ Panel: A generic container for organizing other components.
○​ Dialog: A pop-up window, often used for messages or input.
○​ Checkbox: A component that can be in either an "on" (true) or "off" (false) state.
○​ Choice: A pop-up menu of choices.
○​ List: A component that presents a scrollable list of text items.
●​ SWING: Swing is a more advanced GUI toolkit built entirely in Java, which makes its
components "lightweight" and platform-independent. Swing components draw
themselves, providing a consistent look and feel across different operating systems.
Swing offers a richer set of components and more flexible customization options
compared to AWT.
○​ Components: JFrame, JPanel, JRadioButton, JScrollPane, JTabbedPane,
JComboBox [Image 1].
○​ JFrame: Swing's top-level window, analogous to AWT's Frame, but with enhanced
features.
○​ JPanel: A general-purpose lightweight container, similar to AWT's Panel, used for
grouping other components.
○​ JRadioButton: A button that can be selected or deselected, typically used in groups
where only one button can be selected at a time.
○​ JScrollPane: Provides a scrollable view of a component.
○​ JTabbedPane: A component that allows multiple components (usually panels) to be
displayed in a single area, with tabs for navigation.
○​ JComboBox: A combination of a text field and a drop-down list, allowing users to
select from a list or type their own entry.
The evolution from AWT to Swing represents a significant step in Java GUI development. AWT's
reliance on native peer components meant that the appearance and behavior of applications
could vary significantly across different operating systems, which contradicted Java's "Write
Once, Run Anywhere" philosophy for GUI applications. Swing, by rendering its components
entirely in Java, achieved true platform independence in appearance and behavior. This shift
provided developers with a more consistent and powerful toolkit for building rich desktop
applications. While AWT is still part of the Java standard library, Swing is generally preferred for
new GUI development due to its flexibility, richer component set, and consistent look and feel
across platforms.

2. Using Listeners: ActionListener, ContainerListener, FocusListener,


ItemListener, KeyListener, MouseListener, TextListener,
WindowListener
Event handling is a fundamental aspect of interactive GUI applications in Java, allowing
programs to respond to user actions or system events. Java employs the Delegation Event
Model, where events are generated by a "source" component and processed by "listeners" that
are registered to receive specific event types.
●​ Event Handling Mechanism: An event is a change in the state of an object, typically
triggered by user interaction, such as clicking a button, moving the mouse cursor, or
pressing a key on the keyboard. The java.awt.event package provides various event
classes and listener interfaces to manage these actions.
○​ Source: The component that generates an event (e.g., a button, a checkbox, a
window).
○​ Listener: An object that "listens" for and processes specific types of events.
Listener interfaces define methods that must be implemented to handle events. To
handle events, the source component must be registered with a listener using
specific methods, typically in the format addTypeListener() (e.g.,
addActionListener() for ActionEvent). When an event occurs, an event object (a
subclass of EventObject) is automatically created, containing information about the
event source. This event object is then passed to the relevant method of the
registered listener.
The event handling mechanism in Java is an application of the Observer Pattern. In this
pattern, an event source (the "publisher") generates a stream of discrete events, and one
or more listeners (the "subscribers") register their interest in these events. When an event
occurs, the source distributes it to all subscribed listeners by calling their respective
listener methods. This decouples the event generator from the event processor,
promoting modular and flexible design. The listener function itself is an example of a
callback, where the client (the listener) provides a piece of code (the method
implementation) for the module (the event source) to call when a specific event happens.
This contrasts with normal control flow where the client calls the module's functions.
●​ Common Listener Interfaces:
1.​ ActionListener:
■​ Description: This interface is used for receiving action events, which occur
when a component-defined action takes place, such as a button click, a menu
item selection, or pressing Enter in a text field. It has a single method:
actionPerformed(ActionEvent e).
■​ Real-world Analogy: Think of a doorbell button. The button is the event
source. When someone presses it (the action event), it doesn't directly open
the door. Instead, it triggers a "ring" (calls the actionPerformed method) that a
person inside the house (the listener) hears. The person then decides to
open the door. The doorbell itself doesn't know what happens after it rings,
only that it needs to notify someone.
■​ Code Example:​
import javax.swing.*;​
import java.awt.event.ActionEvent;​
import java.awt.event.ActionListener;​

public class ButtonClickExample {​
public static void main(String args) {​
JFrame frame = new JFrame("ActionListener Demo");​
JButton button = new JButton("Click Me!");​
JLabel label = new JLabel("Button not clicked
yet.");​

// Add an ActionListener to the button​
button.addActionListener(new ActionListener() {​
@Override​
public void actionPerformed(ActionEvent e) {​
label.setText("Button was clicked!"); //
Update label text on button click​
}​
});​

frame.setLayout(new FlowLayout()); // Simple
layout manager​
frame.add(button);​
frame.add(label);​
frame.setSize(300, 150);​

frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);​
frame.setVisible(true);​
}​
}​

2.​ MouseListener:
■​ Description: This interface is used for receiving mouse events that occur
when the mouse is not in motion, such as a mouse button being pressed,
released, or clicked, or when the mouse cursor enters or exits a component's
area. It defines five methods: mouseClicked(), mousePressed(),
mouseReleased(), mouseEntered(), and mouseExited().
■​ Real-world Analogy: Imagine a pressure plate on the floor in a smart
home. The plate (component) detects when someone steps on it
(mousePressed), steps off it (mouseReleased), or simply walks over it
(mouseEntered/mouseExited). It doesn't know who stepped on it, but it
triggers a specific action (like turning on a light or sending a notification) when
a mouse event occurs.
■​ Code Example (Conceptual):​
// import java.awt.event.MouseEvent;​
// import java.awt.event.MouseListener;​
// public class MyPanel extends JPanel implements
MouseListener {​
// @Override public void mouseClicked(MouseEvent e) {
/* handle click */ }​
// @Override public void mousePressed(MouseEvent e) {
/* handle press */ }​
// @Override public void mouseReleased(MouseEvent e)
{ /* handle release */ }​
// @Override public void mouseEntered(MouseEvent e) {
/* handle enter */ }​
// @Override public void mouseExited(MouseEvent e) {
/* handle exit */ }​
// }​

3.​ WindowListener:
■​ Description: This interface provides methods for responding to various
window-related events, such as a window being opened, closed, activated,
deactivated, iconified (minimized), or deiconified (restored). It iconified()`.
■​ Real-world Analogy: Consider a security system for a building. The
building's main entrance (the window) has sensors (listeners) that detect
various states: when the door is opened for the first time (windowOpened),
when someone tries to close it (windowClosing), or when it's fully closed
(windowClosed). These sensors trigger specific protocols, like saving data
when a program window closes (windowClosing) or changing a user's status
to "Away" in a chat application when the window is deactivated.
■​ Code Example (Conceptual):​
// import java.awt.event.WindowEvent;​
// import java.awt.event.WindowListener;​
// public class MyFrame extends JFrame implements
WindowListener {​
// @Override public void windowOpened(WindowEvent e)
{ System.out.println("Window opened!"); }​
// @Override public void windowClosing(WindowEvent e)
{ System.out.println("Window is closing...");
System.exit(0); } // Common use​
// @Override public void windowClosed(WindowEvent e)
{ /* handle closed */ }​
// @Override public void windowActivated(WindowEvent
e) { /* handle activated */ }​
// @Override public void
windowDeactivated(WindowEvent e) { /* handle deactivated
*/ }​
// @Override public void windowIconified(WindowEvent
e) { /* handle minimized */ }​
// @Override public void
windowDeiconified(WindowEvent e) { /* handle restored */
}​
// }​

Other listeners mentioned in the syllabus include:


○​ ContainerListener: For events when a component is added to or removed from a
container.
○​ FocusListener: For events related to a component gaining or losing keyboard
focus.
○​ ItemListener: For events indicating an item was selected or deselected (e.g., in a
checkbox or choice).
○​ KeyListener: For events related to key presses, releases, or typing on the
keyboard.
○​ TextListener: For events when an object's text changes.

3. JDBC: Type1 to Type4 drivers


JDBC (Java Database Connectivity) is a Java API that provides a standard way for Java
programs to connect to and interact with relational databases. To achieve this, a specific JDBC
driver, custom-built by database vendors, is required to facilitate the exchange of SQL queries
between a Java application and its particular database.
●​ JDBC Driver Types: There are four distinct types of JDBC drivers, each with different
architectures and use cases.
1.​ Type 1 JDBC Driver (JDBC-ODBC Bridge Driver):
■​ Detailed Explanation: This driver acts as a lightweight facade or bridge
between Java's JDBC calls and ODBC (Open Database Connectivity) calls. It
translates JDBC API calls into ODBC function calls, which then interact with
the database via an ODBC driver. This type requires an ODBC driver to be
installed and configured on the client machine.
■​ Real-World Analogy: Imagine using a universal adapter for electronics
when traveling. The adapter (Type 1 driver) doesn't directly change the
voltage or current, but it allows your device (Java application) to plug into a
different type of outlet (ODBC) that can then connect to the local power grid
(database).
■​ Usage Recommendation: Type 1 drivers are generally not recommended
for production systems due to their reliance on the ODBC bridge, which
can introduce performance overhead and platform dependency. They are
typically used for development or testing, or when no other driver type is
available for a specific database (e.g., Microsoft Access).
■​ Code Example (Conceptual):​
// Example of loading a Type 1 driver for MS Access
(requires DSN setup)​
// Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");​
// Connection con =
DriverManager.getConnection("jdbc:odbc:myAccessDSN");​

2.​ Type 2 JDBC Driver (Native-API Driver / Partly Java Driver):


■​ Detailed Explanation: This driver converts JDBC calls into calls on the
client-side native API of the database. It is typically written in a language
other than Java, such as C++ or C, and requires the installation of
vendor-specific client-side libraries (native API) on each client machine.
■​ Real-World Analogy: Consider communicating with someone who speaks a
different language. Instead of learning their language, you hire a specialized
interpreter (Type 2 driver) who is an expert in both your language and theirs,
and they use their own specific tools (native API libraries) to facilitate the
conversation.
■​ Usage Recommendation: Type 2 drivers are often used in production
systems, especially when high performance is critical or when connecting to
non-standard systems. Examples include IBM DB2's Type 2 driver, known for
its speed and efficiency.
■​ Code Example (Conceptual - driver class depends on DB vendor):​
// Example for IBM DB2 Type 2 driver (requires DB2 client
libraries)​
// Class.forName("com.ibm.db2.jcc.DB2Driver");​
// Connection con =
DriverManager.getConnection("jdbc:db2:databaseName",
"user", "password");​

3.​ Type 3 JDBC Driver (Network Protocol Driver / Middleware Driver / Fully Java
Driver):
■​ Detailed Explanation: This driver is written purely in Java but does not send
SQL calls directly to the database. Instead, it communicates with a separate
middleware application server using a specific network protocol. This
middleware server then marshals the requests back and forth to the target
database, potentially using different driver types (Type 1, 2, or 4) to connect
to various databases.
■​ Real-World Analogy: Imagine a call center. You (Java application) call the
call center (middleware server), and an agent at the call center (Type 3
driver) then connects to the specific department (database) to retrieve
information. You do not directly interact with the department; the call center
acts as an intermediary.
■​ Usage Recommendation: Type 3 drivers are useful when a single driver is
needed to connect to multiple different databases, as the middleware handles
the specific database protocols. They can also provide features like
server-side security or two-phase transactional commits.
■​ Code Example (Conceptual - driver class and URL depend on
middleware vendor):​
// Example for a generic Type 3 driver​
// Class.forName("com.​

Works cited

1. JDK vs JRE vs JVM in Java: Key Differences Explained | DigitalOcean,


https://www.digitalocean.com/community/tutorials/difference-jdk-vs-jre-vs-jvm 2. Difference
Between JDK, JRE, And JVM Explained (With Analogy ...,
https://unstop.com/blog/difference-between-jdk-jre-and-jvm 3. Differences Between JDK, JRE
and JVM - GeeksforGeeks, https://www.geeksforgeeks.org/java/differences-jdk-jre-jvm/ 4. Unit
Two - Java Architecture and OOPS | PPT - SlideShare,
https://www.slideshare.net/slideshow/unit-two-java-architecture-and-oops/279142363 5. Java
Classes and Objects - DataCamp, https://www.datacamp.com/doc/java/classes-and-objects 6.
Classes and Objects in Java - GeeksforGeeks,
https://www.geeksforgeeks.org/java/classes-objects-java/ 7. Java Classes and Objects -
CodeGym, https://codegym.cc/groups/posts/java-classes-and-objects 8. What is static or
non-Static in Java: Concepts and use cases,
https://www.pluralsight.com/resources/blog/software-development/static-non-static-java-overvie
w 9. Java Constructor Tutorial: Learn Basics and Best Practices ...,
https://www.digitalocean.com/community/tutorials/constructor-in-java 10. Full Java constructors
tutorial | TheServerSide, https://www.theserverside.com/video/Full-Java-constructors-tutorial 11.
static Keyword in Java: Usage & Examples - DataCamp,
https://www.datacamp.com/doc/java/static 12. Static Keyword in Java (with Example) -
Geekster, https://www.geekster.in/articles/static-keyword-in-java/ 13. Learn How To Return
ArrayList In Java With Detailed Examples ...,
https://unstop.com/blog/how-to-return-arraylist-in-java 14. ArrayList vs LinkedList in Java -
Scaler Topics, https://www.scaler.com/topics/arraylist-vs-linkedlist/ 15. collections - What is a
practical, real world example of the Linked ...,
https://stackoverflow.com/questions/644167/what-is-a-practical-real-world-example-of-the-linked
-list 16. Java IO - Input/Output in Java with Examples - GeeksforGeeks,
https://www.geeksforgeeks.org/java/java-io-input-output-in-java-with-examples/ 17. InputStream
and OutputStream Classes | Coding Shuttle,
https://www.codingshuttle.com/java-programming-handbook/input-stream-and-output-stream-cla
sses 18. 1. Introducing I/O - Java I/O, 2nd Edition [Book] - O'Reilly Media,
https://www.oreilly.com/library/view/java-io-2nd/0596527500/ch01.html 19. Java bufferedreader
and java bufferedwriter - CodeGym,
https://codegym.cc/groups/posts/bufferedreader-and-bufferedwriter 20. ICSE Class 10
Computer Applications Chapter 10 – Input/Output in Java and Section – Introduction To I/o In
Java - AllRounder.ai,
https://allrounder.ai/icse-class-10-computer-applications/inputoutput-in-java/introduction-to-io-in-
java-101-lesson-682ec1 21. Types of Inheritance in Java - Shiksha,
https://www.shiksha.com/online-courses/articles/types-of-inheritance-in-java-blogId-156091 22.
Extends Keyword In Java | Syntax, Uses & More (+Examples) // Unstop,
https://unstop.com/blog/extends-keyword-in-java 23. Java: Abstract classes and abstract
methods | I'd Rather Be Writing ..., https://idratherbewriting.com/java-abstract-methods/ 24. 15
Differences Between Overloading And Overriding In Java // Unstop,
https://unstop.com/blog/difference-between-overloading-and-overriding-in-java 25. Java
Interface - GeeksforGeeks, https://www.geeksforgeeks.org/interfaces-in-java/ 26. Method
Overloading In Java Explained (With Code Examples ...,
https://unstop.com/blog/method-overloading-in-java 27. What is Abstraction in Java?
Advantages, Types & Examples - NxtWave,
https://www.ccbp.in/blog/articles/what-is-abstraction-in-java 28. Separated Interface Pattern in
Java: Streamlining Java Development with Interface Isolation,
https://java-design-patterns.com/patterns/separated-interface/ 29. Top 10 Differences Between
Error & Exception In Java (+Codes ..., https://unstop.com/blog/error-vs-exception-in-java 30.
Exception Handling in Java - Interview Guides - Your Interview ...,
https://interview.in28minutes.com/interview-guides/java/exception-handling-in-java/ 31.
Multithreading In Java - A Complete Guide With Code Examples ...,
https://unstop.com/blog/multithreading-in-java 32. Thread In Java | Create, Methods, Priority &
More (+Examples ..., https://unstop.com/blog/thread-in-java 33. Thread Synchronization In Java
| Uses, Types & More(+Codes ..., https://unstop.com/blog/thread-synchronization-in-java 34.
Importance of Thread Synchronization in Java - GeeksforGeeks,
https://www.geeksforgeeks.org/importance-of-thread-synchronization-in-java/ 35. Difference
Between Byte Stream And Character Stream In Java - JustAcademy,
https://www.justacademy.co/blog-detail/difference-between-byte-stream-and-character-stream-in
-java 36. What are the main differences between byte streams and character streams in Java?,
https://forum-external.crio.do/t/what-are-the-main-differences-between-byte-streams-and-charac
ter-streams-in-java/460 37. Byte Stream in Java: Everything You Need to Know - upGrad,
https://www.upgrad.com/tutorials/software-engineering/java-tutorial/byte-stream-in-java/ 38.
Chapter 8 -- Java Input/Output and Networking - CSE IIT KGP,
https://cse.iitkgp.ac.in/~dsamanta/java/ch8.htm 39. ICSE Class 10 Computer Applications
Chapter 11 – Basic File Handling and Section,
https://allrounder.ai/icse-class-10-computer-applications/basic-file-handling/classes-used-for-file-
handling-113-lesson-682ec1 40. Event Handling in Java - GeeksforGeeks,
https://www.geeksforgeeks.org/java/event-handling-in-java/ 41. Swing Event Listeners -
Tutorialspoint, https://www.tutorialspoint.com/swing/swing_event_listeners.htm 42. Reading 25:
Callbacks - MIT, https://web.mit.edu/6.031/www/fa20/classes/25-callbacks/ 43. Java
ActionListener in AWT - GeeksforGeeks,
https://www.geeksforgeeks.org/advance-java/java-actionlistener-in-awt/ 44. Java MouseListener
| Examples to implement Java MouseListener, https://www.educba.com/java-mouselistener/ 45.
MouseListener and MouseMotionListener in Java - GeeksforGeeks,
https://www.geeksforgeeks.org/mouselistener-mousemotionlistener-java/ 46. Java
WindowListener in AWT - GeeksforGeeks,
https://www.geeksforgeeks.org/java-windowlistener-in-awt/ 47. Java WindowListener |
Comprehensive Guide to Java WindowListener, https://www.educba.com/java-windowlistener/
48. 11. jdbc | PPT - SlideShare, https://www.slideshare.net/slideshow/11-jdbc/31924045 49.
JDBC (Java Database Connectivity) - GeeksforGeeks,
https://www.geeksforgeeks.org/java/introduction-to-jdbc/ 50. Java's 4 JDBC driver types
explained - The Server Side,
https://www.theserverside.com/blog/Coffee-Talk-Java-News-Stories-and-Opinions/JDBC-Driver-
Types-Explained 51. JDBC – Type 3 Driver - GeeksforGeeks,
https://www.geeksforgeeks.org/jdbc-type-3-driver/

You might also like