Java Question Bank with Answers

Answers for Core Java Question Bank –

Chapter 4: Exception Handling, Strings, and Files

Moderate Level Questions

8. Explain the hierarchy of Exception classes in Java. Differentiate between Checked and Unchecked exceptions with suitable examples for each.

Answer:
The Java exception handling system is built upon a hierarchical structure where all exceptions and errors originate from the
java.lang.Throwable class.

Hierarchy of Exception Classes:

java.lang.Object

    └── java.lang.Throwable

        ├── java.lang.Error

        └── java.lang.Exception

            ├── java.lang.RuntimeException

            └── Other `Exception` classes

  • java.lang.Throwable: This is the superclass of all errors and exceptions in Java. Only objects that are instances of Throwable can be thrown and caught.
  • java.lang.Error: Represents serious problems that are generally not expected to be caught or handled by an application. These often indicate problems with the Java Virtual Machine or system resources.
  • java.lang.Exception: Represents conditions that an application might want to catch and handle. It further branches into RuntimeException (and its subclasses) and other Exception classes.

Differentiation between Checked and Unchecked Exceptions:

Feature

Checked Exceptions

Unchecked Exceptions (RuntimeException and its subclasses)

Compiler Enforcement

Mandatory to handle or declare. The compiler forces you to either enclose the code in try-catch or specify throws in the method signature.

No compiler enforcement. The compiler does not require you to handle or declare them.

Recovery

Often represent anticipated external problems that a program can potentially recover from (e.g., file not found, network issues).

Typically represent programming errors or logical flaws (e.g., trying to access an array out of bounds, dereferencing a null pointer). Recovery is often not straightforward; fixing the bug is the usual solution.

Typical Use

For problems that are beyond the program’s immediate control but are expected in normal operation.

For problems that indicate a defect in the code itself.

Parent Class

Directly or indirectly inherit from Exception but not RuntimeException.

Directly or indirectly inherit from RuntimeException.

Suitable Examples:

  • Checked Exception Examples:
    • IOException (e.g., FileNotFoundException): Occurs when an I/O operation fails. You must handle this when working with files.
    • SQLException: Signifies an error with database access.
    • ClassNotFoundException: Thrown when a class cannot be located.
  • Unchecked Exception Examples:
    • NullPointerException: Occurs when trying to use an object reference that has a null value.
    • ArrayIndexOutOfBoundsException: Occurs when attempting to access an array element with an index that is outside the bounds of the array.
    • ArithmeticException: Occurs for illegal arithmetic operations, like division by zero.

9. Write a Java program that demonstrates how to catch and handle a NumberFormatException that occurs when trying to convert an invalid string to an integer.

Answer:
A
NumberFormatException is an unchecked exception that occurs when an attempt is made to convert a string into a numeric type (like int, double, etc.) but the string does not have an appropriate format.

public class NumberFormatExceptionDemo {

    public static void main(String[] args) {

        String validNumberStr = "123";

        String invalidNumberStr = "abc"; // This cannot be converted to an integer

        String nullStr = null;            // This will cause a NullPointerException if used directly

 

        System.out.println("— Attempting to convert valid string —");

        try {

            int number1 = Integer.parseInt(validNumberStr);

            System.out.println("Successfully converted ‘" + validNumberStr + "’ to integer: " + number1);

        } catch (NumberFormatException e) {

            System.err.println("Caught NumberFormatException for ‘" + validNumberStr + "’: " + e.getMessage());

        }

 

        System.out.println("\n— Attempting to convert invalid string —");

        try {

            int number2 = Integer.parseInt(invalidNumberStr); // This line will throw NumberFormatException

            System.out.println("Successfully converted ‘" + invalidNumberStr + "’ to integer: " + number2);

        } catch (NumberFormatException e) {

            System.err.println("Caught NumberFormatException for ‘" + invalidNumberStr + "’: Input string was not a valid number.");

            System.err.println("Error details: " + e.getMessage());

        }

 

        System.out.println("\n— Attempting to convert a null string —");

        try {

            if (nullStr == null) {

                throw new IllegalArgumentException("Input string cannot be null.");

            }

            int number3 = Integer.parseInt(nullStr);

            System.out.println("Successfully converted ‘" + nullStr + "’ to integer: " + number3);

        } catch (NumberFormatException e) {

            System.err.println("Caught NumberFormatException: " + e.getMessage());

        } catch (NullPointerException e) {

            System.err.println("Caught NullPointerException: Cannot parse a null string.");

            System.err.println("Error details: " + e.getMessage());

        } catch (IllegalArgumentException e) {

            System.err.println("Caught IllegalArgumentException: " + e.getMessage());

        }

 

        System.out.println("\nProgram continues after all attempts.");

    }

}

