Effective Java Second Edition Chapter 9 Exceptions

9 abnormal

Java's throwable structure is divided into three categories:
(1)
Runtime exceptions: All are RuntimeException and its subclass exceptions, such as NullPointerException (null pointer exception), IndexOutOfBoundsException (subscript out-of-bounds exception), etc. These exceptions are unchecked exceptions, and the program can choose to capture them or not. These exceptions are generally caused by program logic errors, and the program should avoid the occurrence of such exceptions as much as possible from a logical point of view.

The characteristic of the runtime exception is that the Java compiler will not check it, that is to say, when such an exception may occur in the program, even if it is not caught with a try-catch statement or thrown with a throws clause statement, it will still be compiled.

(2)
Non-runtime exceptions (compiler exceptions, checked exceptions, checked exceptions): They are exceptions other than RuntimeException, and they all belong to the Exception class and its subclasses (not the subclasses of RunTimeException). From the perspective of program syntax, it is an exception that must be handled. If it is not handled, the program cannot be compiled. Such as IOException, SQLException, etc., and user-defined Exception exceptions, generally do not define custom check exceptions.
(3) Error

Item 57 Use exceptions only for unusual situations

        try {
    
    
            int i = 0;
            while(true) {
    
    
                range[i++].climb();
            }
        } catch (ArrayIndexOutOfBoundsException e) {
    
    
        }

This is a wrong way of writing, trying to use java's error judgment mechanism to improve performance, because the VM checks for out-of-bounds conditions for every array access, and mistakenly believes that the normal loop termination check is hidden by the compiler. There are three errors in this thinking:

  • The original intention of exception design is for abnormal situations, so few JVMs will optimize exceptions.
  • Putting code in a try-catch block prevents certain optimizations that modern JVMs are supposed to perform.
  • Using standard array traversal does not result in redundant checks. Now the JVM will optimize this.

As the name suggests, exceptions should only be used in exceptional situations, exceptions should never be used for normal flow of control.

A well-designed API should not force its clients to use exceptions for normal control flow. If a class has "state-dependent" methods (that is, methods that can only be called under certain unpredictable conditions), then this class should also have state-testing (state-testing) methods (that is, indicate whether this state-dependent method can be called). For example, Iterator has a state-related next method, and a corresponding state-testing method hasNext.

Guidelines for the two practices "stateful test methods" and "recognized return values":

  • If the object is accessed concurrently in the absence of external synchronization, or can be changed by the outside world, use "recognized return values". Because the object state may have changed between calling the state test method and the state-related method.
  • If a state-testing method must duplicate the work of a state-related method, use a "recognized return value" for performance reasons.
  • If all other things are equivalent, the "state test method" is preferred.

Item 58 Use checked exceptions for recoverable cases and runtime exceptions for programming errors

The Java language provides three throwable structures (throwable): checked exception (checked exception), runtime exception (runtime exception) and error (error).

If you expect the caller to recover properly, you should use checked exceptions for this situation. Every checked exception declared to be thrown in a method is a potential indication to the user of the API that the condition associated with the exception is a possible outcome of calling the method.

Runtime exceptions and errors are throwable constructs that don't need and shouldn't be caught. If a program throws an unchecked exception or error, it is often an unrecoverable situation, and continuing to execute does more harm than good.

Use runtime exceptions to indicate programming errors. Most runtime exceptions represent precondition violations. A prerequisite violation is when an API client fails to abide by a contract established by the API specification. For example, the access convention of an array specifies the range of the subscript of the array. ArrayIndexOutOfBoundsException indicates that this premise has been violated.

By convention, errors are reserved by the JVM to indicate insufficient resources, constraint failures, or other conditions that prevent program execution from continuing. So it's best not to implement any new Error subclasses. All unchecked throw constructs you implement should be subclasses of RunTimeException.

Item 59 Avoid unnecessary use of checked exceptions

If the correct use of the API does not prevent the generation of such abnormal conditions, and once an exception occurs, the programmer using the API can take useful actions immediately. This burden on the programmer is justified. Unless both of the above conditions are true, it is more appropriate to use unchecked exceptions.

Rule 60 Exceptions to Priority Standards

IllegalArgumentException: Illegal parameter
IllegalStateException: The object state is wrong. For example, this exception is thrown if the caller attempts to use an object before it has been properly initialized.
NullPointerException:
ArrayIndexOutOfBoundsException:
ConcurrentModificationException: This exception can be thrown if an object is designed for single-threaded use or to be used with external synchronization mechanisms once it is found to be (or has been) concurrently modified.
UnsupportedOperationException:
ArithmeticException:
NumberFormatException:

Item 61 Throw the exception corresponding to the abstraction

