Wednesday, June 23, 2021

Java Tutorial: Exception Handling

Chapters

Java Exception Handling

Exception handling is the mechanism in java that handles run-time errors in a program. Exception is an abnormal condition that disrupts the normal flow of a program. Java has classes that are thrown everytime our program encounters these runtime errors. These classes are the subclasses of Throwable class which is the superclass of all exceptions and errors in the java language.

Throwable class has direct subclasses which are Error and Exception. These subclasses are conventionally used to indicate that exceptional situations have occurred.
Errors are serious and unrecoverable; regular applications shouldn't try to catch errors.
Exceptions are abnormal conditions that regular applications should try to catch.

In java, exceptions/errors can be categorized into two sections: Checked Exceptions and Unchecked Exceptions.

Checked Exceptions: These exceptions are checked at compile-time. We are required to handle these exceptions and java won't compile our source code if we don't handle these exceptions. For example, we use FileReader class in our source code. This class may throw IOException and FileNotFoundException; IOException and FileNotFoundException are checked exceptions.

That's why java requires us to handle IOException or FileNotFoundException everytime we use FileReader class and other stream classes that may throw these exceptions.
Example

import java.io.*;

public class SampleClass{

  public static void main(String[]args){
    String path = "C:"+File.separator+"tmp"+
                  File.separator+"test.txt";
    //This code below may throw FileNotFoundException
    //'cause file in the path might not be existed
    //this source code won't compile cause 
    //FileNotFoundException is not handled
    FileReader reader = new FileReader(path);
  }
}
Unchecked Exceptions: These exceptions are checked at run-time. Unlike checked exceptions, we are not required to handle unchecked exceptions. For example, if we try to access an array index that is less than 0 or greater than the length of the array then, java will throw an ArrayIndexOutOfBoundsException which is an unchecked exception.
Example

//This source code will be compiled just fine.
//However, once we run the compiled version
//of this source code then java will implicitly
//throw an ArrayIndexOutOfBoundsException and terminate
//the program

public class SampleClass{

  public static void main(String[]args){
  
    String[] str = {"a","b"};
    
    System.out.println("First Element: " +str[0]);
    
    //This println() below makes java
    //throw an ArrayIndexOutOfBoundsException
    //Since we didn't handle the exception
    //our program will terminate right
    //here and won't execute any codes
    //next to this println()
    System.out.println(str[2]);
    
    //this won't be executed
    System.out.println("Second Element: " + str[1]);
  }
}
You might ask: "How can we determine if an exception is checked or unchecked?". According to Throwable class documentation: " For the purposes of compile-time checking of exceptions, Throwable and any subclass of Throwable that is not also a subclass of either RuntimeException or Error are regarded as checked exceptions."

In other words, Error,RuntimeException and their subclasses are unchecked exceptions, Other than that, all exceptions are checked exceptions. Don't be confused between RuntimeExceptions and exceptions that are thrown at run-time.

Generally, all exceptions are thrown at run-time. One of the purposes of RuntimeException class is to separate some exceptions that are checked at run-time from exceptions that are checked at compile-time.

Don't be confused between the programming term "exception" and the Exception class in java. Exception(or run-time error), in programming term, is an abnormal condition that disrupts the normal flow of a program whereas Exception class separates "exceptions" that should be caught from "exceptions" that shouldn't be caught.

Don't be confused between compile-time errors and exceptions. compile-time errors are errors thrown during compile-time whereas exceptions are thrown during run-time. Generally, compile-time errors and run-time errors are called errors.

Compile-time warning is like compile-time error. However, warnings don't stop the compiler from compiling our source code. Nevertheless, we should resolve warnings to have clean code.

try-catch clause

try-catch clause is used to enclose a statement or group of statements that may throw an exception. we use try-catch clause to catch an exception and to avoid abrupt termination of our program due to uncaught exceptions.
public class SampleClass{
  
  public static void main(String[]args){
    String str = null;
    
    try{
      
      //Switch block will throw 
      //NullPointerException 'cause
      //switch doesn't accept null
      //values
      switch(str){
      
        case "A":
        break;
        
        case "B":
        break;
      }
      
      System.out.println("This code won't be executed");
    }
    catch(NullPointerException e){
      
      if(str == null)
         System.out.println("NullPointerException has been"
                        +" thrown in the try block because"
                        +" str is null");
      else
        System.out.println("NullPointerException has been"
                        +" thrown in the try block");
    }
    
    //This code will be executed
    System.out.println("Exiting program...");
    
  }
}
As you can see in the example above, statements next to switch block are not executed because once the execution encounters an exception in the try block, it will leave the try block and it will check if the exception in the catch parentheses matches the exception thrown in the try block.