10. Describe the advantages of using a finally block in exception handling. Provide a code snippet to illustrate its use, ensuring that a resource (e.g., a file) is closed regardless of whether an exception occurs.

Answer:
The
finally block is an optional block that can be associated with a try-catch block. Its primary purpose is to contain code that must be executed, regardless of whether an exception occurred in the try block or was caught by a catch block.

Advantages of using a finally block:

1.     Guaranteed Execution: The code inside the finally block is guaranteed to execute, even if an exception is thrown, caught, or not caught, or if the try block completes normally. The only exceptions to this guarantee are if the JVM exits (e.g., System.exit()) or if an infinite loop occurs within the try or catch block.

2.     Resource Cleanup: It is ideal for placing cleanup code, such as closing open files, network connections, database connections, or releasing other system resources. This ensures that resources are properly deallocated, preventing resource leaks.

3.     Code Centralization: It allows you to centralize cleanup logic in one place, making the code cleaner and more maintainable.

Code Snippet Illustrating Resource Closure with finally:

import java.io.FileWriter;

import java.io.IOException;

 

public class FinallyBlockDemo {

    public static void main(String[] args) {

        FileWriter writer = null; // Declare outside try so it’s accessible in finally

 

        try {

            writer = new FileWriter("important_log.txt"); // Open a file

            writer.write("This is a log entry.\n");

            System.out.println("Data written successfully.");

 

            // Simulate an error here, e.g., division by zero

            int result = 10 / 0; // This will throw an ArithmeticException

            writer.write("This line will not be written.\n"); // This line won’t be reached

 

        } catch (IOException e) {

            System.err.println("Caught IOException: " + e.getMessage());

        } catch (ArithmeticException e) {

            System.err.println("Caught ArithmeticException: " + e.getMessage());

        } finally {

            // This block will always execute, regardless of exception or normal completion

            if (writer != null) {

                try {

                    writer.close(); // Close the file to prevent resource leaks

                    System.out.println("FileWriter closed in finally block.");

                } catch (IOException e) {

                    System.err.println("Error closing FileWriter: " + e.getMessage());

                }

            }

        }

 

        System.out.println("Program finished.");

    }

}

11. Explain the process of "Creating User-Defined Exceptions" in Java. Provide a simple example of a custom exception class and demonstrate how to throw and catch it.

Answer:
While Java provides a rich set of built-in exception classes, there are situations where you need to define your own custom exceptions to represent specific error conditions unique to your application’s logic. This is known as creating user-defined exceptions.

Process of Creating User-Defined Exceptions:

1.     Extend an Existing Exception Class: Your custom exception class must extend an existing exception class from Java’s hierarchy, typically java.lang.Exception or java.lang.RuntimeException.

o    If you extend Exception (or any of its checked subclasses), your custom exception will be a checked exception, meaning callers must handle or declare it.

o    If you extend RuntimeException (or any of its unchecked subclasses), your custom exception will be an unchecked exception, and callers are not forced to handle it.

2.     Provide Constructors: It’s good practice to provide at least two constructors:

o    A no-argument constructor.

o    A constructor that accepts a String message, which can be passed to the superclass constructor (super(message)) to provide a descriptive error message.

3.     ** Add Custom Fields and Methods:** You can add specific fields or methods to your custom exception class to carry additional information about the error, which might be useful for handling it.

Simple Example of a Custom Exception Class and Demonstration:

Let’s create a custom checked exception called InvalidAgeException that is thrown when a user enters an age that is outside a valid range.

// 1. Define the Custom Exception Class

class InvalidAgeException extends Exception {

    // Constructor 1: Default constructor

    public InvalidAgeException() {

        super("Age is not within the valid range (0-120).");

    }

 

    // Constructor 2: Accepts a custom message

    public InvalidAgeException(String message) {

        super(message);

    }

 

    // Constructor 3: Accepts a message and a cause

    public InvalidAgeException(String message, Throwable cause) {

        super(message, cause);

    }

}

 

// Class that uses the custom exception

public class CustomExceptionDemo {

 

    // Method that might throw InvalidAgeException (must declare it)

