Life is like a Markov chain,
         your future only depends on what you are doing now,
                               and independent of your past.

Core Java Volume I—Fundamentals (4)

Chapter 7

Exceptions, Assertions, and Logging

7.1 Dealing with Errors

If an operation cannot be completed because of an error, the program ought to either

    • Return to a safe state and enable user to execute other commands; or
    • Allow the user to save all work and terminate the program.

The mission of exception handling is to transfer control from where the error occurred to an error handler that can deal with the situation.

What sorts of problems do you need to consider?

    • User input errors.
    • Device errors.
    • Physical limitations.
    • Code errors.

Java allows every method an alternative exit path if it is unable to complete its task in the normal way. In this situation, the method throws an object that encapsulates the error information.

The exception-handling mechanism begins its search for an exception handler.

Exceptions have their own syntax and are part of a special inheritance hierarchy.

7.1.1 The Classification of Exceptions

An exception object is always an instance of a class derived from Throwable.

The Error hierarchy describes internal errors and resource exhaustion situation inside Java runtime system. You should not throw an object of this type.

A RuntimeException happens because you made a programming error.

Exceptions that inherit from RuntimeException include such problems as

    • A bad cast
    • An out-of-bounds array access
    • A null pointer access

Exceptions that do not inherit from RuntimeException include

    • Tring to read past the end of a file
    • Trying to open a file that doesn’t exist
    • Tring to find a Class object for a string that does not denote an existing class

The rule: “if it is a RuntimeException, it was your fault.”

The NullPointerException would not have happened had you checked whether the variable was null before using it.

The notion of “existence”depends on the environment, not just on your code.

Unchecked exception—any exception that derives from the class Error or the class RuntimeException. All other exceptions are called checked exceptions.

The compiler checks that you provide exception handlers for all checked exceptions.

7.1.2 Declaring Checked Exceptions

The header of the method changes to reflect the checked exceptions the method you can throw.

Example:

public FileInputStream(String name) throws FileNotFoundException

An exception is thrown in any of the following 4 situations:

    • You can call a method that throws a checked exception
    • You can detect an error and throw a checked exception with the throw statement.
    • You make a programming error that gives rise to an unchecked exception.
    • An internal error occurs in the virtual machine or runtime library.

If either of two scenarios occurs, you must tell the programmers who will use your method about the possibility of an exception.

If a method might throw more than one checked exception type, you must list all exception classes in the header, separate them by commas.

You do not need to advertise internal Java errors and you should not advertise unchecked exceptions inheriting from RuntimeException.

Caution:

If you override a method from a superclass, the checked exceptions that the subclass method declares cannot be more general than those of the superclass method. In particular, if the superclass method throws no checked exception at all, neither can the subclass.

When a method in a class declares that it throws an exception that is an instance of a particular class, it may throw an exception of that class or of any of its subclasses.

7.1.3 How to Throw an Exception

Throwing an exception is easy if one of the existing exception classes works for you:

    1. Find an appropriate exception class.
    2. Make an object of that class.
    3. Throw it.

Once a method throws an exception, it does not return to its caller.

7.1.4 Creating Exception Classes

Derive your class from Exception, or from a subclass of Exception. It is customary to give both a default constructor and a constructor that contains a detailed message. (The toString method of the Throwable superclass returns a string containing that detailed message.)

7.2 Catching Exceptions

7.2.1 Catching an Exception

To catch an exception, set up a try/catch block.

Syntax:

try

{

          Code;

          …

}

catch(ExceptionType e)

{

          Handler for this type;

}

If any code inside the try block throws an exception of the class specified in the catch clause, then

    1. The program skips the remainder of the code in the try block.
    2. The program executes the handler code inside the catch clause.

Otherwise, the program skips the catch clause.

If any of the code in a method throws an exception other than the one named in the catch clause, this method exits immediately.

The compiler strictly enforces the throws specifiers. If you call a method that throws a checked exception, you must either handle it or pass it on.

General rule: Catch those exceptions that you know how to handle and propagate those that you don’t know how to handle.

When you propagate an exception, you must add a throws specifier to alert the caller that an exception may be thrown.

If you are writing a method that overrides a superclass method which throws no exception, then you must catch each checked exception in the method code.

7.2.2 Catching Multiple Exception

You can catch multiple exception types in a try block and handle each type differently by using separate catch clause for each type or in the same catch clause(as of Java SE7).

Example:

catch(FileNotFoundException | UnknownHostException)
{

…

}

And the latter one is only needed when catching exception types that are not subclasses of one another.

When you catch multiple exceptions, the exception variable is implicitlyfinal so you cannot assign a different value to e in the body of the catch clause.

