Java Object-Oriented Programming, Packages, Interfaces, I/O Streams, and Exception Handling Study Guide
Hierarchical Abstractions and Inheritance Core
Hierarchical abstraction is a foundational concept in software design and computer programming. It involves structuring a codebase or system into a layered or hierarchical format to manage complexity by breaking the system down into distinct levels of abstraction. Each level represents a specific degree of detail, promoting modularity, reusability, and code maintainability.
Key Aspects of Hierarchical Abstraction
- High-Level Abstraction: Positioned at the top of the hierarchy, high-level abstractions represent the system's overall functionality and core purpose. This level focuses on broader goals and system architecture.
- Intermediate-Level Abstraction: Located below the high-level layer, intermediate-level abstractions break down system-wide goals into modular components designed to handle specific tasks or subset functionalities.
- Low-Level Abstraction: Placed at the bottom of the hierarchy, low-level abstractions handle specific implementation details, including granular algorithms, concrete data structures, and hardware- or type-specific operations.
In Java, hierarchical abstraction is realized through classes and interfaces:
// High-level abstraction
interface Shape {
void draw();
}
// Intermediate-level abstraction
abstract class TwoDimensionalShape implements Shape {
// Common properties or methods for 2D shapes
}
// Low-level abstraction
class Circle extends TwoDimensionalShape {
@Override
public void draw() {
System.out.println("Drawing a circle");
}
}
class Square extends TwoDimensionalShape {
@Override
public void draw() {
System.out.println("Drawing a square");
}
}
Here, Shape serves as the high-level abstraction, TwoDimensionalShape serves as an intermediate abstraction, and Circle and Square serve as low-level concrete abstractions. Higher levels define common behaviors, while lower levels supply concrete implementations.
Base Class Objects
Creating an instance of a base class (interchangeably referred to as a superclass or parent class) instantiates the primary parent entity in an inheritance structure.
// Base class (superclass)
class Animal {
void makeSound() {
System.out.println("Some generic sound");
}
}
// Subclass (derived class)
class Dog extends Animal {
void bark() {
System.out.println("Woof! Woof!");
}
}
public class Main {
public static void main(String[] args) {
// Creating an object of the base class
Animal genericAnimal = new Animal();
genericAnimal.makeSound();
// Creating an object of the subclass
Dog myDog = new Dog();
myDog.makeSound(); // Inherited from Animal
myDog.bark(); // Specific to Dog
}
}
Base class instances have access solely to fields and methods defined within that base class, whereas subclass instances inherit all accessible elements from the parent class in addition to their own specific members.
Substitutability, Subtype, and Subclass
Substitutability
Substitutability allows a variable declared as a superclass type to hold a reference to an instance of any of its subclasses. While conventional structural languages like C cannot perform substitutability, object-oriented languages like Java support it inherently.
Substitutability is satisfied under three strict conditions:
- Subclass instances must contain all fields (instance variables) defined in the superclass.
- Subclass instances must implement, through inheritance, all functionality defined for the superclass.
- Subclass instances must retain all behavioral characteristics of the superclass, allowing them to stand in for superclass objects without breaking program operations.
Subtype
Type is defined as a subtype of type (where and are classes or interfaces) if:
- An instance of type can be legally assigned to a variable of type
- The instance of type can be accessed via the variable of type without any observable deviation in expected behavior.
Subclass
A class that inherits characteristics and behavior from another class is called a subclass, identified in Java code by the extends keyword.
abstract class Shape {
int dim1, dim2;
Shape(int x, int y) {
dim1 = x;
dim2 = y;
}
abstract void area();
abstract void volume();
}
class Triangle extends Shape {
Triangle(int x, int y) {
super(x, y);
}
void area() {
System.out.println("Area of triangle is: " + (dim1 * dim2) / 2);
}
void volume() {
System.out.println("Volume of the triangle");
}
}
class Rectangle extends Shape {
Rectangle(int x, int y) {
super(x, y);
}
void area() {
System.out.println("Area of rectangle is: " + (dim1 + dim2));
}
void volume() {
System.out.println("Volume of the rectangle");
}
}
class ShapeDemo {
public static void main(String args[]) {
Shape s;
s = new Triangle(10, 20);
s.area();
s.volume();
s = new Rectangle(20, 40);
s.area();
s.volume();
}
}
Execution Output:
Area of triangle is: 100
Volume of the triangle
Area of rectangle is: 60
Volume of the rectangle
In this example, Shape is the superclass, and Triangle and Rectangle are subclasses and subtypes. Objects of Triangle and Rectangle are substituted into the superclass reference s seamlessly.
Forms of Inheritance
Inheritance in Java manifests in six distinct forms, each serving unique design purposes:
- Specialization: The most ideal form of inheritance. The subclass is a specialized child entity of the parent class and fully adheres to the principle of substitutability.
- Specification: The parent class defines required method signatures without implementing them, delegating logic to the child class (supported via
abstractclasses andinterfacestructures). Holds substitutability. - Construction: The child class modifies parent class behaviors significantly via overriding, often breaking the standard substitutability expectations.
- Extension: The child class adds entirely new properties or behaviors while leaving parent functionality unchanged. Holds substitutability.
- Limitation: The child class restricts or disables inherited parent behaviors. Violates substitutability.
- Combination: A child class inherits properties from multiple parent classes (Multiple Inheritance). Java does not support combination inheritance using classes.
Benefits and Costs of Inheritance
Benefits
- Code Reusability: Child classes reuse parent code without duplication.
- Time and Effort Savings: Core execution logic is written once and shared across hierarchy levels.
- Clear Structure: Establishes organized, logical models.
- Reduced Costs: Lowers application development and long-term maintenance costs.
- Method Overriding: Allows specialized behavioral implementations in derived classes.
- Data Encapsulation: Parent classes can restrict sensitive data access using
privatemodifiers.
Costs
- Decreased Execution Speed: Method execution incurs runtime lookup overhead when navigating class hierarchies.
- Tight Coupling: Parent and child classes become mutually dependent; changes to parent structures ripple through child classes.
- Increased Complexity: Excessive inheritance structures make software systems complex and difficult to trace.
Member Access Control, Super, and Final Keywords
Member Access Rules
Access modifiers govern whether a subclass or external class can access, read, or modify parent properties.
Levels of Access Control
- Top Level (Class Level):
publicor package-private (default/no modifier). - Member Level:
public,private,protected.
- private: Accessible only within the declaring class. Variables, methods, or constructors marked
privatecannot be accessed by subclasses or outer classes.
class A {
private int x = 10;
}
class B extends A {
int y = 20;
void display() {
// System.out.println(x); // Illegal: x has private access in A
}
}
If a constructor is marked private, external instantiation is blocked entirely:
class A {
int x;
private A(int k) {
x = k;
}
}
class Test {
public static void main(String args[]) {
// A ob = new A(10); // Compile-time error: A(int) has private access in A
}
}
- protected: Accessible within the same package, and by subclasses located in external packages.
- public: Accessible universally across all packages and classes.
Access Control Protection Matrix
| Access Modifier | Within Class | Within Package | Outside Package by Subclass Only | Outside Package |
|---|---|---|---|---|
| Private | Yes | No | No | No |
| Default (No modifier) | Yes | Yes | No | No |
| Protected | Yes | Yes | Yes | No |
| Public | Yes | Yes | Yes | Yes |
Uses of the super Keyword
The super keyword resolves variable and method naming conflicts between parent and child classes and explicitly handles parent construction. It can only be used inside child classes.
1. Referencing Parent Class Data Members
class ParentClass {
int num = 10;
}
class ChildClass extends ParentClass {
int num = 20;
void showData() {
System.out.println("Inside the ChildClass");
System.out.println("ChildClass num = " + num);
System.out.println("ParentClass num = " + super.num);
}
}
public class SuperKeywordExample {
public static void main(String[] args) {
ChildClass obj = new ChildClass();
obj.showData();
System.out.println("\nInside the non-child class");
System.out.println("ChildClass num = " + obj.num);
// super.num cannot be referenced here
}
}
2. Referencing Parent Class Methods
class ParentClass {
int num1 = 10;
void showData() {
System.out.println("\nInside the ParentClass showData method");
System.out.println("ChildClass num = " + num1);
}
}
class ChildClass extends ParentClass {
int num2 = 20;
void showData() {
System.out.println("\nInside the ChildClass showData method");
System.out.println("ChildClass num = " + num2);
super.showData(); // Invokes parent version of showData()
}
}
public class SuperKeywordExample {
public static void main(String[] args) {
ChildClass obj = new ChildClass();
obj.showData();
}
}
3. Calling Parent Class Constructors
When a child class object is instantiated, it automatically calls the parent class default constructor first. However, to execute a parameterized parent constructor, super(arguments) must be explicitly called as the very first statement inside the child class constructor.
class ParentClass {
int num1;
ParentClass() {
System.out.println("\nInside the ParentClass default constructor");
num1 = 10;
System.out.println("\nnum1= " + num1);
}
ParentClass(int value) {
System.out.println("\nInside the ParentClass parameterized constructor");
num1 = value;
System.out.println("\nnum1= " + num1);
}
}
class ChildClass extends ParentClass {
int num2;
ChildClass() {
super(100); // Explicit call to ParentClass(int)
System.out.println("\nInside the ChildClass constructor");
num2 = 200;
System.out.println("\nnum2= " + num2);
System.out.println("\nFrom Parent Class num1= " + super.num1);
}
}
public class SuperKeywordExample {
public static void main(String[] args) {
ChildClass obj = new ChildClass();
}
}
Using final with Inheritance
The final keyword restricts modification across variables, methods, and classes.
1. final Variables (Constants)
Prevents reassignment. Must be initialized upon declaration or inside constructors.
public class FinalVariableExample {
public static void main(String[] args) {
final int a = 10;
System.out.println("a = " + a);
// a = 100; // Compile-time Error: cannot assign a value to final variable a
}
}
2. final Methods
Prevents method overriding by subclasses.
class ParentClass {
int num = 10;
final void showData() {
System.out.println("Inside ParentClass showData() method");
System.out.println("num = " + num);
}
}
class ChildClass extends ParentClass {
// void showData() { } // Error: showData() in ChildClass cannot override showData() in ParentClass
}
3. final Classes
Prevents class inheritance entirely.
final class ParentClass {
int num = 10;
}
// class ChildClass extends ParentClass { } // Error: cannot inherit from final ParentClass
Polymorphism, Method Overriding, and Abstract Classes
Dynamic Method Dispatch and Method Overriding
Polymorphism allows defining a single method interface with multiple distinct implementations. In Java, runtime polymorphism is achieved through method overriding.
Method Overriding
When a child class redefines a method inherited from a parent class with the exact same signature (name, parameters, return type), the child method overrides the parent method. Method binding occurs at runtime, known as late binding, dynamic method dispatch, or pure polymorphism.
class ParentClass {
int num = 10;
void showData() {
System.out.println("Inside ParentClass showData() method");
System.out.println("num = " + num);
}
}
class ChildClass extends ParentClass {
@Override
void showData() {
System.out.println("Inside ChildClass showData() method");
System.out.println("num = " + num);
}
}
public class PurePolymorphism {
public static void main(String[] args) {
ParentClass obj = new ParentClass();
obj.showData(); // Calls ParentClass showData()
obj = new ChildClass();
obj.showData(); // Calls ChildClass showData() dynamically
}
}
10 Rules for Method Overriding
- Static methods cannot be overridden.
- Final methods cannot be overridden.
- Private methods cannot be overridden.
- Constructors cannot be overridden.
- Abstract methods declared in a parent class must be overridden by concrete child classes.
- Use the
superkeyword to invoke a parent overridden method from within the child class. - The return type of the overriding method must be identical to that of the parent class method.
- The access specifier visibility can be expanded, but never restricted (e.g., a
protectedmethod in a parent class can becomepublicin the child class, but notprivateor default). - If the parent method throws no exceptions, the overriding child method can only throw unchecked runtime exceptions.
- If the parent method throws exceptions, the overriding child method can throw the same exceptions, subclass exceptions, or no exceptions at all, but cannot throw broader or new checked exceptions.
Abstract Classes
An abstract class is declared using the abstract keyword. It can contain abstract methods (methods without a body) and concrete methods (methods with a body).
abstract class ClassName {
// Abstract and non-abstract methods
}
Abstract Class Implementation Example
import java.util.Scanner;
abstract class Shape {
int length, breadth, radius;
Scanner input = new Scanner(System.in);
abstract void printArea();
}
class Rectangle extends Shape {
void printArea() {
System.out.println("*** Finding the Area of Rectangle ***");
System.out.print("Enter length and breadth: ");
length = input.nextInt();
breadth = input.nextInt();
System.out.println("The area of Rectangle is: " + (length * breadth));
}
}
class Triangle extends Shape {
void printArea() {
System.out.println("\n*** Finding the Area of Triangle ***");
System.out.print("Enter Base And Height: ");
length = input.nextInt();
breadth = input.nextInt();
System.out.println("The area of Triangle is: " + (length * breadth) / 2);
}
}
class Circle extends Shape {
void printArea() {
System.out.println("\n*** Finding the Area of Circle ***");
System.out.print("Enter Radius: ");
radius = input.nextInt();
System.out.println("The area of Circle is: " + (3.14f * radius * radius));
}
}
public class AbstractClassExample {
public static void main(String[] args) {
// Referencing subclass instances using Abstract Base Reference
Shape obj = new Rectangle();
obj.printArea();
obj = new Triangle();
obj.printArea();
obj = new Circle();
obj.printArea();
}
}
8 Rules for Abstract Classes
- Must be declared explicitly using the
abstractkeyword. - Can be instantiated as a reference type, but cannot be instantiated using
newdirectly. - Can exist without containing any abstract methods.
- Can contain both abstract and concrete (non-abstract) methods.
- Can contain
finalmethods (which cannot be overridden). - Can contain
staticmethods, but abstract methods themselves cannot be declared static. - Can contain constructors, which execute when child instances are created.
- Any subclass extending an abstract class must override all abstract methods; otherwise, the subclass itself must be declared
abstract.
The Java Object Class, Packages, and Access Protection
The java.lang.Object Class
The Object class sits at the root of the Java class hierarchy. Every class in Java directly or indirectly derives from Object. If a class does not use extends, it inherits directly from java.lang.Object.
Built-in Methods of the Object Class
| Method Signature | Description |
|---|---|
public final Class getClass() | Returns the runtime Class object, providing class metadata. |
public int hashCode() | Returns a hashcode memory address identity integer for the object. |
public boolean equals(Object obj) | Compares equality between the target object and argument object. |
protected Object clone() throws CloneNotSupportedException | Creates and returns an exact field-by-field copy of the object. |
public String toString() | Returns the textual string representation of the object. |
public final void notify() | Wakes up a single thread waiting on the object's monitor. |
public final void notifyAll() | Wakes up all threads waiting on the object's monitor. |
public final void wait(long timeout) throws InterruptedException | Causes current thread to wait until notified or timeout expires. |
public final void wait(long timeout, int nanos) throws InterruptedException | Causes thread to wait for millisecond and nanosecond intervals. |
public final void wait() throws InterruptedException | Suspends thread indefinitely until another thread calls notify() or notifyAll(). |
protected void finalize() throws Throwable | Invoked by the Garbage Collector prior to object garbage collection. |
Java Packages
A package is a grouped container of related classes, interfaces, and sub-packages.
Categories of Packages
- Predefined (Built-in) Packages: Provided by Java standard API:
java.lang: Language basics, primitive wrappers, system functions, thread models.java.util: Collection framework, event models, date structures, utilities.java.io: Input/output handling, streams, file operations.java.awt: Graphical User Interface components.java.time: Date, time, instant, and duration measurement APIs.
- User-Defined Packages: Created by developers to structure project code modules.
Package Syntax and Compilation
To assign a file to a package, place package packagename; at the absolute top of the source file.
package mypack;
public class A {
public void show() {
System.out.println("Sum method");
}
}
Compilation Command:
javac -d . A.java
The -d flag directs the compiler to build directory folders corresponding to the declared package structure (. specifies the current directory). Note: Package source files containing no main() method cannot be executed directly; they are imported by execution programs.
import mypack.A;
public class Hello {
public static void main(String args[]) {
A a = new A();
a.show();
System.out.println("show() class A");
}
}
Setting CLASSPATH
CLASSPATH informs the Java runtime environment where to locate user-defined classes and external JAR files:
- Permanently in Windows OS:
- System Properties -> Advanced -> Environment Variables -> New/Edit
CLASSPATH. - Example Value:
.;c:\javaproject\classes;d:\tomcat\lib\servlet-api.jar(Includes current directory.).
- System Properties -> Advanced -> Environment Variables -> New/Edit
- Temporarily in Command Shell:
SET CLASSPATH=.;c:\javaproject\classes;d:\tomcat\lib\servlet-api.jar
```
3. **Direct Command Line Flag:**
bash java -classpath c:\javaproject\classes com.abc.project1.subproject2.MyClass3 ```
Package Access Protection Matrix
| Access Specifier | Same Class | Same Package Subclass | Same Package Non-subclass | Different Package Subclass | Different Package Non-subclass |
|---|---|---|---|---|---|
| private | Yes | No | No | No | No |
| No Modifier (Default) | Yes | Yes | Yes | No | No |
| protected | Yes | Yes | Yes | Yes | No |
| public | Yes | Yes | Yes | Yes | Yes |
Interfaces and Nested Interfaces
Core Principles of Interfaces
An interface in Java is a reference type containing public static final constants and abstract method signatures, providing complete abstraction and multi-inheritance capability.
Abstract Class vs. Interface Comparison
| Feature | Abstract Class | Interface |
|---|---|---|
| Content | Abstract and concrete methods. | Purely abstract methods (standard Java). |
| Scope | Common code reuse within specific class lineages. | Universal contract usable across disparate class types. |
| Multiple Inheritance | Not supported. | Fully supported. |
| Keyword | Defined using abstract. | Defined using interface. |
| Variables | Can hold instance variables and constants. | Holds public static final constants only. |
| Default Access Modifier | Default package-private for methods. | public implicitly for all methods. |
| Inheritance Keyword | Extended using extends. | Implemented using implements. |
| Constructor | Can define constructors. | Cannot define constructors. |
| Variable Initialization | Optional at declaration time. | Compulsory at declaration time. |
| Variable Modifiers | No modifier restrictions. | Cannot be private, protected, transient, volatile. |
| Method Modifiers | No modifier restrictions. | Cannot be strictfp, protected, static, native, private, final, synchronized. |
Defining and Implementing Interfaces
interface A {
public static final int a = 10;
void display();
}
class B implements A {
public void display() {
System.out.println("Hello");
}
}
class InterfaceDemo {
public static void main(String[] args) {
B obj = new B();
obj.display();
System.out.println(A.a);
}
}
Practical Implementation: IntStack Structure
// IntStack.java
interface IntStack {
void push(int item);
int pop();
}
class FixedStack implements IntStack {
private int stck[];
private int top;
FixedStack(int size) {
stck = new int[size];
top = -1;
}
public void push(int item) {
if (top == stck.length - 1)
System.out.println("Stack is full.");
else
stck[++top] = item;
}
public int pop() {
if (top == -1) {
System.out.println("Stack underflow.");
return 0;
} else {
return stck[top--];
}
}
}
class InterfaceTest {
public static void main(String args[]) {
FixedStack mystack1 = new FixedStack(5);
FixedStack mystack2 = new FixedStack(8);
for (int i = 0; i < 5; i++) mystack1.push(i);
for (int i = 0; i < 8; i++) mystack2.push(i);
for (int i = 0; i < 5; i++) System.out.println(mystack1.pop());
for (int i = 0; i < 8; i++) System.out.println(mystack2.pop());
}
}
Interface Variables and Shared Constants
Variables in interfaces are implicitly public static final. They import global constants into implementing classes cleanly.
import java.io.*;
interface SharedConstants {
int NO = 0;
int YES = 1;
int MAYBE = 2;
int LATER = 3;
int NEVER = 4;
}
class Question implements SharedConstants {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
int ask() throws IOException {
System.out.println("Would you like to have a cup of coffee?");
String ans = br.readLine();
if (ans.equals("no")) return NO;
else if (ans.equals("yes")) return YES;
else if (ans.equals("notnow")) return LATER;
else return NEVER;
}
}
class AskMe {
public static void main(String args[]) throws IOException {
Question q = new Question();
System.out.println(q.ask());
}
}
Extending Interfaces
Interfaces inherit other interfaces using extends. Implementing classes must fulfill all method obligations across the entire inheritance chain.
interface A {
void meth1();
void meth2();
}
interface B extends A {
void meth3();
}
class MyClass implements B {
public void meth1() { System.out.println("Implement meth1()."); }
public void meth2() { System.out.println("Implement meth2()."); }
public void meth3() { System.out.println("Implement meth3()."); }
}
class InterfaceDemo {
public static void main(String arg[]) {
MyClass ob = new MyClass();
ob.meth1();
ob.meth2();
ob.meth3();
}
}
Nested Interfaces
An interface declared inside another interface or class is a nested interface. Nested interfaces are implicitly static.
- Nested inside an interface: Must be declared
publicimplicitly/explicitly. - Nested inside a class: May use any valid access modifier (
private,protected,public, default).
// Syntax inside interface
interface OuterInterface {
interface NestedInterface {
void msg();
}
}
// Syntax inside class
class OuterClass {
interface NestedInterface {
void msg();
}
}
Nested Interface Code Examples
// Example 1: Nested Interface inside Interface
interface Showable {
void show();
interface Message {
void msg();
}
}
class TestNestedInterface1 implements Showable.Message {
public void msg() {
System.out.println("Hello nested interface");
}
public static void main(String args[]) {
Showable.Message message = new TestNestedInterface1();
message.msg();
}
}
// Example 2: Nested Interface inside Class
class A {
interface Message {
void msg();
}
}
class TestNestedInterface2 implements A.Message {
public void msg() {
System.out.println("Hello nested interface");
}
public static void main(String args[]) {
A.Message message = new TestNestedInterface2();
message.msg();
}
}
Java I/O Streams and Input/Output Processing
Java handles I/O operations through Streams, defined in java.io. A stream represents a continuous flow of data linked to underlying physical devices.
Stream Categories
- Byte Stream: Handles 8-bit raw byte input/output operations.
- Character Stream: Handles 16-bit Unicode character operations for global internationalization.
Java Byte Stream Classes
Derived from abstract superclasses InputStream and OutputStream.
| Stream Class | Description |
|---|---|
BufferedInputStream | Implements buffered byte input stream handling. |
BufferedOutputStream | Implements buffered byte output stream handling. |
DataInputStream | Reads Java primitive datatypes from streams. |
DataOutputStream | Writes Java primitive datatypes to streams. |
FileInputStream | Input stream for reading bytes from disk files. |
FileOutputStream | Output stream for writing bytes to disk files. |
InputStream | Abstract class describing byte stream input. |
OutputStream | Abstract class describing byte stream output. |
PrintStream | Output stream containing print() and println() functions. |
Primary methods: read() (reads bytes) and write() (writes bytes).
Java Character Stream Classes
Derived from abstract superclasses Reader and Writer.
| Stream Class | Description |
|---|---|
BufferedReader | Handles buffered character input streams. |
BufferedWriter | Handles buffered character output streams. |
FileReader | Character input stream reading from disk files. |
FileWriter | Character output stream writing to disk files. |
InputStreamReader | Bridge translating raw bytes to Unicode characters. |
OutputStreamReader | Bridge translating Unicode characters to raw bytes. |
PrintWriter | Output stream containing character print() and println(). |
Reader | Abstract class defining character stream input. |
Writer | Abstract class defining character stream output. |
Console Input Processing
Uses BufferedReader wrapped around InputStreamReader(System.in).
import java.io.*;
// Character Reading Example
class CharRead {
public static void main(String args[]) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
char c = (char) br.read(); // Reads single character
}
}
// String Reading Example
class MyInput {
public static void main(String[] args) throws IOException {
String text;
InputStreamReader isr = new InputStreamReader(System.in);
BufferedReader br = new BufferedReader(isr);
text = br.readLine(); // Reads full text string
System.out.println(text);
}
}
File Processing Examples
import java.io.*;
// Reading File Content
class ReadTest {
public static void main(String[] args) {
try {
File fl = new File("d:/myfile.txt");
BufferedReader br = new BufferedReader(new FileReader(fl));
String str;
while ((str = br.readLine()) != null) {
System.out.println(str);
}
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
// Writing String Content to File
class WriteTest {
public static void main(String[] args) {
try {
File fl = new File("d:/myfile.txt");
String str = "Write this string to my file";
FileWriter fw = new FileWriter(fl);
fw.write(str);
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Exception Handling Fundamentals and Exception Models
An exception is an abnormal condition occurring during program execution, interrupting normal control flow. Causes include missing files, invalid user input, loss of network connectivity, array index out-of-bounds access, or hardware failures.
Handling exceptions prevents abnormal program termination.
Exception Class Hierarchy

All exceptions stem from java.lang.Throwable, which splits into Error (irrecoverable conditions) and Exception (recoverable conditions).
Exception Categories
- Checked Exceptions: Checked by the compiler at compile time. The program must handle them or declare them using
throws. They derive directly fromjava.lang.Exception(excludingRuntimeException). Examples:IOException,FileNotFoundException,ClassNotFoundException,SQLException,DataAccessException,InstantiationException,UnknownHostException. - Unchecked Exceptions: Occur at runtime and bypass compiler verification. They derive from
java.lang.RuntimeException. Examples:ArithmeticException,NullPointerException,NumberFormatException,ArrayIndexOutOfBoundsException,StringIndexOutOfBoundsException.
Exception Models in Java
- Termination Model: Default Java behavior. When an exception occurs, method execution terminates, and control transfers to the nearest handling
catchblock. The faulting method cannot resume execution at the point of failure. - Resumptive Model: The exception handler fixes the issue, then retries the faulting operation. In Java, this is emulated using
whileloops containingtry-catchstructures that repeatedly execute until the operation succeeds.
Uncaught Exceptions
If exceptions are unhandled, execution halts, and the default exception handler prints an error stack trace. The JVM invokes dispatchUncaughtException() on the faulting thread and terminates it.
import java.util.Scanner;
public class UncaughtExceptionExample {
public static void main(String[] args) {
Scanner read = new Scanner(System.in);
System.out.println("Enter the a and b values: ");
int a = read.nextInt(); // Example input: 10
int b = read.nextInt(); // Example input: 0
int c = a / b; // Triggers ArithmeticException
System.out.println(a + "/" + b + " = " + c);
}
}
Output:
Exception in thread "main" java.lang.ArithmeticException: / by zero
at UncaughtExceptionExample.main(UncaughtExceptionExample.java:11)
Exception Handling Keywords and Control Flow
Java exception handling relies on five keywords: try, catch, finally, throw, and throws.
try {
// Code to be tested for exceptions
} catch (ExceptionType object) {
// Code for handling the exception
}
Basic Try-Catch Handling
import java.util.Scanner;
public class TryCatchExample {
public static void main(String[] args) {
Scanner read = new Scanner(System.in);
System.out.println("Enter the a and b values: ");
try {
int a = read.nextInt();
int b = read.nextInt();
int c = a / b;
System.out.println(a + "/" + b + " = " + c);
} catch (ArithmeticException ae) {
System.out.println("Problem info: Value of divisor can not be ZERO");
}
}
}
Multiple Catch Clauses
A try block can be followed by multiple catch blocks to handle different exception types.
Rules for Multiple Catch Blocks
- A
tryblock generates only one exception at a time; at most onecatchblock executes. catchblocks must be ordered from most specific to most general.catch (Exception e)must be placed last. Placing a general parent catch block before a specific child catch block causes a compile error.
public class MultipleCatchExample {
public static void main(String[] args) {
try {
int list[] = new int[5];
list[2] = 10;
list[4] = 2;
list[10] = list[2] / list[4]; // Triggers ArrayIndexOutOfBoundsException
} catch (ArithmeticException ae) {
System.out.println("Problem info: Value of divisor can not be ZERO.");
} catch (ArrayIndexOutOfBoundsException aie) {
System.out.println("Problem info: ArrayIndexOutOfBoundsException has occurred.");
} catch (Exception e) {
System.out.println("Problem info: Unknown exception has occurred.");
}
}
}
Nested Try Statements
try blocks can be nested within other try blocks. If an inner try block encounters an exception its own catch blocks cannot handle, control propagates up to the outer try block's catch blocks.
public class NestedTryExample {
public static void main(String[] args) {
try {
int list[] = new int[5];
list[2] = 10;
list[4] = 2;
list[0] = list[2] / list[4];
try {
list[10] = 100; // Inner exception
} catch (ArrayIndexOutOfBoundsException aie) {
System.out.println("Problem info: ArrayIndexOutOfBoundsException has occurred.");
}
} catch (ArithmeticException ae) {
System.out.println("Problem info: Value of divisor can not be ZERO.");
} catch (Exception e) {
System.out.println("Problem info: Unknown exception has occurred.");
}
}
}
The throw Keyword
Used to explicitly throw an exception instance inside a method or code block. Execution of the try block stops immediately upon hitting throw, transferring control to the matching catch block.
Rules for throw
- Can only throw instances of
Throwableor its subclasses. - Throws only a single exception instance at a time.
- Must be followed by a
Throwableinstance (e.g., initialized vianew).
// Syntax
throw instance;
import java.util.Scanner;
public class ThrowSample {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
int num1, num2, result;
System.out.print("Enter any two numbers: ");
num1 = input.nextInt();
num2 = input.nextInt();
try {
if (num2 == 0)
throw new ArithmeticException("Division by zero is not possible");
result = num1 / num2;
System.out.println(num1 + "/" + num2 + " = " + result);
} catch (ArithmeticException ae) {
System.out.println("Problem info: " + ae.getMessage());
}
System.out.println("End of the program");
}
}
The throws Keyword
Used in method declarations to specify that a method delegates exception handling to its caller. Callers executing a method with a throws clause must wrap the call in a try-catch block or declare throws themselves.
import java.util.Scanner;
public class ThrowsExample {
int num1, num2, result;
Scanner input = new Scanner(System.in);
void division() throws ArithmeticException {
System.out.print("Enter any two numbers: ");
num1 = input.nextInt();
num2 = input.nextInt();
result = num1 / num2;
System.out.println(num1 + "/" + num2 + " = " + result);
}
public static void main(String[] args) {
try {
new ThrowsExample().division();
} catch (ArithmeticException ae) {
System.out.println("Problem info: " + ae.getMessage());
}
System.out.println("End of the program");
}
}
The finally Keyword
Defines a code block that always executes, regardless of whether an exception occurs or is caught. It is typically used for resource cleanup (e.g., closing files, releasing database connections).
Rules for finally
- Only one
finallyblock is allowed pertryblock. - The
finallyblock is optional.
import java.util.Scanner;
public class FinallyExample {
public static void main(String[] args) {
int num1, num2, result;
Scanner input = new Scanner(System.in);
System.out.print("Enter any two numbers: ");
num1 = input.nextInt();
num2 = input.nextInt();
try {
if (num2 == 0)
throw new ArithmeticException("Division by zero");
result = num1 / num2;
System.out.println(num1 + "/" + num2 + " = " + result);
} catch (ArithmeticException ae) {
System.out.println("Problem info: " + ae.getMessage());
} finally {
System.out.println("The finally block executes always");
}
System.out.println("End of the program");
}
}
Built-in Exception Reference and Custom Exceptions
Built-in Checked Exceptions Reference
| Exception Class | Description |
|---|---|
ClassNotFoundException | Thrown when the JVM attempts to load a class by name, but no definition is found on the classpath. |
CloneNotSupportedException | Thrown when Object.clone() is invoked on an instance whose class does not implement Cloneable. |
IllegalAccessException | Thrown when attempting to access a class method or field blocked by visibility modifiers. |
InstantiationException | Thrown when attempting to instantiate an abstract class or interface via newInstance(). |
InterruptedException | Thrown when a thread that is sleeping, waiting, or occupied is interrupted. |
NoSuchFieldException | Thrown when accessing a requested named field that does not exist in the class. |
NoSuchMethodException | Thrown when accessing a requested named method that does not exist in the class. |
Built-in Unchecked Exceptions Reference
| Exception Class | Description |
|---|---|
ArithmeticException | Handles arithmetic error conditions, such as division by zero. |
ArrayIndexOutOfBoundsException | Handles array access using an invalid index (negative or array size). |
ArrayStoreException | Thrown when attempting to store an incompatible object type inside an object array. |
AssertionError | Thrown to indicate that an assert statement condition failed. |
ClassCastException | Thrown when improperly casting an object reference to an incompatible type. |
IllegalArgumentException | Thrown to indicate that a method was passed an illegal or inappropriate argument. |
IllegalMonitorStateException | Thrown when operating on a monitor thread without owning its lock. |
IllegalStateException | Signals that a method was invoked at an illegal or inappropriate execution time. |
IllegalThreadStateException | Thrown when attempting to modify a thread's state at an invalid time. |
IndexOutOfBoundsException | Thrown when accessing an invalid index in a collection, array, vector, or string. |
NegativeArraySizeException | Thrown if an application attempts to instantiate an array with a negative size. |
NullPointerException | Thrown when attempting to dereference or access members on a null object reference. |
NumberFormatException | Thrown when attempting to parse an invalid string into a numeric value (e.g., integer or float). |
SecurityException | Thrown by security managers to signal a security violation. |
StringIndexOutOfBoundsException | Thrown by String methods when accessing an index that is negative or string length. |
UnsupportedOperationException | Thrown to indicate that a requested operation is not supported. |
Creating Custom User-Defined Exception Classes
Custom exceptions are created by subclassing java.lang.Exception. Pass custom error message strings to the parent constructor using super(msg) and retrieve them later using getMessage().
import java.util.Scanner;
// Custom Exception Subclass
class NotEligibleException extends Exception {
NotEligibleException(String msg) {
super(msg);
}
}
class VoterList {
int age;
VoterList(int age) {
this.age = age;
}
void checkEligibility() {
try {
if (age < 18) {
throw new NotEligibleException("Error: Not eligible for vote due to under age.");
}
System.out.println("Congrats! You are eligible for vote.");
} catch (NotEligibleException nee) {
System.out.println(nee.getMessage());
}
}
public static void main(String args[]) {
Scanner input = new Scanner(System.in);
System.out.println("Enter your age in years: ");
int age = input.nextInt();
VoterList person = new VoterList(age);
person.checkEligibility();
}
}
Core Object-Oriented Programming Review and Code Examples
Features of Object-Oriented Programming
- Abstraction: Hides complex underlying implementation details, exposing only essential functionalities via abstract classes and interfaces.
- Encapsulation: Wraps data fields and methods within a single class unit, restricting direct state modification using visibility modifiers (
private,protected,public). - Inheritance: Enables child classes to acquire data members and methods from parent classes (
extends), encouraging code reuse and structural hierarchies. - Polymorphism: Allows methods to behave differently based on the object executing them. Implemented via compile-time overloading and runtime method overriding.
Constructors in Java
A constructor initializes newly instantiated objects. It shares the exact name of its declaring class and lacks a return type.
- Default Constructor: Inserted automatically by the Java compiler if no constructors are declared. Initializes fields to default values.
- Parameterized Constructor: Takes explicitly defined arguments to initialize custom instance state upon creation.
- Copy Constructor: Accepts an instance of the same class as a parameter, copying its field values into a new object instance.
Garbage Collection and the this Keyword
- Garbage Collection: An automatic JVM process running on a background thread that frees heap memory occupied by unreachable objects. Before reclaiming an object, the garbage collector calls its
finalize()method. - The
thisKeyword: An active reference pointing to the current executing instance of a class. Used to differentiate instance variables from local method parameters of the same name, invoke overloaded constructors (this()), or pass current instance references as method arguments.
Java Program Code Examples
1. Factorial Calculation Program
import java.util.Scanner;
public class FactorialProgram {
public static long calculateFactorial(int n) {
long fact = 1;
for (int i = 1; i <= n; i++) {
fact *= i;
}
return fact;
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.print("Enter a number: ");
int num = sc.nextInt();
if (num < 0) {
System.out.println("Factorial is not defined for negative numbers.");
} else {
System.out.println("Factorial of " + num + " is: " + calculateFactorial(num));
}
}
}
2. Demonstrating super Keyword Usage
class BaseClass {
int value = 50;
BaseClass(String message) {
System.out.println("Base Constructor: " + message);
}
void display() {
System.out.println("Base class display method.");
}
}
class DerivedClass extends BaseClass {
int value = 100;
DerivedClass() {
super("Initializing via super keyword"); // 1. Calling base constructor
}
void display() {
super.display(); // 2. Invoking base method
System.out.println("Derived value: " + value);
System.out.println("Base value via super: " + super.value); // 3. Accessing base field
}
}
public class SuperDemo {
public static void main(String[] args) {
DerivedClass obj = new DerivedClass();
obj.display();
}
}
3. Division by Zero Exception Handling
import java.util.Scanner;
public class DivisionByZeroHandler {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.print("Enter numerator: ");
int numerator = scanner.nextInt();
System.out.print("Enter denominator: ");
int denominator = scanner.nextInt();
try {
int result = numerator / denominator;
System.out.println("Result: " + result);
} catch (ArithmeticException ae) {
System.out.println("ArithmeticException caught: Division by zero is not allowed.");
System.out.println("Exception Details: " + ae.getMessage());
}
System.out.println("Program execution continues smoothly after handling error.");
}
}