Higher-level implementations should catch lower-level exceptions while throwing exceptions that can be interpreted in terms of higher-level abstractions. This practice is called exception translation. For example:

	此代码片段取自AbstractSequentialList/**
     * Returns the element at the specified position in this list.
     *
     * <p>This implementation first gets a list iterator pointing to the
     * indexed element (with <tt>listIterator(index)</tt>).  Then, it gets
     * the element using <tt>ListIterator.next</tt> and returns it.
     *
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E get(int index) {
    
    
        try {
    
    
            return listIterator(index).next();
        } catch (NoSuchElementException exc) {
    
    
            throw new IndexOutOfBoundsException("Index: "+index);
        }
    }

Exception chaining and chaining-aware:

    public static void main(String[] args) throws HigherLevelException {
    
    
        try {
    
    
            throw new LowerLevelException();
        } catch (LowerLevelException cause) {
    
    
            throw new HigherLevelException(cause);
        }
    }

public class HigherLevelException extends Exception {
    
    
    public HigherLevelException(Throwable cause) {
    
    
        super(cause);
    }
}

In the exception chain, low-level exceptions are passed to high-level exceptions, and high-level exceptions provide access methods to obtain low-level exceptions. For exceptions that do not have a backing chain, you can take advantage of Throwable's capabilities. initCause sets the cause of the exception, and getCause can access the cause.

    public synchronized Throwable initCause(Throwable cause) {
    
    
        if (this.cause != this)
            throw new IllegalStateException("Can't overwrite cause with " +
                                            Objects.toString(cause, "a null"), this);
        if (cause == this)
            throw new IllegalArgumentException("Self-causation not permitted", this);
        this.cause = cause;
        return this;
    }

    public synchronized Throwable getCause() {
    
    
        return (cause==this ? null : cause);
    }

Exception translation cannot be abused. Make sure that low-level methods execute successfully before calling them, so that they don't throw exceptions. It is useful to check the validity of parameters.

Item 62 Document every exception thrown by a method

Always declare checked exceptions individually, and use the javadoc @throws tag to document exactly the conditions under which each exception is thrown.

For methods in interfaces, it is especially important to document the unchecked exceptions that it may throw. This document forms part of the **general contract** of the interface. Specifies the public behavior that interface implementations must follow.

Use the javadoc's @throws tag to document every unchecked exception that a method may throw, but do not use the throws keyword to include unchecked exceptions in the method's declaration.

If many methods in a class throw the same exception for the same reason, it is acceptable to document the exception in the class's doc comments.

Clause 63 Include information in detail messages to catch failures

In order to catch failures, the exception details should contain the values ​​of all parameters and fields that "contributed to the exception". For example, the details of IndexOutOfBoundsException should include: the lower bound, the upper bound, and the index value that does not fall within the bounds.

The "hard data" in the exception message is very important, but it is not necessary to include a lot of descriptive information. It's a good idea to pass this "hard data" in the exception's constructor. For example:

    以前IndexOutOfBoundsException有这种构造函数,现在没有了。
    public IndexOutOfBoundsException(int lowerBound, int upperBound, int index) {
    
    
        super(String.format("Lower bound: %d, Upper bound: %d, Index = %d"));
        this.lowerBound = lowerBound;
        this.upperBound = upperBound;
        this.index = index;
    }

Item 64 Strive to keep failures atomic

In general, a failed method call should leave the object in the state it was in before it was called . Methods with this property are failure atomic .

Several ways to achieve failure atomicity:

  • Immutable objects
    prevent the creation of new objects if the operation fails. The original object is in its original state.
  • For mutable objects, check the validity of the parameters before performing the operation.
  • Adjust the order of computation processing so that any portion of computation that might fail occurs before object state is modified.
  • Write recovery code (recovery code)
    to intercept failures that occur during the operation, so that the object is rolled back to the state before the operation began. This approach is primarily used for persistent (disk-based) data structures.
  • Performs an operation on a temporary copy of the object, and when the operation completes, replaces the contents of the object with the result from the temporary copy.
    For example, Collections.sort first transfers the input list to an array before performing sorting, so as to reduce the overhead required to access elements in the inner loop of sorting. While considering performance, it adds an advantage: even if the sorting fails, the input list can be guaranteed to remain the same.
          public static <T extends Comparable<? super T>> void sort(List<T> list) {
          
          
          list.sort(null);
      }  
    
          default void sort(Comparator<? super E> c) {
          
          
          Object[] a = this.toArray();
          Arrays.sort(a, (Comparator) c);
          ListIterator<E> i = this.listIterator();
          for (Object e : a) {
          
          
              i.next();
              i.set((E) e);
          }
      } 
    

In general, raising any exception as part of the method specification should leave the object in the state it was in before it was called. If this rule is violated, the API documentation should specify what state the object will be in.

Item 65 Don't Ignore Exceptions

An empty catch block will make exceptions not achieve their intended purpose , which is to force programmers to deal with abnormal situations. At a minimum, the catch block should contain a statement explaining why the exception can be ignored.

Correctly handling exceptions can completely recover from failure. Just propagating the exception to the outside world at least causes the program to fail quickly, preserving information about the condition that helps debug that failure.

Guess you like

Origin blog.csdn.net/kaikai_sk/article/details/126728536