7.2.3 Rethrowing and Chaining Exceptions

Use ServletException to indicate a failure of the subsystem.

Servlet: a small, server-resident program that typically runs automatically in response to user input.

Example:

throw new ServletException(“database error: ”+ e.getMessage());

It’s better idea to set the original exception as the “cause” of the new exception:

// for example

catch (SQLException e)

{

Throwable se = new ServletException(“… error”);

se.initCause(e);

throw se;

}

The original exception can be retrieved:

Throwable e = se.getCause();

This wrapping technique allows you to throw high-level exceptions in subsystems without losing the details of the original failure.

Just log an exception and rethrow it without any change:

logger.log(level, message, e);
throw e;

 

7.2.4 The finally Clause

The code in the finally clause executes whether or not an exception was caught.

You can use the finally clause without a catch clause.

Good idea: To use the finally clause in this way whenever you need to close a resource.

Decouple try/catch and try/finally blocks:

Syntax:

try
{

    try
    {

        Code that might throw exceptions;

    }
finally
{

…

}

}
catch
{
    Show error message;
}

The outer try block ensure that errors are reported. Also, errors in the finally clause are reported.

Caution:

A finally clause can yield unexpected results when it contains return statements. Suppose you exit the middle of a try block with a return statement. Before the method returns, the finally block is executed. If the finally block also contains a return statement, then it masks the original return value.

7.2.5 The Try-with-Resources Statement

Premise: the resource belong to a class that implements the AutoCloseable interface.

Form:

try (Resource res = …)     // You can specify multiple resource.
{
Work with res;
}

When the try block exits, then res.close() is called automatically.

Use the try-with-resources statement whenever you need to close a resource.

7.2.6 Analyze Stack Trace Elements

A stack trace is a listing of all pending method calls at a particular point in the execution of a program.

You can access the text description of a stack trace by calling the printStackTrace method of the Throwable class.

A more flexible approach is the getStackTrace method that yields an array of StackTraceElement object, which you can analyze in your program.

The StackTraceElement class has methods to obtain the file name and line number, as well as the class and method name, of the executing line of code. The toString method yields a formatted string containing all of this information.

The static Thread.getAllStackTraces method yieldsthe stack traces of all threads.

How to use that method?

Map<Thread, StackTraceElement[]> map = Thread.getAllStackTraces();
for (Thread t : map.keySet())
{
StackTraceElement[] frames = map.get(t);
Analyze frames
}

 

7.3 Tips for Using Exceptions

  1. Exception handling is not supposed to replace a simple test. Use exceptions for exceptional circumstances only.
  2. Do not micromanage exceptions. To separate normal processing from error handling.
  3. Make good use of exception hierarchy. Find an appropriate subclass or create your own. Respect the difference between checked and unchecked exceptions.
  4. Do not forcefully suppress exceptions.
  5. When you detect an error, “tough love” works better than indulgence.
  6. Propagating exceptions is not sign of shame. Higher level methods are often better equipped to inform the user of errors or to abandon unsuccessful commands.

5&6 -> “Throw early, catch late.”

7.4 Using Assertions

7.4.1 The Assertion Concepts

The assertion mechanism allows you to put in checks during testing and to have them automatically removed in the production code.

It has 2 forms:

assert condition;

    and

assert condition : expression;

Both statements evaluate the condition and throw an AssertionError if it is false. In the second one, the expression is passes to the constructor of the AssertionError object and turned into a message string. (the sole purpose of the expression)

7.4.2 Assertion Enabling and Disabling

By default, assertions are disabled. Enable them by running the program with the –enableassertions or –ea option:

java –enableassertions MyApp  // for example

You can turn on assertions in specific classes or in entire packages.

For example:

java –ea:MyClass –ea:com.mycompany.my.mylib… MyApp

This command turns on assertions for the class MyClass and all classes in the package and its subpackages.

The option –ea… turns on assertions in all classes of the default packages.

You can also disable assertions in certain classes and packages with the -disableassertions or –da oiption .

Use the –enablesystemassertions/-esa switch to enable assertions in system classes.

It’s also possible to programmatically control the assertion status of class loaders. (java.lang.ClassLoader)

7.4.3 Using Assertions for Parameter Checking

To deal with system failures:

    • Throwing an exception
    • Logging
    • Using assertions

When you should choose assertions?

    • Assertion failures are intended to be fatal, unrecoverable errors.
    • Assertion checks are turned on only during development and testing.

A common scenario–the checking of method parameters. Before using assertion, you have to look at the documentation of the method.

7.4.4 Using Assertion for Documenting Assumptions

