17.2 C
London
Thursday, September 19, 2024

Exceptions in Java: Superior options and kinds



My instance revealed just one trigger. Exceptions thrown from non-trivial real-world functions could include in depth chains of many causes. You possibly can entry these causes by using a loop comparable to the next:

catch (Exception exc)
{
   Throwable t = exc.getCause();
   whereas (t != null)
   {
      System.out.println(t);
      t = t.getCause();
   }
}

Strive-with-resources

Java functions typically entry information, database connections, sockets, and different sources that depend upon associated system sources (e.g., file handles). The shortage of system sources implies that they need to finally be launched, even when an exception happens. When system sources aren’t launched, the applying finally fails when trying to accumulate different sources, as a result of no extra associated system sources can be found.

In my introduction to exception dealing with fundamentals, I discussed that sources (truly, the system sources on which they rely) are launched in a lastly block. This could result in tedious boilerplate code, such because the file-closing code that seems under:

lastly
{
   if (fis != null)
      strive
      {
         fis.shut();
      }
      catch (IOException ioe)
      {
         // ignore exception
      }
   if (fos != null)
      strive
      {
         fos.shut();
      }
      catch (IOException ioe)
      {
         // ignore exception
      }
}

Not solely does this boilerplate code add bulk to a classfile, the tedium of writing it would result in a bug, even perhaps failing to shut a file. JDK 7 launched try-with-resources to beat this downside.

The fundamentals of try-with-resources

The try-with-resources assemble routinely closes open sources when execution leaves the scope during which they have been opened and used, whether or not or not an exception is thrown from that scope. This assemble has the next syntax:

strive (useful resource acquisitions)
{
   // useful resource utilization
}

The strive key phrase is parameterized by a semicolon-separated checklist of resource-acquisition statements, the place every assertion acquires a useful resource. Every acquired useful resource is on the market to the physique of the strive block, and is routinely closed when execution leaves this physique. Not like an everyday strive assertion, try-with-resources doesn’t require catch blocks and/or a lastly block to observe strive(), though they are often specified.

Contemplate the next file-oriented instance:

strive (FileInputStream fis = new FileInputStream("abc.txt"))
{
   // Do one thing with fis and the underlying file useful resource.
}

On this instance, an enter stream to an underlying file useful resource (abc.txt) is acquired. The strive block does one thing with this useful resource, and the stream (and file) is closed upon exit from the strive block.

Utilizing ‘var’ with ‘try-with-resources’

JDK 10 launched help for var, an identifier with particular which means (i.e., not a key phrase). You should use var with try-with-resources to cut back boilerplate. For instance, you could possibly simplify the earlier instance to the next:

strive (var fis = new FileInputStream("abc.txt"))
{
   // Do one thing with fis and the underlying file useful resource.
}

Copying a file in a try-with-resources context

Within the earlier article, I excerpted the copy() methodology from a file-copy software. This methodology’s lastly block accommodates the file-closing boilerplate offered earlier. Itemizing 8 improves this methodology through the use of try-with-resources to deal with the cleanup.

Itemizing 8. Copy.java

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class Copy
{
   public static void predominant(String[] args)
   {
      if (args.size != 2)
      {
         System.err.println("utilization: java Copy srcfile dstfile");
         return;
      }

      strive
      {
         copy(args[0], args[1]);
      }
      catch (IOException ioe)
      {
         System.err.println("I/O error: " + ioe.getMessage());
      }
   }

   static void copy(String srcFile, String dstFile) throws IOException
   {
      strive (FileInputStream fis = new FileInputStream(srcFile);
           FileOutputStream fos = new FileOutputStream(dstFile))
      {
         int c;
         whereas ((c = fis.learn()) != -1)
            fos.write(c);
      }
   }
}

copy() makes use of try-with-resources to handle supply and vacation spot file sources. The spherical bracketed-code following strive makes an attempt to create file enter and output streams to those information. Assuming success, its physique executes, copying the supply file to the vacation spot file.

