Week 6 Home Week 8

Errors And Exceptions

Errors and exceptions are an inevitable part of programming. While we aim for correctness, errors can occur due to various reasons, such as invalid user input, resource limitations, or coding mistakes.

Types of Errors in Java

  1. External Errors: These include issues like incorrect user input, unavailable resources, or hardware malfunctions. For example:

  2. Coding Mistakes: Errors resulting from flaws in the code, such as:

  3. Resource Limitations: Situations where external resources are depleted, such as:

Signaling Errors

One way to signal an error is to return an invalid value, such as -1 to indicate the end of a file or null to represent the absence of a valid result. However, this approach has limitations, as it may not always be possible when there is no clearly defined invalid value.

This limitation brings to the topic of Exception handling.

To handle these situations effectively, Java employs the concept of exceptions, which allow programmers to signal and manage abnormal conditions in the code. Exception handling provides a structured approach to gracefully manage these errors, ensuring that the program does not crash unexpectedly.

Exception Handelling in Java

Java provides a structured mechanism to handle errors gracefully using exceptions. This ensures that errors do not lead to program crash but allow recovery or meaningful failure notifications.

  1. Throwing an Exception:

  2. Catching an Exception:

  3. Graceful Interruption:

Example: Throwing and Catching Exceptions

public class ExceptionExample {

    // Method to demonstrate throwing an exception
    public static int divide(int a, int b) throws ArithmeticException {

        // Check for division by zero
        if (b == 0) {

            // Throw an ArithmeticException with a meaningful message
            throw new ArithmeticException("Division by zero is not allowed.");
        }

        // Perform division if no exception occurs
        return a / b;
    }

    public static void main(String[] args) {

        try {

            // Attempt to divide numbers, expecting a successful result
            System.out.println("Result: " + divide(10, 2));

            // Attempt to divide by zero, which will throw an exception
            System.out.println("Result: " + divide(10, 0));

        } catch (ArithmeticException e) {

            // Catch the exception and handle it gracefully
            System.out.println("Error: " + e.getMessage());

        } finally {

            // Optional: The `finally` block executes regardless of exceptions
            System.out.println("Division operation completed.");
        }
    }
}

Output:

Result: 5
Error: Division by zero is not allowed.
Division operation completed.

Code Explanation:

Java's Classification of Erros

Error Hierarcy in Java

Throwable
    |
    |-- Error (Unrecoverable JVM-level issues)
    |
    |-- Exception
        |
        |-- RuntimeException (Unchecked exceptions)
        |
        |-- Checked Exceptions

Java organizes errors into a hierarchy under the parent class Throwable. The main categories are:

Error

Errors represent severe issues that are usually beyond the control of the programmer. They indicate problems that typically arise in the Java Virtual Machine (JVM) itself and are unlikely to be recoverable during runtime.

Key Characteristics:

Examples of Errors:

  1. OutOfMemoryError: Thrown when the JVM runs out of memory.
  2. StackOverflowError: Occurs when the call stack exceeds its limit, usually due to infinite recursion.
  3. VirtualMachineError: Represents serious errors like JVM crashes or internal issues.

Code Example:

public class ErrorExample {
    public static void main(String[] args) {
        // Example of a StackOverflowError
        causeStackOverflow();
    }

    public static void causeStackOverflow() {
        // Recursive call without termination
        causeStackOverflow();
    }
}

Exception

Exceptions represent conditions that a program should handle gracefully. They are recoverable, and handling them appropriately allows the program to continue executing.

Key Characteristics:

RuntimeException/Unchecked Exception

Unchecked exceptions occur due to programming mistakes that could have been avoided with proper input validation or checks. The compiler does not enforce handling or declaring these exceptions.

Key Characteristics:

● Unchecked exceptions extend the RuntimeException class. ● They usually indicate logical or programming errors. ● Do not need to be declared using the throws keyword.

Examples of Unchecked Exceptions:

  1. ArithmeticException: Thrown during mathematical errors (e.g., division by zero).
  2. NullPointerException: Occurs when trying to use an object reference that is null.
  3. ArrayIndexOutOfBoundsException: Accessing an array element with an invalid index.

Code Example:

public class UncheckedExceptionExample {

