`
leonzhx
  • 浏览: 793319 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

Chapter 11. Exceptions, Assertions, Logging, and Debugging

阅读更多

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

  
2.  The Error hierarchy describes internal errors and resource exhaustion situations inside the Java runtime system. You should not throw an object of this type. There is little you can do if such an internal error occurs, beyond notifying the user and trying to terminate the program gracefully.

Commented by Sean: Error is not checked but Throwable is.

 

3.  The Exception hierarchy also splits into two branches: exceptions that derive from RuntimeException and those that do not. The general rule is this: A RuntimeException happens because you made a programming error. Any other exception occurs because a bad thing, such as an I/O error.

 

4.  The rule “If it is a RuntimeException, it was your fault” works pretty well.

 

5.  Any exception that derives from the class Error or the class RuntimeException is called an unchecked exception. All other exceptions are called checked exceptions. The compiler checks that you provide exception handlers for all checked exceptions.

 

6.  An exception is thrown in any of the following four situations:
    a)  You call a method that throws a checked exception.
    b)  You detect an error and throw a checked exception with the throw statement
    c)  You make a programming error that gives rise to an unchecked exception.
    d)  An internal error occurs in the virtual machine or runtime library.

 

7.  Any method that throws an exception is a potential death trap. If no handler catches the exception, the current thread of execution terminates.

 

8.  A method must declare all the checked exceptions that it might throw. Unchecked exceptions are either beyond your control (Error) or result from conditions that you should not have allowed in the first place (RuntimeException). If your method fails to faithfully declare all checked exceptions, the compiler will issue an error message.

 

9.  The checked exceptions that the subclass method declares cannot be more general than those of the overrided superclass method.

 

10.  If an exception occurs that is not caught anywhere, the program will terminate and print a message to the console, giving the type of the exception and a stack trace. GUI programs (both applets and applications) catch exceptions, print stack trace messages, and then go back to the user interface processing loop.

 

11.  If you call a method that throws a checked exception, you must either handle it or pass it on. You should catch those exceptions that you know how to handle and propagate those that you do not know how to handle.

 

12.  You are not allowed to add more throws specifiers to a subclass method than are present in the overrided superclass method.

 

13.    As of Java SE7, you can catch multiple exception types in the same catch clause (which is only needed when catching exception types that are not subclasses of one another.):

 

try
{
   code that might throw exceptions
}
catch (FileNotFoundException | UnknownHostException e)
{
   emergency action for missing files and unknown hosts
}
catch (IOException e)
{
   emergency action for all other I/O problems
}

When you catch multiple exceptions, the exception variable is implicitly final.

Commented by Sean: because the type of exception variable is non-decisive and the compiler cannot validate the assignment.

 

14.  You can throw an exception in a catch clause. Typically, you do this when you want to change the exception type. (i.e. ServletException)

 

15.  It is a better idea to set the original exception as the “cause” of the new exception:

 

try
{
   access the database
}
catch (SQLException e)
{
   Throwable se = new ServletException("database error");
   se.initCause(e);
   throw se;
}

When the exception is caught, the original exception can be retrieved:

Throwable e = se.getCause();

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

 

16.  Sometimes, you just want to log an exception and rethrow it without any change:

 

try
{
   access the database
}
catch (Exception e)
{
   logger.log(level, message, e);
   throw e;
}

Before Java SE 7, there was a problem with this approach. Suppose the code is inside a method

public void updateRecord() throws SQLException
The Java compiler looked at the throw statement inside the catch block, then at the type of e, and complained that this method might throw any Exception, not just a SQLException. This has now been improved. The compiler now tracks the fact that e originates from the try block. Provided that the only checked exceptions in that block are SQLException instances, and provided that e is not changed in the catch block, it is valid to declare the enclosing method as throws SQLException.

 

17.  The code in the finally clause executes whether or not an exception was caught. You can use the finally clause without a catch clause.

 

18.  It is strongly suggested that you decouple try/catch and try/finally blocks. This makes your code far less confusing:

 

InputStream in = ...;
try
{
   try
   {
      code that might throw exceptions
   }
   finally
   {
      in.close();
   }
}
catch (IOException e)
{
   show error message
}

 
The inner try block has a single responsibility: to make sure that the input stream is closed. The outer try block has a single responsibility: to ensure that errors are reported. Not only is this solution clearer, it is also more functional: Errors in the finally clause are reported.

 

 

19.  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.

 

20.  It’s similar when try block throws an uncaught exception and finally block throws another one. This is a problem because the first exception is likely to be more interesting. If you want to do the right thing and rethrow the original exception, the code becomes incredibly tedious:

 

InputStream in = ...;
Exception ex = null;
try
{
   try
   {
      code that might throw exceptions
   }
   catch (Exception e)
   {
      ex = e;
      throw e;
   }
}
finally
{
   try
   {
      in.close();
   }
   catch (Exception e)
   {
      if (ex == null) throw e;
   }
}

 

21.    Java SE 7 provides a useful try-with-resources statement:

 

try (Resource res = ...)
{
   work with res
}

provided the resource belongs to a class that implements the AutoCloseable interface. That interface has a single method

void close() throws Exception;
When the try block exits, then res.close() is called automatically. You can specify multiple resources of same type separated by commas and those of different type by semi-colon.

 

22.  When the try block throws an exception and the close method also throws an exception, the try-with-resources statement handles this situation quite elegantly. The original exception is rethrown, and any exceptions thrown by close methods are considered “suppressed”. They are automatically caught and added to the original exception with the addSuppressed method. If you are interested in them, call the getSuppressed method which yields an array of the suppressed expressions from close methods.

Commented by Sean:  compiler will ask you to explicitly handle the exception declared by the close method (or declare to throw it also) without checking whether it's suppressed by any exception thrown from try block.

 

23.  There is also a Closeable interface. It is a subinterface of AutoCloseable, also with a single close method. However, that method is declared to throw an IOException.

 

24.  A try-with-resources statement can itself have catch clauses and a finally clause. These are executed after closing the resources.

 

25.  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: 

Throwable t = new Throwable();
ByteArrayOutputStream out = new ByteArrayOutputStream();
t.printStackTrace(out);
String description = out.toString();

  

26.  The getStackTrace method that yields an array of StackTraceElement objects, 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.

 

27.  getMethodName method of StackTraceElement gets the name of the method containing the execution point of this element. The name of a constructor is <init>. The name of a static initializer is <clinit>. You can’t distinguish between overloaded methods with the same name.

 

28.  The static Thread.getAllStackTraces method yields the stack traces of all threads:

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

Commented by Sean: you can get the current stack trace by Thread.currentThread().getStackTrace()

 

29.  Tips for using exceptions:
    a)  Exception handling is not supposed to replace a simple test. It took far longer to catch an exception than to perform a simple test. The moral is: Use exceptions for exceptional circumstances only.
    b)  Do not micromanage exceptions. Don’t wrap every statement in a separate try block and separate normal processing from error handling.
    c)  Make good use of the exception hierarchy. Don’t just throw a RuntimeException. Find an appropriate subclass or create your own. Don’t just catch Throwable. It makes your code hard to read and maintain. Don’t throw checked exception for logic errors.
    d)  Do not squelch exceptions. If you believe that exceptions are at all important, you should make some effort to handle them right.
    e)  When you detect an error, “tough love” works better than indulgence. Don’t hesitate to throw exceptions if necessary.
    f)  Propagating exceptions is not a sign of shame. Higher-level methods are often better equipped to inform the user of errors or to abandon unsuccessful commands.
Rules e) and f) can be summarized as “throw early, catch late.”

 

30.  The assertion mechanism allows you to put in checks during testing and to have them automatically removed in the production code. Usually it’s used to check the contract that method parameters should conform to.

 

31.  The Java language has a keyword assert. There are two forms:
    assert condition;
and
    assert condition : expression;
Both statements evaluate the condition and throw an AssertionError if it is false. In the second statement, the expression is passed to the constructor of the AssertionError object and turned into a message string.

 

32.  By default, assertions are disabled. Enable them by running the program with the -enableassertions or -ea option:
     java -enableassertions MyApp

 

33.  Enabling or disabling assertions is a function of the class loader. When assertions are disabled, the class loader strips out the assertion code so that it won’t slow execution.

 

34.  You can even turn on assertions in specific classes or in entire packages:
java -ea:MyClass -ea:com.mycompany.mylib... MyApp
This command turns on assertions for the class MyClass and all classes in the com.mycompany.mylib package and its subpackages. The option -ea... turns on assertions in all classes of the default package.

 

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

 

36.  The -ea and -da switches that enable or disable all assertions do not apply to the “system classes” without class loaders. Use the -enablesystemassertions/-esa switch to enable assertions in system classes.

 

37.  When should you choose assertions:
    a)  Assertion failures are intended to be fatal, unrecoverable errors.
    b)  Assertion checks are turned on only during development and testing.
    c)  Using assertions for documenting assumptions
Therefore, you would not use assertions for signaling recoverable conditions to another part of the program or for communicating problems to the program user. Assertions should only be used to locate internal program errors during testing.

 

38.  It is also possible to programmatically control the assertion status of class loaders through the API of ClassLoader.

 

39.  The principal advantages of the logging API:
    a)  It is easy to suppress all log records or just those below a certain level, and just as easy to turn them back on.
    b)  Suppressed logs are very cheap, so that there is only a minimal penalty for leaving the logging code in your application.
    c)  Log records can be directed to different handlers—for displaying in the console, writing to a file, and so on.
    d)  Both loggers and handlers can filter records. Filters can discard boring log entries, using any criteria supplied by the filter implementor.
    e)  Log records can be formatted in different ways—for example, in plain text or XML.
    f)  Applications can use multiple loggers, with hierarchical names such as com.mycompany.myapp, similar to package names.
    g)  By default, the logging configuration is controlled by a configuration file. Applications can replace this mechanism if desired.

 

40.  For simple logging, use the global logger and call its info method:

Logger.getGlobal().info("File->Open menu item selected");
If you call
Logger.getGlobal().setLevel(Level.OFF);
 at an appropriate place, then all logging is suppressed.

 

 

41.  In a professional application, you wouldn’t want to log all records to a single global logger. Instead, you can define your own loggers. Call the Logger.getLogger method to create or retrieve a logger. Logger names are hierarchical and logger parents and children share certain properties. For example, if you set the log level on the logger "com.mycompany", then the child loggers inherit that level.

 

42.  There are seven logging levels:
    a)  SEVERE
    b)  WARNING
    c)  INFO
    d)  CONFIG
    e)  FINE
    f)  FINER
    g)  FINEST
By default, the top three levels are actually logged.

 

43.  You can set a different level by Logger.setLevel. Use Level.ALL to turn on logging for all levels or Level.OFF to turn all logging off.

 

44.    There are logging methods for different levels, such as:

 

logger.warning(message);
logger.fine(message);
 and so on. Alternatively, you can use the log method and supply the level, such as

 

 

logger.log(Level.FINE, message);
 

 

45.  If you set the logging level to a value finer than INFO, you also need to change the log handler configuration. The default log handler suppresses messages below INFO.

 

46.  The default log record shows the name of the class and method that contain the logging call, as inferred from the call stack. However, if the virtual machine optimizes execution, accurate call information may not be available. You can use the logp method to give the precise location of the calling class and method. The method signature is

void logp(Level l, String className, String methodName, String message)
 There are convenience methods for tracing execution flow:
void entering(String className, String methodName)
void entering(String className, String methodName, Object param)
void entering(String className, String methodName, Object[] params)
void exiting(String className, String methodName)
void exiting(String className, String methodName, Object result)
 These calls generate log records of level FINER that start with the strings ENTRY and RETURN.

 

 

47.  A common use for logging is to log unexpected exceptions:

 

void throwing(String className, String methodName, Throwable t)
void log(Level l, String message, Throwable t)
 The throwing call logs a record with level FINER and a message that starts with THROW.

 

 

48.  You can change various properties of the logging system by editing a configuration file:
jre/lib/logging.properties
To use another file, set the java.util.logging.config.file property to the file location by starting your application with
java -Djava.util.logging.config.file=configFile MainClass

 

49.  The log manager is initialized during VM startup, before main executes. If you call System.setProperty("java.util.logging.config.file", file) in main, also call LogManager.readConfiguration() to reinitialize the log manager.

 

50.  To change the default logging level, edit the configuration file and modify the line
.level=INFO
You can specify the logging levels for your own loggers by appending the .level suffix to the logger name:
com.mycompany.myapp.level=FINE

 

51.  The loggers don’t actually send the messages to the console—that is the job of the handlers. Handlers also have levels. To see FINE messages on the console, you also need to set
java.util.logging.ConsoleHandler.level=FINE

 

52.  The logging properties file is processed by the java.util.logging.LogManager class. It is possible to specify a different log manager by setting the java.util.logging.manager system property to the name of a subclass. Alternatively, you can keep the standard log manager and still bypass the initialization from the logging properties file. Set the java.util.logging.config.class system property to the name of a class that sets log manager properties in some other way.

Commented By Sean: for the default log manager, setting the java.util.logging.config.class system property will bypass the configuration file, and the log manager will just create an instance of the class specified in the system property.

 

53.  It is also possible to change logging levels in a running program by using the jconsole program. See www.oracle.com/technetwork/articles/java/jconsole-1564139.html#LoggingControl for information.

 

54.  A resource bundle consists of a set of mappings for various locales. A program may contain multiple resource bundles and each resource bundle has a name.

 

55.  To add mappings to a resource bundle, you supply a file for each locale. English message mappings are in a file com/mycompany/logmessages_en.properties, and German message mappings are in a file com/mycompany/logmessages_de.properties. (The en and de are the language codes.) You place the files together with the class files of your application, so that the ResourceBundle class will automatically locate them. These files are plain text files, consisting of entries such as

readingFile=Achtung! Datei wird eingelesen
 . . .

When requesting a logger, you can specify a resource bundle:

Logger logger = Logger.getLogger(loggerName, "com.mycompany.logmessages");

Then you specify the resource bundle key, not the actual message string, for the log message.

logger.info("readingFile");

56.  A message may contain placeholders: {0}, {1} and so on to include arguments into localized messages.

 

57.  By default, loggers send records to a ConsoleHandler that prints them to the System.err stream. Specifically, the logger sends the record to the parent handler, and the ultimate ancestor (with name "") has a ConsoleHandler.

 

58.  Like loggers, handlers have a logging level. For a record to be logged, its logging level must be above the threshold of both the logger and the handler.

 

59.  You can bypass the configuration file altogether and install your own handler.

Logger logger = Logger.getLogger("com.mycompany.myapp");
logger.setLevel(Level.FINE);
logger.setUseParentHandlers(false);
Handler handler = new ConsoleHandler();
handler.setLevel(Level.FINE);
logger.addHandler(handler);

By default, a logger sends records both to its own handlers and the handlers of the parent.

 

60.  The logging API provides two useful handlers for this purpose: a FileHandler and a SocketHandler. The SocketHandler sends records to a specified host and port. Of greater interest is the FileHandler that collects records in a file.

 

61.  The FileHandler sends the records to a file javan.log in the user’s home directory , where n is a number to make the file unique. By default, the records are formatted in XML.

 

62.  You can modify the default behavior of the file handler by setting various parameters in the log manager configuration:

  
63.  To change the log file name, you should use another pattern, such as %h/myapp.log:
 

 
64.  You can also define your own handlers by extending the Handler or the StreamHandler class and define the publish, flush, and close methods. The StreamHandler handler buffers the records and only writes them to the stream when the buffer is full.

 

65.  By default, records are filtered according to their logging levels. Each logger and handler can have an optional filter to perform additional filtering. To define a filter, implement the Filter interface and define the method

boolean isLoggable(LogRecord record)

 To install a filter into a logger or handler, simply call the setFilter method. You can have at most one filter at a time.

 

66.  You can define your own formats as well. You need to extend the Formatter class and override the method:

String format(LogRecord record)

 In your format method, you may want to call the method

String formatMessage(LogRecord record)

That method formats the message part of the record, substituting parameters and applying localization. Many file formats (such as XML) require a head and tail parts that surround the formatted records. To achieve this, override the methods

String getHead(Handler h)
String getTail(Handler h)

 Finally, call the setFormatter method to install the formatter into the handler.

 

67.  Logging Recipe:
    a)  For a simple application, choose a single logger. It is a good idea to give the logger the same name as your main application package:

private static final Logger logger = Logger.getLogger("com.mycompany.myprog");

    b)  The default logging configuration logs all messages of level INFO or higher to the console. Users can override the default configuration. The following code ensures that all messages are logged to an application-specific file. Place the code into the main method of your application:

if (System.getProperty("java.util.logging.config.class") == null
      && System.getProperty("java.util.logging.config.file") == null)
{
   try
   {
      Logger.getLogger("").setLevel(Level.ALL);
      final int LOG_ROTATION_COUNT = 10;
      Handler handler = new FileHandler("%h/myapp.log", 0, LOG_ROTATION_COUNT);
      Logger.getLogger("").addHandler(handler);
   }
   catch (IOException e)
   {
      logger.log(Level.SEVERE, "Can't create log file handler", e);
   }
}

    c)    Keep in mind that all messages with level INFO, WARNING, and SEVERE show up on the console. Therefore, reserve these levels for messages that are meaningful to the users of your program. The level FINE is a good choice for logging messages that are intended for programmers.

 

68.  Most of the classes in the Java library are very conscientious about overriding the toString method to give you useful information about the class. This is a real boon for debugging. You should make the same effort in your classes.

 

69.  A logging proxy is an object of a subclass that intercepts method calls, logs them, and then calls the superclass. For example, if you have trouble with the nextDouble method of the Random class, you can create a proxy object as an instance of an anonymous subclass:

Random generator = new
   Random()
   {
      public double nextDouble()
      {
         double result = super.nextDouble();
         Logger.getGlobal().info("nextDouble: " + result);
         return result;
      }
   };

Whenever the nextDouble method is called, a log message is generated.

 

70.  Simply insert the statement

Thread.dumpStack();

anywhere into your code to print a stack trace to standard error stream.

 

71.  Capture the error stream as
java MyProgram 2> errors.txt
To capture both System.err and System.out in the same file, use
java MyProgram >& errors.txt

 

72.  You can change the handler for uncaught exceptions with the static Thread.setDefaultUncaughtExceptionHandler method:

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

 

 

73.  To watch class loading, launch the Java virtual machine with the -verbose flag. This can occasionally be helpful to diagnose class path problems.

 

74.  The term “lint” originally described a tool for locating potential problems in C programs, but is now generically applied to any tools that flag constructs that are questionable but not illegal:


  
75.  Find out the ID of the operating system process that runs the virtual machine. In UNIX/Linux, run the ps utility; in Windows, use the task manager. Then launch the jconsole program:
jconsole processID
The console gives you a wealth of information about your running program. See www.oracle.com/technetwork/articles/java/jconsole-1564139.html for more information.

 

76.  You can use the jmap utility to get a heap dump that shows you every object on the heap:
jmap -dump:format=b,file=dumpFileName processID
jhat dumpFileName
Then, point your browser to localhost:7000. You will get a web application that lets you drill down into the contents of the heap at the time of the dump.

 

77.  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. The profiling information is sent to System.out. The output also tells you which methods were compiled by the just-in-time compiler.

  • 大小: 137.2 KB
  • 大小: 154.5 KB
  • 大小: 62.4 KB
  • 大小: 93 KB
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics