Chapter 34:Method Hiding Conclusion and Introduction to Exception Handling
Table of contents
Method Hiding
1. Method Hiding in Java
Method hiding is a behavior that arises when a subclass declares a static method with the same name and parameters as a static method in its superclass. Unlike instance methods, static methods in subclasses are not overridden but are instead hidden. Let’s break down the details.
1.1 Key Concepts of Method Hiding
Static Methods and Inheritance:
- Static methods in Java do participate in inheritance, meaning they can be called using either the class name or a reference to an object.
Overriding vs. Method Hiding:
A non-static (or instance) method in the child class with the same signature as a method in the parent class is said to be overridden.
However, a static method in the child class with the same signature as a static method in the parent class is not overridden; instead, it is said to be hidden.
Specialized Method:
- The static method in the child class, because it does not override the parent class's method, is treated as a specialized method that exists only in the child class.
Runtime Behavior:
When calling a static method using an object reference, Java determines the method to execute based on the reference's class type, not the actual object type.
Hence, the parent class method is executed when a static method is hidden, even if the object is of the child class.
This unique behavior, where the static method in the child class does not override the parent’s method, is called method hiding.
1.2 Access Rules with Parent Reference
When using a parent reference, we can only access methods that are:
Inherited from the parent class
Overridden (for non-static methods) in the child class
Attempting to access a specialized static method defined in the child class will result in a compiler error.
1.3 Practical Examples of Method Hiding in Java
To understand method hiding in practice, we’ll go through a series of examples.
Example 1: Accessing Methods with Parent Reference
In this example, we define a parent reference that points to a child object. We can access only the inherited methods and overridden methods, not any specialized method defined in the child class.
class Parent {
public void disp() {
System.out.println("Hello, Parent");
}
}
class Child extends Parent {
public void disp1() { // Specialized method
System.out.println("Hello, Child");
}
}
public class MethodHidingExample {
public static void main(String[] args) {
Parent p = new Child();
p.disp(); // Output: Hello, Parent
// p.disp1(); // Compiler error, cannot access specialized method
}
}
In the above code:
- The
disp1
method in theChild
class is a specialized method, so it is not accessible through aParent
reference.
Example 2: Static Methods and Inheritance
Static methods participate in inheritance, but they cannot be overridden. Calling a static method through a parent reference in the child class calls the parent class’s static method.
class Parent {
public static void disp() {
System.out.println("Hello, Parent");
}
}
class Child extends Parent {}
public class MethodHidingExample {
public static void main(String[] args) {
Parent p = new Child();
p.disp(); // Output: Hello, Parent
}
}
Explanation:
p.disp()
outputs "Hello, Parent" because the method call is resolved based on the reference type, not the object type, due to the static nature of the method.
Example 3: Overridden Instance Methods
When an instance (non-static) method is overridden in the child class, the child’s method executes. This shows the difference between instance methods and static methods in inheritance.
class Parent {
public void disp() {
System.out.println("Hello, Parent");
}
}
class Child extends Parent {
public void disp() { // Overridden method
System.out.println("Hello, Child");
}
}
public class MethodHidingExample {
public static void main(String[] args) {
Parent p = new Child();
p.disp(); // Output: Hello, Child
}
}
Explanation:
p.disp()
executes the child class’sdisp()
method due to the dynamic method dispatch in Java, which applies to instance methods.
Example 4: Method Hiding with Static Methods
In this final example, we demonstrate method hiding. The child class defines a static disp()
method, hiding the disp()
method in the parent class.
class Parent {
public static void disp() {
System.out.println("Hello, Parent");
}
}
class Child extends Parent {
public static void disp() {
System.out.println("Hello, Child");
}
}
public class MethodHidingExample {
public static void main(String[] args) {
Parent p = new Child();
p.disp(); // Output: Hello, Parent
Child c = new Child();
c.disp(); // Output: Hello, Child
}
}
Explanation:
p.disp()
outputs "Hello, Parent" because the method call is resolved based on the reference type (Parent).c.disp()
outputs "Hello, Child," as the method inChild
hides the one inParent
.
2. Lambda Expressions in Multithreading
Lambda expressions are a powerful feature in Java, introduced to simplify syntax for functional interfaces. A functional interface is an interface with a single abstract method. Lambda expressions are particularly useful in multithreading, where they can be used to implement Runnable
without the need for boilerplate code.
2.1 Understanding the Runnable Interface
The Runnable
interface in Java is a functional interface, meaning it has a single abstract method:
void run()
: This method is intended to contain the code that should run in a separate thread.
2.2 Lambda Expressions with Runnable Interface
Lambda expressions make it easy to define the behavior of a thread. Instead of writing a separate class to implement Runnable
, we can directly define the run()
method in a concise manner.
Example:
public class LambdaExample {
public static void main(String[] args) {
// Using lambda expression to define the Runnable task
Runnable task = () -> {
for (int i = 0; i < 5; i++) {
System.out.println("Running in thread " + Thread.currentThread().getName());
}
};
// Creating a new Thread with the lambda-defined task
Thread thread = new Thread(task);
thread.start();
}
}
Explanation:
Here, we define the behavior of
run()
using a lambda expression.The lambda expression replaces a traditional
Runnable
implementation, making the code more concise and readable.
Introduction to Exception Handling in Java
1. Exception Handling in Java
In Java, exception handling is a powerful mechanism for managing runtime errors and ensuring that the flow of the program continues smoothly. It helps to handle potential issues that may arise during the execution of the program, such as invalid inputs, unexpected null values, or network issues.
Key Concepts in Java Execution
Execution Process in Java
Java code execution involves two main stages:
Compilation: This is the process where the Java source code (
.java
file) is converted to bytecode (.class
file) by the Java compiler (javac
).Execution: The bytecode is executed by the Java Virtual Machine (JVM), which produces the output. If compilation is successful, there will be no syntax errors.
Diagram of Java Execution Process:
.java file --> javac --> .class file (bytecode) --> JVM (executes) --> Output
2. Types of Errors in Java
In Java, errors can generally be classified into two types:
Compile-time Errors:
Occur during the compilation stage due to syntax errors in the code.
Examples include missing semicolons, mismatched curly braces, and misspelled keywords.
These errors are easy to identify and fix because the compiler flags them.
// Example of a compile-time error
public class Example {
public static void main(String[] args) {
System.out.println("Hello, World!") // Missing semicolon
}
}
Output:
Error: ';' expected
Runtime Errors:
Occur during the execution of the program, even after successful compilation.
There are two main types:
Logical Errors: Errors in the program's logic that cause it to produce incorrect results.
Exceptions: Unplanned events that occur during runtime, usually due to invalid input or unexpected conditions (e.g., division by zero or accessing a null reference).
Exception Example:
public class Example { public static void main(String[] args) { int a = 5; int b = 0; System.out.println(a / b); // Division by zero } }
Output:
Exception in thread "main" java.lang.ArithmeticException: / by zero
Stack Overflow Error:
public class Example { public static void disp() { disp(); // Recursive call with no termination } public static void main(String[] args) { disp(); } }
Output:
Exception in thread "main" java.lang.StackOverflowError
3. Historical Context of Exception Handling
Early Architectures and Challenges:
1-Tier Architecture (1970s):
- Programs were developed, compiled, and run on a single computer, so errors affected only that machine.
2-Tier Architecture (1980s):
- Programs could run on multiple machines connected through local networks. Although errors impacted multiple machines, the number of users was still limited.
Internet Era (1990s):
- As the internet evolved, applications became accessible globally. If an application terminated abruptly, it could impact many users. This large-scale usage demanded a robust error-handling mechanism, leading to the development of exception handling in Java.
4. Exception Handling in Java
Java provides a structured approach to handling exceptions, allowing developers to handle potential runtime issues gracefully.
4.1 Try-Catch Block
The try-catch
block is the primary way to handle exceptions in Java. The code that might throw an exception is placed in the try
block, and the catch
block handles the exception if it occurs.
Example: Division by Zero
public class DivisionExample {
public static void main(String[] args) {
try {
int a = 5;
int b = 0;
System.out.println(a / b); // Might throw ArithmeticException
} catch (ArithmeticException e) {
System.out.println("Error: Division by zero is not allowed.");
}
}
}
Output:
Error: Division by zero is not allowed.
4.2 Finally Block
The finally
block is used to execute code after the try-catch
blocks, regardless of whether an exception was thrown or not. It is often used for cleanup actions.
Example: File Closing
import java.io.*;
public class FileExample {
public static void main(String[] args) {
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader("file.txt"));
System.out.println(reader.readLine());
} catch (IOException e) {
System.out.println("Error reading file.");
} finally {
try {
if (reader != null) reader.close();
} catch (IOException e) {
System.out.println("Error closing file.");
}
}
}
}
5. Multi-Tier Architectures and Exception Handling
As applications became accessible globally, it became crucial to handle errors gracefully to avoid application crashes that could affect multiple users simultaneously.
Exception Handling in Modern Web Applications:
- Web applications rely on continuous internet connectivity and often handle many concurrent users. Java’s robust exception-handling system allows developers to manage errors gracefully, minimizing the impact on end-users.
6. Visualization of Exception Handling Flow
Normal Execution Flow:
- The program runs from start to end without any interruptions.
Exception Flow:
- When an exception occurs in a
try
block, the program jumps to the correspondingcatch
block, bypassing any remaining statements in thetry
block.
- When an exception occurs in a
Finally Flow:
- After handling an exception or completing the
try
block, the program always executes thefinally
block if it exists.
- After handling an exception or completing the
Summary
Java's exception handling mechanism is essential for building reliable applications. By using try
, catch
, and finally
blocks, developers can prevent programs from terminating abruptly, handle unexpected situations, and provide meaningful error messages to users.
Subscribe to my newsletter
Read articles from Rohit Gawande directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Rohit Gawande
Rohit Gawande
🚀 Tech Enthusiast | Full Stack Developer | System Design Explorer 💻 Passionate About Building Scalable Solutions and Sharing Knowledge Hi, I’m Rohit Gawande! 👋I am a Full Stack Java Developer with a deep interest in System Design, Data Structures & Algorithms, and building modern web applications. My goal is to empower developers with practical knowledge, best practices, and insights from real-world experiences. What I’m Currently Doing 🔹 Writing an in-depth System Design Series to help developers master complex design concepts.🔹 Sharing insights and projects from my journey in Full Stack Java Development, DSA in Java (Alpha Plus Course), and Full Stack Web Development.🔹 Exploring advanced Java concepts and modern web technologies. What You Can Expect Here ✨ Detailed technical blogs with examples, diagrams, and real-world use cases.✨ Practical guides on Java, System Design, and Full Stack Development.✨ Community-driven discussions to learn and grow together. Let’s Connect! 🌐 GitHub – Explore my projects and contributions.💼 LinkedIn – Connect for opportunities and collaborations.🏆 LeetCode – Check out my problem-solving journey. 💡 "Learning is a journey, not a destination. Let’s grow together!" Feel free to customize or add more based on your preferences! 😊