    public static void main(String[] args) {

        int[] numbers = {1, 2, 3};

        try {
            // Attempt to access an invalid index
            System.out.println(numbers[5]);

        } catch (ArrayIndexOutOfBoundsException e) {

            // Handle the unchecked exception
            System.out.println("Error: " + e.getMessage());
        }
    }
}

Output:

Error: Index 5 out of bounds for length 3
Checked Exception

Checked exceptions represent exceptional conditions that the program should anticipate and handle. The Java compiler enforces that these exceptions must be declared using the throws keyword or handled using a try-catch block.

Key Characteristics:

Examples of Checked Exceptions:

  1. IOException: Issues with input/output operations, such as file handling.
  2. SQLException: Errors related to database access.
  3. FileNotFoundException: Raised when a file cannot be located.

Code Example:

import java.io.*;

public class CheckedExceptionExample {
    public static void main(String[] args) {
        try {
            readFile("example.txt");
        } catch (IOException e) {
            System.out.println("An error occurred: " + e.getMessage());
        }
    }

    // Method that throws a checked exception
    public static void readFile(String fileName) throws IOException {
        FileReader file = new FileReader(fileName);
        BufferedReader br = new BufferedReader(file);

        String line;
        while ((line = br.readLine()) != null) {
            System.out.println(line);
        }

        br.close();
    }
}

Output:

An error occurred: example.txt (No such file or directory)

Code Explanation:

Since IOException is a Checked Exception, the compiler forces us to handle or declare it.

User-Defined Checked Exception:

Java allows programmers to create their own checked exceptions. This is useful when custom rules or constraints need to be enforced.

Note: This is also an example of Custom Exception.

Code Example:

// Custom exception class extending Exception (checked exception)
class InvalidAgeException extends Exception {
    public InvalidAgeException(String message) {
        super(message); // Pass the message to the parent Exception class
    }
}

public class UserDefinedCheckedException {
    // Method that throws a user-defined checked exception
    public void validateAge(int age) throws InvalidAgeException {
        if (age < 18) {
            // Throw custom checked exception if age is less than 18
            throw new InvalidAgeException("Age must be 18 or older.");
        }
        System.out.println("Age validation passed. You are eligible.");
    }

    public static void main(String[] args) {
        UserDefinedCheckedException example = new UserDefinedCheckedException();

        try {
            // Test with invalid age
            example.validateAge(16);
        } catch (InvalidAgeException e) {
            // Handle the exception
            System.out.println("Caught Exception: " + e.getMessage());
        }

        try {
            // Test with valid age
            example.validateAge(20);
        } catch (InvalidAgeException e) {
            System.out.println("Caught Exception: " + e.getMessage());
        }
    }
}

Output:

Caught Exception: Age must be 18 or older.
Age validation passed. You are eligible.

Code Explanation:

  1. Custom Exception (InvalidAgeException)
  1. UserDefinedCheckedException Class
  1. Main Method (main)
AspectChecked ExceptionsUnchecked Exceptions
DeclarationMust be declared using throws.No need to declare.
Compiler EnforcementEnforced by the compiler.Not enforced by the compiler.
Use CaseAnticipated conditions; recoverable.Programming errors; logical flaws.
ExamplesIOException, SQLException.ArithmeticException, NullPointerException.
InheritanceExtends Exception class (not RuntimeException).Extends RuntimeException class.

Basics of Exception Handelling

Exception handling in Java revolves around three main constructs:

  1. try Block: Encapsulates code that may generate an exception.
  2. catch Block: Handles the exception if it occurs.
  3. finally Block: Executes cleanup code regardless of whether an exception was thrown.
try {
    // Code that might throw an exception
}
catch (ExceptionType1 e) {
    // Handle specific exception
}
catch (ExceptionType2 e) {
    // Handle another type of exception
}
finally {
    // Cleanup code
}

Catching Exceptions

Single Exception

Handle a specific type of exception.

// Demonstrating a single checked exception using IOException
import java.io.IOException;

public class SingleExceptionExample {
    public static void main(String[] args) {
        // Use a try-catch block to handle the IOException
        try {
            // Simulate an I/O operation that throws an exception
            throw new IOException("Simulated I/O error");
        } catch (IOException e) {
            // Handle the exception and display the error message
            System.out.println("Caught an IOException: " + e.getMessage());
        }
        // Program continues execution after handling the exception
        System.out.println("Program execution continues smoothly.");
    }
}

Output:

Caught an IOException: Simulated I/O error
Program execution continues smoothly.

Code Explanation:

  1. Try Block:
  1. Throw Statement (throw new IOException(...)):
  1. Catch Block:
  1. Program Continuation:

Multiple Exception

Use multiple catch blocks for different exception types. The blocks are evaluated in sequence.

// Demonstrating multiple catch blocks to handle specific and general exceptions

import java.io.FileNotFoundException;
import java.io.IOException;

public class MultipleExceptionsExample {
    public static void main(String[] args) {

        // Use a try-catch block to handle multiple exception types
        try {

            // Simulate a file-related error by throwing FileNotFoundException
            throw new FileNotFoundException("File not found error");

        } catch (FileNotFoundException e) {

            // Catch and handle the more specific exception (FileNotFoundException)
            System.out.println("Caught FileNotFoundException: " + e.getMessage());

        } catch (IOException e) {

            // Catch and handle the more general exception (IOException)
            System.out.println("Caught IOException: " + e.getMessage());

        }

        // Program continues execution after handling the exceptions
        System.out.println("Program execution continues smoothly.");
    }
}

Note: Arrange catch blocks from the most specific to the most general. For example, IOException should follow FileNotFoundException.

Output:

Caught FileNotFoundException: File not found error
Program execution continues smoothly.

Code Explanation:

  1. Try Block:
  1. Multiple Catch Blocks:
  1. Order of Catch Blocks:
  1. Program Continuation:

Throwing Exceptions

Java allows developers to explicitly throw exceptions using the throw keyword. When a method can throw exceptions, it must declare them using the throws keyword in the method signature.

Compiler Enforcement: The Java compiler ensures that checked exceptions are either caught or declared in the calling code.

Code Example:

// Class to demonstrate exceptions with the `throws` keyword
public class ThrowsKeywordExample {

    /**
     * This method validates a person's age for voting eligibility.
     * It uses the `throws` keyword to declare that it can throw an
     * IllegalArgumentException.
     *
     * @param age The age of the person.
     * @throws IllegalArgumentException if the age is less than 18.
     */
    public void validateAge(int age) throws IllegalArgumentException {
        // Check if the age is less than 18
        if (age < 18) {
            // Throw an IllegalArgumentException with a custom message
            throw new IllegalArgumentException("Age must be 18 or above to vote.");
        }
        // If no exception occurs, print a success message
        System.out.println("Age is valid for voting.");
    }

    public static void main(String[] args) {
        // Create an instance of the class
        ThrowsKeywordExample example = new ThrowsKeywordExample();

        // Test cases with different ages
        int[] testAges = {16, 20, 15, 25};

        for (int age : testAges) {
            try {
                // Validate the age using the method
                example.validateAge(age);
            } catch (IllegalArgumentException e) {
                // Handle the exception and print the error message
                System.out.println("Caught an exception: " + e.getMessage());
            }
        }
    }
}

Output:

Caught an exception: Age must be 18 or above to vote.
Age is valid for voting.
Caught an exception: Age must be 18 or above to vote.
Age is valid for voting.

Code Explanation:

Custom Exceptions

Custom exceptions can be created by extending the Exception class. This allows developers to define application-specific errors.

// Custom exception class to handle negative values
class NegativeValueException extends Exception {
    // Constructor to initialize the exception with a custom message
    public NegativeValueException(String message) {
        super(message); // Pass the message to the parent Exception class
    }
}

// A class representing a simple linear list
public class LinearList {

    /**
     * Adds a value to the list.
     *
     * @param value The value to be added.
     * @throws NegativeValueException If the value is negative.
     */
    public void add(int value) throws NegativeValueException {
        // Check if the value is negative
        if (value < 0) {
            // Throw a custom exception for negative values
            throw new NegativeValueException("Negative value: " + value);
        }
        // Print a success message if the value is valid
        System.out.println("Value added: " + value);
    }

    public static void main(String[] args) {
        // Create an instance of the LinearList class
        LinearList list = new LinearList();

        // Use a try-catch block to handle the custom exception
        try {
            // Attempt to add a negative value
            list.add(-10);
        } catch (NegativeValueException e) {
            // Handle the custom exception and display the error message
            System.out.println("Caught Exception: " + e.getMessage());
        }

        // Attempt to add a valid positive value
        try {
            list.add(15);
        } catch (NegativeValueException e) {
            System.out.println("Caught Exception: " + e.getMessage());
        }

        // Program continues execution smoothly after handling exceptions
        System.out.println("Program execution continues.");
    }
}