If match then, the execution will execute the catch block and execute the rest. Otherwise, It means that we failed to catch the exception thrown in the try block so, catch block is not executed and the program is terminated.

We can't put a checked exception in catch block if there's no code in the try block that will throw that checked exception.
import java.io.*;
public class SampleClass{

  public static void main(String[]args){
  
    try{
      String str = "A";
      str = str.concat(str);
    }
    catch(IOException e){
      System.out.println("IOException!");
    }
  }
}
The example above will throw a compile-time error: exception IOException is never thrown in the body of corresponding try statement.

Multiple Catch Blocks

There are times that we need to catch multiple exceptions. In order to do that, we can write multiple catch blocks in one try block. We need to follow an order when writing multiple catch blocks, the most specific exception comes first and the most general exception comes last. We can compare this order to the subclass-to-superclass order,if you will, where subclass is the most specific and superclass is the most general.
import java.io.*;
public class SampleClass{

  public static void main(String[]args){
  
    String path = "C:"+File.separator+"test"
                 +File.separator+"read.txt";
    System.out.println("Path: " + path);
    try{
      //if the path is invalid then, this code
      //below will throw a FileNotFoundException
      FileReader reader = new FileReader(path);
      
      
      String content = "";
      //This will make the concat() to throw a
      //NullPointerException
      //String content = null;
      
      int c = 0;
      while((c = reader.read()) != -1)
         content = content.concat(String.valueOf((char)c));
         
      //Always close streams after you're done using it
      reader.close();
      
      System.out.println(content);
      
    }
    //This exception is a subclass of
    //IOException
    catch(FileNotFoundException e){
      System.out.println("File Not Found!");
    }
    //This exception is a subclass of
    //RuntimeException
    catch(NullPointerException e){
      System.out.println("null value is improperly"
                        +" used!");
    }
    //Exception is the parent class of
    //all throwable exceptions
    //(except for exceptions in Error class)
    catch(Exception e){
      System.out.println("An exception has been"+
                         " caught!");
    }
  }
}
We will encounter a compile-time error if we try to put a superclass exception first before its subclasses.
import java.io.*;
public class SampleClass{

  public static void main(String[]args){
  
    String path = "C:"+File.separator+"test"
                 +File.separator+"read.txt";
    System.out.println("Path: " + path);
    try{
      FileReader reader = new FileReader(path);
      
      String content = "";
      //This will make the concat() to throw a
      //NullPointerException
      //String content = null;
      
      int c = 0;
      while((c = reader.read()) != -1)
         content = content.concat(String.valueOf((char)c));
         
      //Always close streams after you're done using it
      reader.close();
      
      System.out.println(content);
      
    }
    //error parent class exception must come 
    //after its subclasses
    catch(Exception e){
      System.out.println("An exception has been"+
                         " caught!");
    }
    catch(FileNotFoundException e){
      System.out.println("File Not Found!");
    }
    catch(NullPointerException e){
      System.out.println("null value is improperly"
                        +" used!");
    }
    
  }
}
Multiple Exceptions in Catch Block

We can put multiple exceptions in one catch block. We're going to use the "|" symbol which is also a Bitwise Inclusive OR(|) Operator to separate multiple exceptions.
import java.io.*;
public class SampleClass{

  public static void main(String[]args){
  
    String path = "C:"+File.separator+"test"
                 +File.separator+"read.txt";
    System.out.println("Path: " + path);
    try{
      FileReader reader = new FileReader(path);
      
      String content = "";
      //This will make the concat() to throw a
      //NullPointerException
      //String content = null;
      
      int c = 0;
      while((c = reader.read()) != -1)
         content = content.concat(String.valueOf((char)c));
         
      //Always close streams after you're done using it
      reader.close();
      
      System.out.println(content);
      
    }
    //multiple exceptions in a catch block
    catch(NullPointerException | IOException e){
          //we use printStackTrace()
          //to get more detailed message
          //about the thrown exception
          e.printStackTrace();
    }
    
  }
}
In the example above, we use the printStackTrace() method. This method returns a stack trace about the exception that calls printStrackTrace(). This method can give us detailed information about the exception, especially the line number where the exception occurs. printStackTrace() doesn't terminate a program after it's invoked.