Whether or not an exception is thrown or not, try-with-resources ensures that each information are closed when execution leaves the strive block. As a result of the boilerplate file-closing code that was proven earlier isn’t wanted, Itemizing 8’s copy() methodology is far less complicated and simpler to learn.

Designing useful resource courses to help try-with-resources

The try-with-resources assemble requires {that a} useful resource class implement the java.lang.Closeable interface or the JDK 7-introduced java.lang.AutoCloseable superinterface. Pre-Java 7 courses like java.io.FileInputStream applied Closeable, which provides a void shut() methodology that throws IOException or a subclass.

Beginning with Java 7, courses can implement AutoCloseable, whose single void shut() methodology can throw java.lang.Exception or a subclass. The throws clause has been expanded to accommodate conditions the place you would possibly want so as to add shut() strategies that may throw exceptions exterior of the IOException hierarchy; for instance, java.sql.SQLException.

Itemizing 9 presents a CustomARM software that exhibits you the way to configure a customized useful resource class as a way to use it in a try-with-resources context.

Itemizing 9. CustomARM.java

public class CustomARM
{
   public static void predominant(String[] args)
   {
      strive (USBPort usbp = new USBPort())
      {
         System.out.println(usbp.getID());
      }
      catch (USBException usbe)
      {
         System.err.println(usbe.getMessage());
      }
   }
}

class USBPort implements AutoCloseable
{
   USBPort() throws USBException
   {
      if (Math.random() < 0.5)
         throw new USBException("unable to open port");
      System.out.println("port open");
   }

   @Override
   public void shut()
   {
      System.out.println("port shut");
   }

   String getID()
   {
      return "some ID";
   }
}

class USBException extends Exception
{
   USBException(String msg)
   {
      tremendous(msg);
   }
}

Itemizing 9 simulates a USB port in which you’ll be able to open and shut the port and return the port’s ID. I’ve employed Math.random() within the constructor as a way to observe try-with-resources when an exception is thrown or not thrown.

Compile this itemizing and run the applying. If the port is open, you’ll see the next output:

port open
some ID
port shut

If the port is closed, you would possibly see this:

unable to open port

Suppressing exceptions in try-with-resources

When you’ve had some programming expertise, you may need seen a possible downside with try-with-resources: Suppose the strive block throws an exception. This assemble responds by invoking a useful resource object’s shut() methodology to shut the useful resource. Nevertheless, the shut() methodology may also throw an exception.

When shut() throws an exception (e.g., FileInputStream‘s void shut() methodology can throw IOException), this exception masks or hides the unique exception. Evidently the unique exception is misplaced.

Actually, this isn’t the case: try-with-resources suppresses shut()‘s exception. It additionally provides the exception to the unique exception’s array of suppressed exceptions by invoking java.lang.Throwable‘s void addSuppressed(Throwable exception) methodology.

Itemizing 10 presents a SupExDemo software that demonstrates the way to repress an exception in a try-with-resources context.

Itemizing 10. SupExDemo.java

import java.io.Closeable;
import java.io.IOException;

public class SupExDemo implements Closeable
{
   @Override
   public void shut() throws IOException
   {
      System.out.println("shut() invoked");
      throw new IOException("I/O error in shut()");
   }

   public void doWork() throws IOException
   {
      System.out.println("doWork() invoked");
      throw new IOException("I/O error in work()");
   }

   public static void predominant(String[] args) throws IOException
   {
      strive (SupExDemo supexDemo = new SupExDemo())
      {
         supexDemo.doWork();
      }
      catch (IOException ioe)
      {
         ioe.printStackTrace();
         System.out.println();
         System.out.println(ioe.getSuppressed()[0]);
      }
   }
}

Itemizing 10’s doWork() methodology throws an IOException to simulate some type of I/O error. The shut() methodology additionally throws the IOException, which is suppressed in order that it doesn’t masks doWork()‘s exception.

The catch block accesses the suppressed exception (thrown from shut()) by invoking Throwable‘s Throwable[] getSuppressed() methodology, which returns an array of suppressed exceptions. Solely the primary component is accessed as a result of just one exception is suppressed.

