Chapter 34:Method Hiding Conclusion and Introduction to Exception Handling

Rohit GawandeRohit Gawande
9 min read

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

  1. 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.
  2. 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.

  3. 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.
  4. 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:

  1. Inherited from the parent class

  2. 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 the Child class is a specialized method, so it is not accessible through a Parent 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’s disp() 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 in Child hides the one in Parent.


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

  1. Execution Process in Java

    • Java code execution involves two main stages:

      1. Compilation: This is the process where the Java source code (.java file) is converted to bytecode (.class file) by the Java compiler (javac).

      2. 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:

  1. 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
  1. 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

  1. Normal Execution Flow:

    • The program runs from start to end without any interruptions.
  2. Exception Flow:

    • When an exception occurs in a try block, the program jumps to the corresponding catch block, bypassing any remaining statements in the try block.
  3. Finally Flow:

    • After handling an exception or completing the try block, the program always executes the finally block if it exists.

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.

0
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! 😊