We can't put a superclass and its subclass at the same catch parentheses.
    catch(FileNotFoundException | IOException e){
          //we use printStackTrace()
          //to get more detailed message
          //about the thrown exception
          e.printStackTrace();
    }
This code above will throw a compile-time error because FileNotFoundException is a subclass of IOException.

The Finally Block

We can add another block next to the try-catch clause and that block is the finally block. Finally block will be executed whether there's an exception caught or not or there's an uncaught exception in the try clause. This block is preferrably used to close streams or connections.
import java.io.*;
public class SampleClass{

  public static void main(String[]args){
  
    String path = "C:"+File.separator+"test"
                 +File.separator+"read.txt";
    System.out.println("Path: " + path);
    FileReader reader = null;
    try{
      reader = new FileReader(path);
      
      String content = "";
      
      int c = 0;
      while((c = reader.read()) != -1)
         content = content.concat(String.valueOf((char)c));
      
      System.out.println(content);
      
    }
    catch(NullPointerException | IOException e){
          e.printStackTrace();
    }
    //finally block. This block will be executed
    //whether there's an exception caught or not
    //in the try-catch clause
    finally{
      //nested try-catch
      //Always close streams after you're done using it
      try{ 
        if(reader != null)
          reader.close();
      }
      catch(IOException e){ e.printStackTrace(); }
      System.out.println("Stream is closed.");
    }
    
  }
}
We put the close() method in the finaly block. finally block will ensure that the close() method will be executed whether there's an exception, uncaught exception or no exception at all, once the execution exits the try block.

We can use the finally block without the catch block.
public class SampleClass{

  public static void main(String[]args){

    try{
      
      String s = "A";
      s = s.concat(s);
      System.out.println("Try block...");
      System.out.println(s);
    }
    finally{
      String t = "B";
      t = t.concat(t);
      System.out.println("finally block...");
      System.out.println(t);
    }
  }
}
If a method exits because of return statement, a finally block will still be executed.
public class SampleClass{

  static int divide(int num1, int num2){
  
    try{
    
      return num1/num2;
    }
    finally{
      System.out.println("Finally block!!");
    }
    
  }

  public static void main(String[] args){
  
    int num = SampleClass.divide(4,2);
    System.out.println(num);
  }
}

Result
Finally block!!
2
Nested try-catch

try-catch(-finally) clause can be nested in another try-catch(-finally) clause.
import java.io.*;
public class SampleClass{

  public static void main(String[]args){
    
    String path = "C:"+File.separator+
                  "test"+File.separator+
                  "test.txt";
    FileWriter writer = null;
    try{
    
      String text1 = "text1";
      
      //nested try-catch
      try{
        String text2 = null;
        text1 = text1.concat(text2);
      }
      catch(NullPointerException e){
        System.out.println("text2 is null "
                          +"can't concatenate with"
                          +" text1");
      }
      System.out.println("Writing text to text file...");
      
      //note: instantiating FileWriter automatically
      //overwrites the specified file in the path
      //assigned to FileWriter. Overwritten file is
      //unrecoverable
      writer = new FileWriter(path);
      writer.write(text1);
      
      System.out.println("Operation Successful!");
    }
    catch(IOException e){
      System.out.println("An IOException"
                        +" has been caught!");
      e.printStackTrace();
    }
    finally{
     System.out.println("Closing Stream...");
     //nested try-catch
     try{ writer.close(); }
     catch(IOException e){
       e.printStackTrace();
     }
    }
    System.out.println("Stream is closed!");
  }
}
try-with-resources

We can use try-with-resources if we're handling streams or connections and we want those to be closed automatically. The streams declared in the try-with-resources parentheses will be closed automatically whether java is done executing the try-with-resources or an exception(caught/uncaught) occurs. Note: streams and connections must have an implementation of AutoCloseable or Closeable interface.
import java.io.*;
public class SampleClass{