Output:

Caught Exception: Negative value: -10
Value added: 15
Program execution continues.

Code Explanation:

  1. Custom Exception Class (NegativeValueException):
  1. Method Declaration with throws:
  1. Throwing the Custom Exception:
  1. Handling the Custom Exception:

Using finally for Cleanup

The finally block ensures that critical cleanup code runs regardless of whether an exception occurs.

Code Example:

// Demonstrating the use of finally for resource cleanup

public class FinallyExample {
    public static void main(String[] args) {

        // Simulate a resource by using a custom resource object
        CustomResource resource = null;

        try {
            // Allocate the resource
            resource = new CustomResource();
            System.out.println("Resource initialized successfully.");

            // Simulate an operation that throws an exception
            int result = 10 / 0; // This will cause an ArithmeticException
            System.out.println("Operation result: " + result);

        } catch (ArithmeticException e) {

            // Handle the specific exception
            System.out.println("Error occurred: " + e.getMessage());

        } finally {

            // Ensure the resource is cleaned up, regardless of an exception
            if (resource != null) {
                resource.cleanup();
            }
            System.out.println("Cleanup completed in finally block.");
        }

        // Program continues after exception handling
        System.out.println("Program execution continues.");
    }
}

// A custom resource class to simulate resource management
class CustomResource {
    // Constructor to initialize the resource
    public CustomResource() {
        System.out.println("CustomResource allocated.");
    }

    // Method to release the resource
    public void cleanup() {
        System.out.println("CustomResource cleaned up.");
    }
}

Output:

CustomResource allocated.
Resource initialized successfully.
Error occurred: / by zero
CustomResource cleaned up.
Cleanup completed in finally block.
Program execution continues.

Code Explanation:

  1. Custom Resource Simulation:
  1. The try Block:
  1. The catch Block:
  1. The finally Block:
  1. Use finally:

Exception Chaining

Java supports chaining exceptions to provide more context about an error. The Throwable class provides methods such as getCause() and initCause() to work with chained exceptions.

Code Example:

// Demonstrating exception chaining in Java
public class ExceptionChainingExample {
    public static void main(String[] args) {

        try {

            // Outer try-catch block to catch and handle RuntimeException

            try {

                // Inner try-catch block to simulate and handle an underlying exception
                throw new IllegalArgumentException("Invalid input provided");

            } catch (IllegalArgumentException e) {

                // Wrap the original exception (cause) into a higher-level exception
                throw new RuntimeException("Processing failed due to invalid input", e);
            }

        } catch (RuntimeException e) {

            // Handle the chained RuntimeException and display its message
            System.out.println("Caught Exception: " + e.getMessage());

            // Retrieve and display the cause of the RuntimeException
            Throwable cause = e.getCause();
            if (cause != null) {
                System.out.println("Caused by: " + cause.getMessage());
            }
        }

        // Program execution continues after handling the exception
        System.out.println("Program execution continues.");
    }
}

Output:

Caught Exception: Processing failed due to invalid input
Caused by: Invalid input provided
Program execution continues.

Code Explanation:

  1. Inner try-catch Block:
  1. Outer try-catch Block:
  1. Exception Chaining:

Methods Used:

Packages

In Java, a package serves as an organizational unit for grouping related classes and interfaces. Packages help prevent naming conflicts by allowing developers to create unique namespaces. They also facilitate better code organization and modularity.

Using Packages

To use classes from a package, the import statement is employed:

import java.math.BigDecimal; // Imports the BigDecimal class
import java.math.*; // Imports all classes in the java.math package

* imports all classes in the specified package but does not include sub-packages. For instance:

Benifits of Packages

  1. Namespace Management:
  1. Code Organization:
  1. Access Control:
  1. Reusability and Modularity:

Creating and Naming Packages

Developers can define custom packages by adhering to Java's naming conventions. The convention for package names follows the reverse of an organization's Internet domain name:

Internet DomainPackage Name
onlinedegree.iitm.ac.inin.ac.iitm.onlinedegree

Defining a Package

To include a class in a specific package, add a package declaration at the top of the .java file: Create a file named Employee.java inside a folder in/ac/iitm/onlinedegree.

// File: in/ac/iitm/onlinedegree/Employee.java
package in.ac.iitm.onlinedegree;