    public static void setAge(int age) throws InvalidAgeException {

        if (age < 0 || age > 120) {

            throw new InvalidAgeException("The provided age " + age + " is invalid.");

        }

        System.out.println("Age set to: " + age);

    }

 

    public static void main(String[] args) {

        System.out.println("— Test Case 1: Valid Age —");

        try {

            setAge(25); // Valid age

        } catch (InvalidAgeException e) {

            System.err.println("Caught exception: " + e.getMessage());

        }

 

        System.out.println("\n— Test Case 2: Invalid Age —");

        try {

            setAge(-5); // Invalid age

        } catch (InvalidAgeException e) {

            System.err.println("Caught exception: " + e.getMessage());

        }

 

        System.out.println("\n— Test Case 3: Another Invalid Age —");

        try {

            setAge(150); // Invalid age

        } catch (InvalidAgeException e) {

            System.err.println("Caught exception: " + e.getMessage());

        }

 

        System.out.println("\nProgram continues after handling all cases.");

    }

}

12. Compare and contrast the String and StringBuffer classes in Java. Discuss scenarios where StringBuffer (or StringBuilder) would be preferred over String.

Answer:
The
String, StringBuffer, and StringBuilder classes are all used to represent sequences of characters in Java, but they differ fundamentally in their mutability and thread-safety, impacting their performance and suitability for various scenarios.

Comparison and Contrast:

Feature

String

StringBuffer

StringBuilder

Mutability

Immutable: Its value cannot be changed after creation.

Mutable: Its value can be changed after creation.

Mutable: Its value can be changed after creation.

Thread-Safety

Inherently thread-safe due to immutability.

Thread-safe: Methods are synchronized, suitable for multi-threaded environments.

Not thread-safe: Methods are not synchronized, not suitable for multi-threaded environments where multiple threads might modify the same object concurrently.

Performance

Less efficient for repeated modifications (concatenations, insertions) as each operation creates a new object.

Less efficient than StringBuilder in single-threaded environments due to synchronization overhead.

Most efficient for repeated modifications in single-threaded environments as it avoids synchronization overhead.

Memory

Can consume more memory for frequent modifications due to creation of many intermediate String objects.

More memory-efficient for manipulations than String because modifications happen in-place.

Similar memory efficiency to StringBuffer for manipulations.

Scenarios where StringBuffer (or StringBuilder) would be preferred over String:

1.     Extensive String Concatenation/Manipulation in Loops:

o    Reason: When you need to append, insert, or delete characters frequently within a loop. Using String with + or concat() in such scenarios creates a new String object in each iteration, leading to significant performance degradation and increased memory consumption due to garbage collection.

o    Example: Building a long string from a list of words.

public class StringBuilderExample {

    public static void main(String[] args) {

 

        // Inefficient approach using String (creates many intermediate objects)

        String result = "";

        for (int i = 0; i < 1000; i++) {

            result += "word" + i; // Each iteration creates a new String object

        }

        System.out.println("Length of result using String: " + result.length());

 

        // Efficient approach using StringBuilder (modifies the object in-place)

        StringBuilder sb = new StringBuilder();

        for (int i = 0; i < 1000; i++) {

            sb.append("word").append(i); // Appends without creating intermediate objects

        }

        String finalResult = sb.toString();

        System.out.println("Length of result using StringBuilder: " + finalResult.length());

    }

}

 String Reversal or Modification:

o    Reason: StringBuffer and StringBuilder provide convenient methods like reverse(), replace(), delete(), and insert() that modify the character sequence directly without creating new objects.

o    Example: Reversing a user’s input string.

2.     Building Complex Strings from Multiple Sources:

o    Reason: When constructing a string from various parts, especially if some parts might be optional or generated dynamically.

o    Example: Generating a complex SQL query or a JSON payload.

Choosing between StringBuffer and StringBuilder:

  • Use StringBuffer when thread-safety is required, i.e., when multiple threads might access and modify the same sequence of characters concurrently. Its synchronized methods ensure data consistency.
  • Use StringBuilder when thread-safety is not a concern, i.e., in a single-threaded environment or when the string manipulation is confined to a single thread. It offers better performance than StringBuffer because it avoids the overhead of synchronization.

13. Write a Java program to concatenate several strings efficiently using StringBuffer. Also, demonstrate how to reverse a string using StringBuffer.

Answer:
This program demonstrates the efficiency of
StringBuffer for concatenation and its reverse() method.

public class StringBufferOperations {