  public static void main(String[]args){
    
    String readPath = "C:"+File.separator+
                      "test"+File.separator+
                      "read.txt";
    String writePath = "C:"+File.separator+
                       "test"+File.separator+
                       "write.txt";
                       
    //Use semicolon to separate two or more
    //resources. Otherwise, omit the semicolon
    //Single resource
    //try(FileReader reader = new FileReader()){}
    //multiple resources
    try(FileReader reader = new FileReader(readPath);
        FileWriter writer = new FileWriter(writePath)){
        
        String content = "";
        int c = 0;
        while((c = reader.read()) != -1)
         content = content.concat(String.valueOf((char)c));
      
        System.out.println(content);
        
        writer.write(content);
        System.out.println("Operation Completed!");
    }
    catch(IOException e){
      e.printStackTrace();
    }
    //We can still add the finally block as part of 
    //try-with-resources
    finally{
      System.out.println("finally block is executed!");
    }
    
  }
}
The throw Keyword

We can use the throw keyword to create a throw statement and to explicitly throw an exception.
General Form: throw new exception-name();

public class SampleClass{

  public static void main(String[]args){
  
    String str = null;
    
    if(str == null)
      throw new NullPointerException("Null"
                +" values are not allowed here!");
    else
      str = str.concat(str);
  }
}
We can also use the throw keyword to rethrow exceptions. Remember, Throwing exception using throw keyword is still a thrown exception and needs to be caught if we don't want our program to terminate.

The throws Keyword

We can use the throws keyword to declare exceptions in a methods/constructors. We can declare multiple exceptions by separating them using comma(,).
import java.io.*;
import java.text.ParseException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
public class SampleClass{
  
  //We can declare an exception in a constructor
  //SampleClass() throws IOException{ //codes }
  
  //use throws keyword to declare IOException
  //in this method
  String readFile() throws IOException{
    String readPath = "C:"+File.separator+
                      "test"+File.separator+
                      "read.txt";
                      
    FileReader reader = new FileReader(readPath);
    String content = "";
    int c = 0;
    while((c = reader.read()) != -1)
         content = content.concat(String.valueOf((char)c));
      
    System.out.println(content);
    reader.close();
    
    return content;
  }
  
  //separate multiple exceptions by comma(,)
  //when declaring them in a method
  void writeData(String data) throws IOException,ParseException{
    String writePath = "C:"+File.separator+
                       "test"+File.separator+
                       "write.txt";
    
    String strDate = "2000-10-10";
    DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
    Date date = dateFormat.parse(strDate);
    
    FileWriter writer = new FileWriter(writePath);
    
    writer.write(data);
    writer.close();
    System.out.println("Data Is Written In " + date);
  }
  
  public static void main(String[]args){
    SampleClass sc = new SampleClass();
    
    try{sc.writeData(sc.readFile());}
    catch(IOException | ParseException e){
      e.printStackTrace();
    }
    
  }
}
If a declared exception is thrown in method block and it's not caught then, that method will drop the thrown exception down the call stack. A call stack is a list of chained methods/constructors that are called. The methods/constructors at the top of the stack is the last method that is called in the chain.

One of the uses of throws keyword is to propagate checked exceptions, which we will discuss later. That's why you will see that throws keyword is mostly used in conjunction with checked exceptions.

Exception Propagation

Exception Propagation is a process of throwing exceptions down the call stack. A call stack is a list of chained methods that are called. Unchecked exceptions are automatically propagated whereas checked exceptions need to be propagated by declaring exceptions in method/constructor blocks by using the throws keyword.

First off, Let's create an example to demonstrate unchecked exception propagation.
public class SampleClass{
  
  String addLetter(String message){
    String letterToAdd = null;
    
    return message.concat(letterToAdd);
  }
  
  String addNumber(String message){
    String numToAdd = "1";
    
    return message.concat(numToAdd);
  }
  
  void displayMessage(String message){
    System.out.println("Message: " + message);
  }
  
  public static void main(String[]args){
    SampleClass sc = new SampleClass();
    
    String message = "My Message";
    try{
      sc.displayMessage(sc.addNumber(sc.addLetter(message)));
    }
    catch(NullPointerException e){
      e.printStackTrace();
    }
    
  }
}

Result:
java.lang.NullPointerException
    at java base/java.lang.String.concat(String.java:1465)
    at SampleClass.addLetter(SampleClass.java:6)
    at SampleClass.main(SampleClass.java:24)
By using printStackTrace() we can see where the exception happens. In the example above, NullPointerException is thrown from java.lang.String.concat() method then, concat() with exception is called in addLetter() then, addLetter() throws the exception in addNumber() then, in displayMessage() and then, the exception is caught by try-catch in main().