/**
 * Represents an Employee with a name.
 */
public class Employee {
    private String name; // Employee's name

    // Constructor to initialize the employee's name
    public Employee(String name) {
        this.name = name;
    }

    // Getter method for the employee's name
    public String getName() {
        return name;
    }
}

Using a Package

Create a file Main.java in the root directory or another package.

// File: Main.java
// Import the Employee class from the specified package
import in.ac.iitm.onlinedegree.Employee;

/**
 * Demonstrates the usage of packages in Java.
 */
public class Main {
    public static void main(String[] args) {
        // Create an Employee object
        Employee emp = new Employee("John Doe");

        // Access the public method of the Employee class
        System.out.println("Employee Name: " + emp.getName());
    }
}

Output:

Employee Name: John Doe

Code Explanation:

  1. Package Declaration:
  1. Import Statement:
  1. Folder Structure:

Directory Structure:

root/
├── in/
│   ├── ac/
│   │   ├── iitm/
│   │   │   ├── onlinedegree/
│   │   │   │   ├── Employee.java
├── Main.java
  1. Visibility Modifiers:

Visibility Modifiers

Visibility modifiers in Java allow developers to control access to classes, methods, and variables. This ensures encapsulation, one of the core principles of object-oriented programming (OOP). Proper use of visibility modifiers leads to better code security, modularity, and maintainability.

Types of Visibility Modifies:

Public:

Private:

Default (Package-Private):

Protected:

Code Example: Visibility Modifiers:

  1. Create the VisibilityExample Class
// File: in/ac/iitm/onlinedegree/VisibilityExample.java
package in.ac.iitm.onlinedegree;

/**
 * Demonstrates different visibility modifiers.
 */
public class VisibilityExample {
    public int publicVar = 100; // Accessible everywhere
    private int privateVar = 200; // Accessible only within this class
    int packagePrivateVar = 300; // Accessible only within the same package
    protected int protectedVar = 400; // Accessible within package and in subclasses

    // Public method: accessible everywhere
    public void showPublic() {
        System.out.println("Public Variable: " + publicVar);
    }

    // Private method: accessible only within this class
    private void showPrivate() {
        System.out.println("Private Variable: " + privateVar);
    }

    // Package-private method: accessible only within the same package
    void showPackagePrivate() {
        System.out.println("Package-Private Variable: " + packagePrivateVar);
    }

    // Protected method: accessible within package and subclasses
    protected void showProtected() {
        System.out.println("Protected Variable: " + protectedVar);
    }

    public void demonstratePrivateAccess() {
        // Private members are accessible within the same class
        showPrivate();
    }
}
  1. Create a Class in the Same Package
// File: in/ac/iitm/onlinedegree/SamePackageAccess.java
package in.ac.iitm.onlinedegree;

/**
 * Demonstrates access levels within the same package.
 */
public class SamePackageAccess {
    public static void main(String[] args) {
        VisibilityExample example = new VisibilityExample();

        // Accessing public member
        example.showPublic();

        // Accessing package-private member
        example.showPackagePrivate();

        // Accessing protected member
        example.showProtected();

        // Accessing private member (will not compile if uncommented)
        // example.showPrivate(); // ERROR: Private members are not accessible
    }
}
  1. Create a Class in a Different Package
// File: DifferentPackageAccess.java
package different;

import in.ac.iitm.onlinedegree.VisibilityExample;

/**
 * Demonstrates access levels from a different package.
 */
public class DifferentPackageAccess extends VisibilityExample {
    public static void main(String[] args) {
        VisibilityExample example = new VisibilityExample();

        // Accessing public member
        example.showPublic();

        // Accessing package-private member (will not compile if uncommented)
        // example.showPackagePrivate(); // ERROR: Package-private members are not accessible outside the package

        // Accessing protected member through inheritance
        DifferentPackageAccess inherited = new DifferentPackageAccess();
        inherited.showProtected();

        // Accessing private member (will not compile if uncommented)
        // example.showPrivate(); // ERROR: Private members are not accessible
    }
}

Output:

For SamePackageAccess Class

Public Variable: 100
Package-Private Variable: 300
Protected Variable: 400

For DifferentPackageAccess Class

Public Variable: 100
Protected Variable: 400

Code Explanation

  1. Public:
  1. Private:
  1. Package-Private:
  1. Protected:

Assertions

In Java, when developing functions or methods, it is important to ensure that the input parameters meet certain expectations or constraints for the function to work correctly. If these constraints are violated, the behavior of the program can become unpredictable or erroneous.

To handle this, developers often rely on two primary mechanisms: exceptions and assertions.

  1. Using Exceptions: Validates constraints and throws an exception when violated.
  2. Using Assertions: Validates assumptions during development and testing but does not trigger during production runtime.

Validating Constraints with Exceptions

For public functions that are accessed by external code, it is common to enforce parameter constraints using exceptions.

For example: If a negative value is passed to the function, an IllegalArgumentException is thrown. This approach ensures that the function's contract is upheld and communicates the error to the calling code.

public class MyFunctionExample {
    // Method to calculate the square root of x, assuming x >= 0
    public static double myfn(double x) throws IllegalArgumentException {
        // Check if x is less than 0 and throw an exception if true
        if (x < 0) {
            throw new IllegalArgumentException("x < 0: Cannot calculate the square root of a negative number.");
        }
        // Return the square root of x if valid
        return Math.sqrt(x);
    }

    public static void main(String[] args) {
        // Test cases to demonstrate the behavior of myfn

        // Test case 1: Valid input (positive number)
        try {
            double result1 = myfn(16); // Expected output: 4.0
            System.out.println("Square root of 16: " + result1);
        } catch (IllegalArgumentException e) {
            System.out.println("Error: " + e.getMessage());
        }

        // Test case 2: Valid input (0)
        try {
            double result2 = myfn(0); // Expected output: 0.0
            System.out.println("Square root of 0: " + result2);
        } catch (IllegalArgumentException e) {
            System.out.println("Error: " + e.getMessage());
        }

        // Test case 3: Invalid input (negative number)
        try {
            double result3 = myfn(-4); // Should throw an exception
            System.out.println("Square root of -4: " + result3);
        } catch (IllegalArgumentException e) {
            System.out.println("Error: " + e.getMessage()); // Expected error message
        }
    }
}

Output

Square root of 16: 4.0
Square root of 0: 0.0
Error: x < 0: Cannot calculate the square root of a negative number.

Code Explanation

  1. Method myfn(double x):
  1. main Method:

Using Assertions for Internal Checks

Assertions provide a lightweight mechanism for validating assumptions during development. Unlike exceptions, assertions are typically used for internal, private methods where parameter constraints are assumed to be met by the developer's code.

Code Example

public class AssertionExample {
    // Method to calculate the square root of x, assuming x >= 0 (checked with an assertion)
    public static double myfn(double x) {
        // Use assertion to ensure that x is non-negative
        assert x >= 0 : "x must be non-negative"; // Throws AssertionError if x < 0
        return Math.sqrt(x);
    }

    public static void main(String[] args) {
        // Enabling assertions requires the JVM to be run with the -ea flag.
        // Example: java -ea AssertionExample

        // Test case 1: Valid input (positive number)
        try {
            double result1 = myfn(16); // Expected output: 4.0
            System.out.println("Square root of 16: " + result1);
        } catch (AssertionError e) {
            System.out.println("Assertion failed: " + e.getMessage());
        }

        // Test case 2: Valid input (0)
        try {
            double result2 = myfn(0); // Expected output: 0.0
            System.out.println("Square root of 0: " + result2);
        } catch (AssertionError e) {
            System.out.println("Assertion failed: " + e.getMessage());
        }

        // Test case 3: Invalid input (negative number)
        try {
            double result3 = myfn(-4); // Should throw AssertionError
            System.out.println("Square root of -4: " + result3);
        } catch (AssertionError e) {
            System.out.println("Assertion failed: " + e.getMessage()); // Expected error message
        }
    }
}

Running the Program

To run the program and ensure assertions are enabled, you must specify the -ea (enable assertions) flag when running the program. Here’s how you would run the program from the command line:

java -ea AssertionExample

Output

Square root of 16: 4.0
Square root of 0: 0.0
Assertion failed: x must be non-negative

Code Explanation

  1. Method myfn(double x):
  1. main Method:

Features of Assertions

  1. Abort on Failure: When an assertion fails, an AssertionError is thrown, aborting the program.
  2. Diagnostic Information: The error message and stack trace help identify the source of the failure.
  3. Not for Runtime Recovery: Assertions are not meant to be caught or handled during runtime. They indicate programming errors that need to be fixed during development.