    public static void main(String[] args) {

 

        // — Efficient String Concatenation using StringBuffer —

        System.out.println("— Efficient String Concatenation —");

        StringBuffer sb = new StringBuffer(); // Create a StringBuffer object

 

        // Concatenate multiple strings

        sb.append("Hello");

        sb.append(" ");

        sb.append("Java");

        sb.append(" ");

        sb.append("Programming");

        sb.append("!");

 

        System.out.println("Concatenated String: " + sb.toString());

 

        // Concatenating in a loop to demonstrate efficiency

        long startTime = System.nanoTime();

        StringBuffer largeString = new StringBuffer();

        for (int i = 0; i < 1000; i++) {

            largeString.append("Part").append(i).append(" ");

        }

        long endTime = System.nanoTime();

        System.out.println("Time taken for 1000 appends: " + (endTime – startTime) + " ns");

 

        // — Reversing a String using StringBuffer —

        System.out.println("\n— String Reversal —");

 

        String originalString = "racecar";

        StringBuffer stringToReverse = new StringBuffer(originalString);

        System.out.println("Original String: " + originalString);

        stringToReverse.reverse(); // Reverse in-place

        System.out.println("Reversed String: " + stringToReverse.toString());

 

        String anotherString = "Hello World";

        StringBuffer anotherSb = new StringBuffer(anotherString);

        System.out.println("Original String: " + anotherString);

        System.out.println("Reversed String: " + anotherSb.reverse().toString());

    }

}

14. Explain the format() method for string data in Java. Provide a Java code example to format a floating-point number to two decimal places and an integer with leading zeros.

Answer:
The
format() method in Java (specifically String.format() and PrintStream.printf()) is a powerful tool for creating formatted strings. It allows developers to control the presentation of various data types (numbers, dates, strings) according to specific patterns. It uses a format string, which contains plain text and format specifiers, and a list of arguments to be formatted.

Format Specifiers:
A format specifier begins with a
% character and ends with a conversion character (e.g., s for string, d for integer, f for floating-point). It can also include flags, width, and precision.

  • %s: Formats a string.
  • %d: Formats a decimal integer.
  • %f: Formats a floating-point number.
  • %n: Inserts a platform-specific new line character.

Java Code Example for Formatting:

public class StringFormatDemo {

    public static void main(String[] args) {

        // — Format a floating-point number to two decimal places —

        double pi = Math.PI;

        System.out.println("Original Pi: " + pi);

 

        String formattedPi = String.format("Pi (2 decimal places): %.2f", pi);

        System.out.println(formattedPi); // Output: Pi (2 decimal places): 3.14

 

        // — Format integers with leading zeros —

        int studentId = 45;

        int courseCode = 7;

 

        String formattedStudentId = String.format("Student ID: %05d", studentId);

        String formattedCourseCode = String.format("Course Code: %03d", courseCode);

 

        System.out.println(formattedStudentId);   // Output: Student ID: 00045

        System.out.println(formattedCourseCode);  // Output: Course Code: 007

 

        // — Combining multiple formats —

        String itemName = "Laptop";

        double itemPrice = 999.9987;

        int itemQuantity = 2;

 

        String orderDetails = String.format(

            "Order: %s – Quantity: %d – Price: $%.2f each – Total: $%.2f",

            itemName, itemQuantity, itemPrice, itemPrice * itemQuantity

        );

 

        System.out.println(orderDetails);

        // Output: Order: Laptop – Quantity: 2 – Price: $1000.00 each – Total: $1999.99

    }

}

15. Differentiate between byte streams and character streams in Java File Handling. When would you use one over the other?

Answer:
Byte streams and character streams are the two fundamental categories of streams in Java’s I/O API, designed to handle different types of data.

Feature

Byte Streams

Character Streams

Data Unit

Reads/writes data in 8-bit bytes.

Reads/writes data in 16-bit Unicode characters.

Purpose

Primarily for binary data (non-textual).

Primarily for text data.

Encoding

Does not handle character encoding explicitly; reads/writes raw bytes. Encoding must be handled manually if reading text.

Handles character encoding automatically (uses default platform encoding or specified encoding).

Classes

InputStream, OutputStream, FileInputStream, FileOutputStream, BufferedInputStream, BufferedOutputStream, DataInputStream, DataOutputStream.

Reader, Writer, FileReader, FileWriter, BufferedReader, BufferedWriter, InputStreamReader, OutputStreamWriter.

Efficiency for Text

Less efficient for text because you have to convert bytes to characters manually, often involving multiple steps and explicit encoding.

More efficient and convenient for text because they directly operate on characters and handle encoding.

When to use one over the other:

  • Use Byte Streams When:

1.     Handling Binary Data: When reading or writing non-textual data like images (.jpg, .png), audio files (.mp3), video files (.mp4), executable files (.exe), serialized objects, or any other raw data.

2.     Working with InputStream and OutputStream: When the underlying resource is byte-oriented, such as network sockets, where raw byte data transfer is required.

3.     Performance with Raw Data: For maximum performance when you are sure you are dealing only with raw bytes and don’t need character encoding translation.

  • Use Character Streams When:

1.     Handling Text Data: When reading or writing human-readable text files, configuration files (.txt, .log, .csv, .xml, .json), or any data that needs to be interpreted as characters.

2.     Internationalization: When dealing with different character encodings (e.g., UTF-8, UTF-16, ISO-8859-1), character streams (especially with InputStreamReader/OutputStreamWriter) provide robust encoding handling.

3.     Convenience: For text processing, character streams often come with buffered readers/writers (BufferedReader, BufferedWriter) that provide methods like readLine(), which simplify line-by-line text processing.

16. Write a Java program that reads text content from a specified file named input.txt and prints it to the console. Include basic exception handling for FileNotFoundException.

Answer:
This program demonstrates how to read text from a file line by line using
FileReader and BufferedReader, and how to handle a FileNotFoundException.

First, ensure you have a file named input.txt in the same directory as your Java program (or provide a full path).
input.txt content (create this file):

Hello, this is the first line.

This is the second line of text.

Java file handling is fun!

Java Program:

import java.io.BufferedReader;

import java.io.FileReader;

import java.io.FileNotFoundException;

import java.io.IOException;

 

public class ReadTextFileDemo {

    public static void main(String[] args) {

        String fileName = "input.txt"; // Name of the file to read

        BufferedReader reader = null; // Declare outside try-block for finally access

 

        System.out.println("Attempting to read from file: " + fileName);

 

        try {

            // Create a FileReader to read characters from the file

            // Wrap it in a BufferedReader for efficient reading line by line

            reader = new BufferedReader(new FileReader(fileName));

            String line;

 

            System.out.println("\n— Content of " + fileName + " —");

            // Read lines until the end of the file (readLine() returns null)

            while ((line = reader.readLine()) != null) {

                System.out.println(line);

            }

            System.out.println("—————————-");

 

        } catch (FileNotFoundException e) {

            // Catch this specific exception if the file does not exist

            System.err.println("Error: File not found at " + fileName);

            System.err.println("Please make sure the file exists in the correct directory.");

        } catch (IOException e) {

            // Catch other general I/O errors that might occur during reading

            System.err.println("An I/O error occurred while reading the file: " + e.getMessage());

        } finally {

            // Ensure the reader is closed to release system resources

            if (reader != null) {

                try {

                    reader.close();

                    System.out.println("\nBufferedReader closed.");

                } catch (IOException e) {

                    System.err.println("Error closing BufferedReader: " + e.getMessage());

                }

            }

        }

 

        System.out.println("Program finished.");

    }

}

17. Design a Java program to append new text content to an existing file named log.txt. Ensure that existing content is not overwritten.

Answer:
To append content to an existing file without overwriting it, you need to open the file in append mode. In Java, this is typically achieved by passing
true as the second argument to the FileWriter or FileOutputStream constructor.

First, let’s assume log.txt already exists with some content.
log.txt content (initial):

First log entry.

Second log entry.

Java Program:

import java.io.FileWriter;

import java.io.IOException;

import java.io.PrintWriter; // For convenient writing of lines

 

public class AppendToFileDemo {

    public static void main(String[] args) {

        String fileName = "log.txt";

        String newContent = "New log entry appended at " + java.time.LocalDateTime.now(); // Content to append

 

        System.out.println("Attempting to append to file: " + fileName);

 

        // Using try-with-resources for automatic resource closure

        // The ‘true’ argument in FileWriter enables append mode

        try (FileWriter fileWriter = new FileWriter(fileName, true);

             PrintWriter printWriter = new PrintWriter(fileWriter)) {

 

            printWriter.println(newContent);          // Appends a new line

            printWriter.println("Another line appended."); // Append another line

 

            System.out.println("Content successfully appended to ‘" + fileName + "’.");

 

        } catch (IOException e) {

            System.err.println("An error occurred while appending to the file: " + e.getMessage());

            e.printStackTrace();

        }

 

        System.out.println("Program finished.");

    }

}