Compile Itemizing 10 and run the applying. It’s best to observe the next output:

doWork() invoked
shut() invoked
java.io.IOException: I/O error in work()
        at SupExDemo.doWork(SupExDemo.java:16)
        at SupExDemo.predominant(SupExDemo.java:23)
        Suppressed: java.io.IOException: I/O error in shut()
                at SupExDemo.shut(SupExDemo.java:10)
                at SupExDemo.predominant(SupExDemo.java:24)

java.io.IOException: I/O error in shut()

A number of catch blocks (multi-catch)

Beginning in JDK 7, it’s doable to codify a single catch block that catches a couple of sort of exception. The aim of this multi-catch characteristic is to cut back code duplication and scale back the temptation to catch overly broad exceptions (as an illustration, catch (Exception e)).

Suppose you’ve developed an software that provides you the flexibleness to repeat information to a database or file. Itemizing 11 presents a CopyToDatabaseOrFile class that simulates this case, and demonstrates the issue with catch block code duplication.

Itemizing 11. CopyToDatabaseOrFile.java

import java.io.IOException;

import java.sql.SQLException;

public class CopyToDatabaseOrFile
{
   public static void predominant(String[] args)
   {
      strive
      {
         copy();
      }
      catch (IOException ioe)
      {
         System.out.println(ioe.getMessage());
         // extra handler code
      }
      catch (SQLException sqle)
      {
         System.out.println(sqle.getMessage());
         // extra handler code that is an identical to the earlier handler's
         // code
      }
   }

   static void copy() throws IOException, SQLException
   {
      if (Math.random() < 0.5)
         throw new IOException("can't copy to file");
      else
         throw new SQLException("can't copy to database");
   }
}

JDK 7 overcomes this code duplication downside by letting you specify a number of exception sorts in a catch block the place every successive sort is separated from its predecessor by inserting a vertical bar (|) between these sorts:

strive
{
   copy();
}
catch (IOException | SQLException iosqle)
{
   System.out.println(iosqle.getMessage());
}

Now, when copy() throws both exception, the exception will likely be caught and dealt with by the catch block.

When a number of exception sorts are listed in a catch block’s header, the parameter is implicitly considered closing. Because of this, you can not change the parameter’s worth. For instance, you can not change the reference saved within the earlier code fragment’s iosqle parameter.

Closing re-throw

Beginning in JDK 7, the Java compiler is ready to analyze re-thrown exceptions extra exactly than in earlier Java variations. This characteristic solely works when no assignments are made to a re-thrown exception’s catch block parameter, which is taken into account to be successfully closing. When a previous strive block throws an exception that’s a supertype/subtype of the parameter’s sort, the compiler throws the caught exception’s precise sort as an alternative of throwing the parameter’s sort (as was accomplished in earlier Java variations).

The aim of this closing re-throw characteristic is to facilitate including a strive–catch assertion round a block of code to intercept, course of, and re-throw an exception with out affecting the statically decided set of exceptions thrown from the code. Additionally, this characteristic permits you to present a standard exception handler to partially deal with the exception near the place it’s thrown, and supply extra exact handlers elsewhere that deal with the re-thrown exception. Contemplate Itemizing 12.

Itemizing 12. MonitorEngine.java

class PressureException extends Exception
{
   PressureException(String msg)
   {
      tremendous(msg);
   }
}

class TemperatureException extends Exception
{
   TemperatureException(String msg)
   {
      tremendous(msg);
   }
}

public class MonitorEngine
{
   public static void predominant(String[] args)
   {
      strive
      {
         monitor();
      }
      catch (Exception e)
      {
         if (e instanceof PressureException)
            System.out.println("correcting strain downside");
         else
            System.out.println("correcting temperature downside");
      }
   }

   static void monitor() throws Exception
   {
      strive
      {
         if (Math.random() < 0.1)
            throw new PressureException("strain too excessive");
         else
         if (Math.random() > 0.9)
            throw new TemperatureException("temperature too excessive");
         else
            System.out.println("all is nicely");
      }
      catch (Exception e)
      {
         System.out.println(e.getMessage());
         throw e;
      }
   }
}

Itemizing 12 simulates the testing of an experimental rocket engine to see if the engine’s strain or temperature exceeds a security threshold. It performs this testing through the monitor() helper methodology.

monitor()‘s strive block throws PressureException when it detects a strain excessive, and throws TemperatureException when it detects a temperature excessive. (As a result of that is solely a simulation, random numbers are used — the java.lang.Math class’s static double random() methodology returns a random quantity between 0.0 and (nearly) 1.0.) The strive block is adopted by a catch block designed to partially deal with the exception by outputting a warning message. This exception is then re-thrown in order that monitor()‘s calling methodology can end dealing with the exception.

Earlier than JDK 7 you couldn’t specify PressureException and TemperatureException in monitor()‘s throws clause as a result of the catch block’s e parameter is of sort java.lang.Exception and re-throwing an exception was handled as throwing the parameter’s sort. JDK 7 and successor JDKs have made it doable to specify these exception sorts within the throws clause as a result of their compilers can decide that the exception thrown by throw e comes from the strive block, and solely PressureException and TemperatureException might be thrown from this block.

As a result of now you can specify static void monitor() throws PressureException, TemperatureException, you may present extra exact handlers the place monitor() known as, as the next code fragment demonstrates:

strive
{
   monitor();
}
catch (PressureException pe)
{
   System.out.println("correcting strain downside");
}
catch (TemperatureException te)
{
   System.out.println("correcting temperature downside");
}

Due to the improved sort checking provided by closing re-throw in JDK 7, supply code that compiled below earlier variations of Java would possibly fail to compile below later JDKs. For instance, think about Itemizing 13.

Itemizing 13. BreakageDemo.java

class SuperException extends Exception
{
}

class SubException1 extends SuperException
{
}

class SubException2 extends SuperException
{
}

public class BreakageDemo
{
   public static void predominant(String[] args) throws SuperException
   {
      strive
      {
         throw new SubException1();
      }
      catch (SuperException se)
      {
         strive
         {
            throw se;
         }
         catch (SubException2 se2)
         {
         }
      }
   }
}

Itemizing 13 compiles below JDK 6 and earlier. Nevertheless, it fails to compile below later JDKs, whose compilers detect and report the truth that SubException2 isn’t thrown within the physique of the corresponding strive assertion. This can be a small downside that you’re unlikely to come across in your packages, and a worthwhile trade-off for having the compiler detect a supply of redundant code. Eradicating redundancies leads to cleaner code and smaller classfiles.

StackWalker and the StackWalking API

Acquiring a stack hint through Thread‘s or Throwable‘s getStackTrace() methodology is pricey and impacts efficiency. The JVM eagerly captures a snapshot of your entire stack (apart from hidden stack frames), even once you solely want the primary few frames. Additionally, your code will most likely must course of frames which are of no curiosity, which can be time-consuming. Lastly, you can not entry the precise java.lang.Class occasion of the category that declared the tactic represented by a stack body. To entry this Class object, you’re compelled to increase java.lang.SecurityManager to entry the protected getClassContext() methodology, which returns the present execution stack as an array of Class objects.

JDK 9 launched the java.lang.StackWalker class (with its nested Possibility class and StackFrame interface) as a extra performant and succesful various to StackTraceElement (plus SecurityManager). To study StackWalker and its associated sorts, see my introduction to the StackWalking API.

In conclusion

This text completes my two-part introduction to Java’s exception dealing with framework. You would possibly need to reinforce your understanding of this framework by reviewing Oracle’s Exceptions lesson within the Java Tutorials. One other good useful resource is Baeldung’s Exception dealing with in Java tutorial, which incorporates anti-patterns in exception dealing with.

Latest news
Related news

LEAVE A REPLY

Please enter your comment!
Please enter your name here