We also see that we don't need to declare NullPointerException in method block in order to be propagated in the main().

Now, let's try propagating checked exception.
import java.io.*;
public class SampleClass{
  
  //Declare IOException here because we
  //don't want to catch IOException in this method
  //we want to propagate IOException in
  //the main() method and catch the exception
  //there
  String readFile() throws IOException{
    String readPath = "C:"+File.separator+
                      "test"+File.separator+
                      "read.txt";
                      
    FileReader reader = new FileReader(readPath);
    String content = "";
    int c = 0;
    while((c = reader.read()) != -1)
         content = content.concat(String.valueOf((char)c));
      
    System.out.println(content);
    reader.close();
    
    return content;
  }
  
  //We are required to declare IOException here because
  //we are using readFile() in this method block and
  //we need to redeclare IOException in order to
  //move down the thrown IOException in readFile()
  //into displayMessage() then in the main() to be
  //caught.
  void displayMessage() throws IOException{
    System.out.println("Message: " + readFile());
  }
  
  public static void main(String[]args){
    SampleClass sc = new SampleClass();
    
    try{ sc.displayMessage(); }
    catch(IOException e){
      e.printStackTrace();
    }
    
  }
}
readFile() will throw an IOException, FileNotFoundException to be exact, if the specified file in the path of readPath doesn't exist. Exception propagation is useful if you want multiple exceptions from different sources to be caught in a single try-catch(-finally) clause.

Wrapping and Rethrowing Exception

We can wrap an exception into another exception to show a more specific exception.
import java.io.*;
public class SampleClass{

  public static void main(String[]args){
  
    String writePath = "C:"+File.separator+
                       "test"+File.separator+
                       "write.txt";
    
    String content = null;
    File file = new File(writePath);
    try{
      content = content.concat(content);
      FileWriter writer = new FileWriter(file);
      writer.write(content);
      System.out.println("Content has been written!");
    }
    catch(IOException | NullPointerException e){
      
      if(content == null)
        throw new RuntimeException(e);
      else
        e.printStackTrace();
    }
    
  }
}
In the example above, we wrap "e" into RuntimeException. Now, java will throw a RuntimeException and the cause of the RuntimeException which is the NullPointerException.

We can rethrow an exception if we want to do some kind of task, for example logging, before throwing the exception.
public class SampleClass{

  public static void main(String[]args){
 
    try{
      String str = null;
      str = str.concat(str);
    }
    catch(NullPointerException e){
      System.out.println("Add to log file: str is null");
      throw e;
    }
    
  }
}
This way, we preserve the stack trace of the exception that will be displayed on the console while adding the exception to a log.

Note: Unchecked exceptions can be easily rethrown. However, checked exceptions are not preferrable to be rethrown.
import java.io.*;
public class SampleClass{

  public static void main(String[]args){
 
    try{
      FileReader reader = new FileReader("C:\\Temp\\test.txt");
    }
    catch(IOException e){
      System.out.println("Add to log file: an IOException");
      //error
      throw e;
      
      //fix but a bad code design
      //unnecessary use of try-catch
      //clause
      /*
      try{
          throw e;
      }catch(IOException s){s.printStackTrace()}
      */
      
    }
    
  }
}

Custom Exception

We can create our own exception by overriding Exception or RuntimeException class. If we want to create a checked exception then, we override the Exception class. If we want to create an unchecked exception then, we override the RuntimeException class.
public class SampleClass{
  
  //LongPathException is checked exception. Therefore, we
  //need to declare it in this method block since we want to
  //catch it in the main()
  void checkPathLength(String path) throws LongPathException{
    if(path.length() > 255)
      throw new LongPathException("Path name is very long!");
  }
  
  //PanelNotCompatibleException is an unchecked exception.
  //Therefore, we don't need to declare it in this method
  //block because unchecked exception is automatically 
  //propagated
  void initMainPanel(SystemPanel sp){
    
    if(sp instanceof MainPanel)
       System.out.println("MainPanel" + sp.getName() +
                          "has been initialized!");
    else
     throw new PanelNotCompatibleException(sp.getName()+" is not a main"+
                                           " panel!");
    
  }
  