Assertions are a tactical tool for testing and debugging. In contrast, logging is a strategic tool for the entire lifecycle of a program.

7.5 Logging

7.5.1 Basic Logging

Use the global logger and call its info method:

Logger.getGlobal().info(“…”);

7.5.2 Advanced Logging

    Call the getLogger method to create or retrieve a logger:

private static final Logger myLogger = Logger.getLogger(“…”);

    A logger that is not referenced by any variable can be garbage collected.

    To prevent this, save a reference to the logger with a static variable.

    Logger names are hierarchical.

    There are 7 logging levels:

    • SEVERE
    • WARNING
    • INFO
    • CONFIG
    • FINE
    • FINER
    • FINEST

By default, the top 3 levels are logged. You can set a different level.

For example:

logger.setLevel(Level.FINE); // FINE and all levels are actually logged.

You can also use Level.All to turn on logging for all levels or Level.Off to turn all logging off.

There are logging methods for all levels, such as

logger.warning(message);

logger.fine(message);

…

You can use the log method and supply the level, such as

logger.log(Level.FINE, message);

Caution:

If logging level < INFO, you need to change the log handler configuraton.

You can use logp method to give the precise location of the calling class and method.

A common use for logging is to log unexpected exceptions.

Two convenience methods include a description of the exception in the log record.

void throwing(String className, String methodName, Throwable t)

void log(Level l, String message, Throwable t)

    Typical uses are

if (…)

{

IOException exception = new IOException(“…”);

logger.throwing(“com.mycompany.myLib.Reader”, ”read”, exception);

throw exception;

}

    And

try
{
…
}
catch (IOException)
{
Logger.getLogger(“com.mycompany.myapp”).log(Level.WARNING, “Reading image”, e);
}

The throwing call logs a record with level FINER and a message that starts with THROW.

7.5.5 Handlers

By default, loggers send records to a ConsoleHandler that prints them to the System.err stream. For a record to be logged, its logging level must be above the threshold of both the logger and the handler.

The default console handler is INFO.

Two useful handler—FilehHandler and SocketHandler.

By default, the records are formatted in XML.

7.5.6 Filters

To define a filter, implement the Filter interface and define the method

boolean isLoggable(LogRecord record)

Analyze the log record, using any criteria that you desire, and return true for those records that should be included in the log.

To install a filter into a logger or handler, call the setFilter method.

7.5.7 Formatters

 The ConsoleHandler and FileHandler classes emit the log records in text and XML formats. You can define your own formats. You need to extend the Formatter class and override the method

String format(LogRecord record)

7.5.8 A Logging Recipe

  1. To give the logger the same name as your main application package.
  2. Install a more reasonable default in your application.
  3. Log unexpected exceptions.
  4. The level FINE is a good choice for logging message that are intended for programmers.
  5. You can print or log the value of any variable with code like this:

7.6 Debugging Tips

  1. You can print or log the value of any variable with code like this:

    System.out.println(“x=” + x);

    or

    Logger.getGlobal().info(“x=” + x);

    To get the state of the implicit parameter object, print the state of this object.

    Logger.getGlobal().info(“this=” + this) ;

     

  2. Put a separate main method in each class. Inside it, you can put a unit test stub that lets you test the class in isolation.

  3. Check out Junit from http://junit.org .

  4. A logging proxy is an object of a subclass that intercepts method calls, logs them, and then calls the superclass.

  5. Get a stack tree from any exception object with the printStackTrace method in the Throwable class.

  6. Capture the stack trace into a string:

    StringWriter out = new StringWriter();
    new Throwable().printStackTrace(new PrintWriter(out));
    String description = out.toString();

    Capture errors in a file:

    java MyProgram 2>error.txt//in bash and windows shell

     

  7. Log the stack traces of uncaught exceptions into a file. You can change the handler for uncaught exceptions with the static Thread.setDefaultUncaughtExceptionsHandler method:

    Thread.setDefaultUncaughtExceptionsHandler(
    new Thread.setDefaultUncaughtExceptionsHandler()
    {
    public void uncaughtException(Thread t, Throwable e)
    {
    save information in log file
    };
    });

     

  8. To watch class loading launch the java virtual machine with the -verbose flag.

  9. The –Xlint option tells the compiler to spot common code problems.

  10.  Linux/Unix: ps utility  Windows: jconsole

  11.  Use jmap to get a heap dump that shows you every object on the heap.

  12. If you launch the Java virtual machine with the –Xprof flag, it runs a rudimentary profiler that keeps track of the methods in your code that were executed most often.

 

posted @ 2018-02-01 05:06  Hu_Yan  阅读(317)  评论(1编辑  收藏  举报