Code Example

public static double myfn(double x) {
    assert x >= 0 : "Invalid input: " + x;
    return Math.sqrt(x);
}

Code Explanation

If x is negative, the program terminates with an AssertionError, and the message Invalid input: <value> is displayed.

Enabling and Disabling Assertions in Java

Assertions in Java can be enabled or disabled at runtime using JVM options, providing flexibility without needing to modify or recompile the code. This allows developers to control when and where assertions should be active, aiding in debugging and development while avoiding unnecessary overhead in production environments.

Runtime Configuration

Assertions are enabled or disabled at runtime using JVM options, without requiring code changes or recompilation.

To enable assertions in Java, you can use the -ea or -enableassertions option with the JVM.

  1. Globally (for all classes in the program):
java -ea MyCode

This enables assertions for the entire application, including all classes.

  1. For a Specific class:
java -ea:com.example.MyClass MyCode

This enables assertions only for the class com.example.MyClass. Replace com.example.MyClass with the fully qualified name of any class you want to target.

  1. For a Package:
java -ea:com.example.package MyCode

This enables assertions for all classes within the com.example.package package.

You can also disable assertions, either globally or for specific parts of the code, by using the -da or -disableassertions option.

  1. Globally (for all classes):
java -da MyCode

This disables assertions throughout the entire application.

  1. For a specific class:
java -da:com.example.MyClass MyCode

This disables assertions for the class com.example.MyClass, while leaving assertions enabled for other classes.

  1. For a package:
java -da:com.example.package MyCode

This disables assertions for all classes within the com.example.package package.

Combining Options: Selective Enabling and Disabling

Java allows you to combine enabling and disabling assertions for specific parts of the application, offering more fine-grained control over which assertions are active.

For example:

java -ea:com.example.package
-da:com.example.package.MyClass MyCode

This approach helps in cases where you want to test assertions in most of your code but exclude certain classes or packages from being checked.

Summary of Options

OptionEffect
-ea or -enableassertionsEnable assertions globally or for specific classes or packages.
-da or -disableassertionsDisable assertions globally or for specific classes or packages.
-ea:package.nameEnable assertions for all classes in the specified package.
-ea:package.name.ClassNameEnable assertions for a specific class in a package.
-da:package.nameDisable assertions for all classes in the specified package.
-da:package.name.ClassNameDisable assertions for a specific class in a package.

When to Use Assertions

Assertions vs. Exceptions

FeatureAssertionsExceptions
PurposeValidate assumptions during testingHandle runtime errors gracefully
When UsedDevelopment and debuggingProduction
Runtime BehaviorDisabled in productionAlways enabled
HandlingDo not catchShould be caught and handled

Logging

Effective logging is essential for diagnosing issues and maintaining traceability in software systems. While print statements are a simple way to track program behavior, they lack flexibility, clutter the code, and are difficult to manage in complex systems. Logging provides a structured and configurable solution for generating diagnostic messages, enabling developers to monitor, debug, and audit their applications efficiently.

The Need for Logging

Print statements have significant drawbacks:

Logging addresses these issues by:

Basics of Logging

The simplest way to log messages in Java is to use the global logger:

import java.util.logging.Level;
import java.util.logging.Logger;

public class LoggingExample {
    public static void main(String[] args) {
        // Obtain the global logger
        Logger globalLogger = Logger.getGlobal();

        // Example 1: Default logging behavior
        System.out.println("Example 1: Default Logging Behavior");
        globalLogger.info("Default: Application started.");
        globalLogger.warning("Default: A potential issue detected.");
        globalLogger.severe("Default: Critical error occurred!");

        // Example 2: Suppress logging by setting level to OFF
        System.out.println("\nExample 2: Logging Suppressed");
        globalLogger.setLevel(Level.OFF); // Suppress all logs
        globalLogger.info("This log will not be shown.");
        globalLogger.warning("This log will not be shown either.");
        globalLogger.severe("Even severe logs are suppressed.");

        // Example 3: Customizing the log level
        System.out.println("\nExample 3: Custom Log Level");
        globalLogger.setLevel(Level.WARNING); // Log only WARNING and above
        globalLogger.info("This INFO log is suppressed.");
        globalLogger.warning("This WARNING log is displayed.");
        globalLogger.severe("This SEVERE log is displayed.");
    }
}