  public static void main(String[]args){
    SampleClass sc = new SampleClass();
    
    String path = "C:\\Users\\User1\\My Documents\\Some"+
                  " Files that needed to be opened\\"+
                  " Bunch of files that can be opened"+
                  " if we want to\\files with very long"+
                  " pathnames\\This file has a very long"+
                  " long long long long long long long"+
                  " long long long long long long long"+
                  " long long long long long long long"+
                  " long long long long long long long"+
                  " path name.txt";
    try{
      //This will throw the PanelNotCompatibleException
      sc.initMainPanel(new SubPanel("Sub Panel"));
      
      //This will throw the LongPathException
      sc.checkPathLength(path);
    }
    catch(LongPathException | PanelNotCompatibleException e){
      e.printStackTrace();
    }
    
  }
}

class SystemPanel{
  private String name;
  
  SystemPanel(String name){
    this.name = name;
  }
  
  String getName(){
    return name;
  }
  
}

class MainPanel extends SystemPanel{
  private int panelID = 1;
  
  MainPanel(String name){
    super(name);
  }
  
  int getID(){
    return panelID;
  }
}

 class SubPanel extends SystemPanel{
  private int panelID = 2;
  
  SubPanel(String name){
    super(name);
  }
  
  int getID(){
    return panelID;
  }
}

//checked exception
class LongPathException extends Exception{
  
  LongPathException(String message){
    super(message);
  }
}

//unchecked exception
class PanelNotCompatibleException extends RuntimeException{

  PanelNotCompatibleException(String message){
    super(message);
  }
}
Suppressed Exceptions

Normally, a method/constructor with declared exception or try block can only throw one exception at a time. Though, Sometimes, two or more exceptions can be thrown in try-catch(-finally) or try-with-resources, if that happens then some exceptions might be suppressed.
public class SampleClass{

  public static void main(String[]args){
  
    try{
      //NullPointerException
      String s = null;
      s = s.concat(s);
    }
    finally{
      //ArithmeticException: can't be
      //divided by 0
      int num = 10/0;
    }
    
  }
}
In the example above, two exceptions are thrown: NullPointerException and ArithmeticException. However, only the AritmeticException will be shown on the console because NullPointerException has been suppressed. Let's try try-with-resources.
import java.io.*;
public class SampleClass{

  public static void main(String[]args){
                       
    try(DummyResource dr = new DummyResource()){
      dr.divideByZero();

    }
    finally{
     throw new IllegalArgumentException("Bad Argument!");
    }
    
  }
}

class DummyResource implements AutoCloseable{
  
  void divideByZero(){
    int num = 10/0;
  }
  
  @Override
  public void close(){
    throw new NullPointerException("Thrown by implicit close()");
  }
}
In the example above, IllegalArgumentException suppressed ArithmeticException thrown from divideByZero() which suppressed NullPointerException thrown from implicit close().

To get suppressed exceptions, we will use the getSuppressed() method from Throwable class.
import java.io.*;
public class SampleClass{

  public static void main(String[]args){
    
    try(DummyResource dr1 = new DummyResource("resource1");
        DummyResource dr2 = new DummyResource("resource2")){
       dr1.divideByZero();

    }
    catch(ArithmeticException e){
      System.out.println("Suppressed Exceptions");
      for(Throwable t : e.getSuppressed())
        System.out.println(t.toString());
        
      System.out.println("rethrow ArithmeticException");
      throw e;
      
    }
    
  }
}

class DummyResource implements AutoCloseable{
  private String name;
  
  DummyResource(String name){
    this.name = name;
  }
  
  void divideByZero(){
    int num = 10/0;
  }
  
  @Override
  public void close(){
    throw new NullPointerException("Thrown from " + name +
                                   " resource");
  }
}
The suppressed exceptions are specified in the exception message of the thrown exception. getSuppressed() is optional in this example. By the way, I'm using java 11. I don't know if this feature is working with other versions.
Exception Snapshot


If we want to suppress an exception, we can use the addSuppressed() method in Throwable class.
public class SampleClass{

  public static void main(String[]args){
    
    NullPointerException npe = new 
                               NullPointerException(
                               "str is null!");
    String str = null;
    try{
      
      str = str.concat(str);
    }
    catch(Exception e){
      
      if(str == null){
        npe.addSuppressed(e);
        throw npe;
      }
      else e.printStackTrace();
    }
  }
}

No comments:

Post a Comment