Output

Example 1: Default Logging Behavior
Jan 8, 2025, 10:30:15 PM LoggingExample main
INFO: Default: Application started.
Jan 8, 2025, 10:30:15 PM LoggingExample main
WARNING: Default: A potential issue detected.
Jan 8, 2025, 10:30:15 PM LoggingExample main
SEVERE: Default: Critical error occurred!
Example 2: Logging Suppressed
(No logs are displayed.)
Example 3: Custom Log Level
Jan 8, 2025, 10:30:15 PM LoggingExample main
WARNING: This WARNING log is displayed.
Jan 8, 2025, 10:30:15 PM LoggingExample main
SEVERE: This SEVERE log is displayed.

Code Explanation

  1. Obtaining the Global Logger:
  1. Logging Levels:
  1. Customizing the Logging Level:
  1. Suppressing Logs:

Custom Loggers

In Java, custom loggers allow you to organize and manage logging more effectively, especially in larger projects. Loggers can be structured hierarchically, similar to package names, providing fine-grained control over logging for specific parts of your application.

import java.util.logging.Level;
import java.util.logging.Logger;

public class CustomLoggerExample {
    // Creating a custom logger
    private static final Logger parentLogger = Logger.getLogger("in.ac.iitm");
    private static final Logger childLogger = Logger.getLogger("in.ac.iitm.onlinedegree");

    public static void main(String[] args) {
        System.out.println("Custom Logger Example:");

        // Example 1: Default logging behavior of custom loggers
        System.out.println("\nExample 1: Default Logging Behavior");
        parentLogger.info("Parent logger: This is an informational message.");
        childLogger.info("Child logger: This is an informational message.");

        // Example 2: Setting log levels for the parent logger
        System.out.println("\nExample 2: Setting Parent Logger Level");
        parentLogger.setLevel(Level.WARNING); // Logs only WARNING and SEVERE for parent and its children
        parentLogger.info("Parent logger: This INFO message is suppressed.");
        parentLogger.warning("Parent logger: This WARNING message is displayed.");
        childLogger.info("Child logger: This INFO message is suppressed by the parent.");
        childLogger.severe("Child logger: This SEVERE message is displayed.");

        // Example 3: Setting log levels specifically for the child logger
        System.out.println("\nExample 3: Setting Child Logger Level");
        childLogger.setLevel(Level.INFO); // Logs INFO, WARNING, and SEVERE for the child logger
        parentLogger.warning("Parent logger: This WARNING message is displayed.");
        childLogger.info("Child logger: This INFO message is now displayed.");
        childLogger.warning("Child logger: This WARNING message is displayed.");
    }
}

Output

Example 1: Default Logging Behavior
Jan 8, 2025, 10:30:15 PM in.ac.iitm
INFO: Parent logger: This is an informational message.
Jan 8, 2025, 10:30:15 PM in.ac.iitm.onlinedegree
INFO: Child logger: This is an informational message.
Example 2: Setting Parent Logger Level
Jan 8, 2025, 10:30:15 PM in.ac.iitm
WARNING: Parent logger: This WARNING message is displayed.
Jan 8, 2025, 10:30:15 PM in.ac.iitm.onlinedegree
SEVERE: Child logger: This SEVERE message is displayed.
Example 3: Setting Child Logger Level
Jan 8, 2025, 10:30:15 PM in.ac.iitm
WARNING: Parent logger: This WARNING message is displayed.
Jan 8, 2025, 10:30:15 PM in.ac.iitm.onlinedegree
INFO: Child logger: This INFO message is now displayed.
Jan 8, 2025, 10:30:15 PM in.ac.iitm.onlinedegree
WARNING: Child logger: This WARNING message is displayed.

Code Explanation

  1. Creating Custom Loggers:
  1. Hierarchy of Loggers:
  1. Log Levels:
  1. Logging Behavior:

Logging Levels

Java provides seven logging levels to categorize messages:

  1. SEVERE
  2. WARNING
  3. INFO (default)
  4. CONFIG
  5. FINE
  6. FINER
  7. FINEST

You can control the level of logging:

logger.setLevel(Level.FINE);

Advanced Configurations and Advantages of Logging

Logging behavior can also be controlled externally using a configuration file. This allows you to adjust logging levels or direct output without modifying the code.

Advantages of Logging: