Java的异常

异常分类

先上图:

Java Exception

Java的顶级异常类是Throwable类,下面分为ErrorException两大子类。Error及其子异常代表的是Java运行时系统内部错误,资源耗尽等情况。如果这种异常发生了,我们只能让自己的程序退出。而Exception及其分支异常则是我们写代码时需要关注的。

上面是按照类的继承来分的。另外一种更重要的分法是把ErrorRuntimeException及其子类异常称为非受检异常(unchecked exception),剩余其它的所有异常称为受检异常(checked exception)。所谓非受检异常就是我们代码中无需对这类异常进行捕获处理,但受检异常则必须有地方捕获处理,否则编译器编译的时候就会报错。

为什么非受检异常无需处理呢?一方面,对于Error类的异常刚才说了,是系统内部错误,任何时候任何地方都可能出现,我们避免不了,如果出现了,我们也无能为力。另一方面,对于RuntimeException异常,基本都是因为我们的代码有bug导致的,这个时候正确的解决方法是修复代码bug,而不是try catch(如果你都已经知道catch了,那你还不赶紧改掉bug?)。

为什么受检异常需要程序处理?因为受检异常也无法避免,且往往不是我们自己代码bug导致的(这点和RuntimeException不同),但如果出现了这类异常,我们一般还是可以处理的(这点和Error不同)。拿受检异常FileNotFoundException举个例子,我们从磁盘读取文件时,如果文件不存在就会抛这个异常,系统上面这个文件存不存在不是我们能决定的,也许你可以先判断存不存在,存在才去读。但这两个操作不是一个原子操作,也许你检查的时候在,真正去读的时候就不在了,所以避免不了。但如果真的出现了,我们的程序不一定就要退出,也许可以采取其它方式让程序继续,这是由业务场景决定的。比如比较常见的一种场景就是配置文件,如果不存在,就全部使用默认值。

以下是Error的直接子类:

AnnotationFormatError, AssertionError, AWTError, CoderMalfunctionError, FactoryConfigurationError, FactoryConfigurationError, IOError, LinkageError, SchemaFactoryConfigurationError, ServiceConfigurationError, ThreadDeath, TransformerFactoryConfigurationError, VirtualMachineError

以下是Exception的直接子类:

AclNotFoundException, ActivationException, AlreadyBoundException, ApplicationException, AWTException, BackingStoreException, BadAttributeValueExpException, BadBinaryOpValueExpException, BadLocationException, BadStringOperationException, BrokenBarrierException, CertificateException, CloneNotSupportedException, DataFormatException, DatatypeConfigurationException, DestroyFailedException, ExecutionException, ExpandVetoException, FontFormatException, GeneralSecurityException, GSSException, IllegalClassFormatException, InterruptedException, IntrospectionException, InvalidApplicationException, InvalidMidiDataException, InvalidPreferencesFormatException, InvalidTargetObjectTypeException, IOException, JAXBException, JMException, KeySelectorException, LambdaConversionException, LastOwnerException, LineUnavailableException, MarshalException, MidiUnavailableException, MimeTypeParseException, MimeTypeParseException, NamingException, NoninvertibleTransformException, NotBoundException, NotOwnerException, ParseException, ParserConfigurationException, PrinterException, PrintException, PrivilegedActionException, PropertyVetoException, ReflectiveOperationException, RefreshFailedException, RemarshalException, RuntimeException, SAXException, ScriptException, ServerNotActiveException, SOAPException, SQLException, TimeoutException, TooManyListenersException, TransformerException, TransformException, UnmodifiableClassException, UnsupportedAudioFileException, UnsupportedCallbackException, UnsupportedFlavorException, UnsupportedLookAndFeelException, URIReferenceException, URISyntaxException, UserException, XAException, XMLParseException, XMLSignatureException, XMLStreamException, XPathException

以下是RuntimeException的直接子类:

AnnotationTypeMismatchException, ArithmeticException, ArrayStoreException, BufferOverflowException, BufferUnderflowException, CannotRedoException, CannotUndoException, ClassCastException, CMMException, CompletionException, ConcurrentModificationException, DataBindingException, DateTimeException, DOMException, EmptyStackException, EnumConstantNotPresentException, EventException, FileSystemAlreadyExistsException, FileSystemNotFoundException, IllegalArgumentException, IllegalMonitorStateException, IllegalPathStateException, IllegalStateException, IllformedLocaleException, ImagingOpException, IncompleteAnnotationException, IndexOutOfBoundsException, JMRuntimeException, LSException, MalformedParameterizedTypeException, MalformedParametersException, MirroredTypesException, MissingResourceException, NegativeArraySizeException, NoSuchElementException, NoSuchMechanismException, NullPointerException, ProfileDataException, ProviderException, ProviderNotFoundException, RasterFormatException, RejectedExecutionException, SecurityException, SystemException, TypeConstraintException, TypeNotPresentException, UncheckedIOException, UndeclaredThrowableException, UnknownEntityException, UnmodifiableSetException, UnsupportedOperationException, WebServiceException, WrongMethodTypeException

本节最后再说明一个问题:能否用try...catch来捕获非受检异常,比如空指针异常NullPointerException?

答案是可以的,任何异常都可以用try...catch来捕获,不会有语法错误,且如果发生了对应的异常,的确可以捕获到。但语法上可以不代表实际中就能这么用,实际编码的时候千万别去捕获非受检异常,那样会被别人鄙视的。如果你已经预见可能会产生某种RuntimeException的异常,在代码里面做一下判断就好了,而不是用try...catch.

关于异常使用的一些注意点

  1. 捕获多个异常时,后面的异常不能是前面异常的子类,否则会报语法错误。比如下面的就是错误的,因为后面的FileNotFoundException是前面IOException的子类:

    try {
        // do something;
    } catch (IOException ioe) {
        // do something;
    } catch (FileNotFoundException e) {
        // do something;
    }
  2. 从Java SE7开始,可以在一个catch里面捕获多个异常,但要注意多个异常之间不能有继承关系,比如不能在一个catch里面捕获FileNotFoundExceptionIOException,因为他两个有继承关系。使用语法为:catch (ExceptionType1 | ExceptionType2 e). 还有要特别注意的是,捕获多个异常的时候,我们定义的那个异常变量e是final的,所以不能在catch的函数体里面给它赋值。但如果是只捕获一个异常的情况下,不是final,可以赋值。看下面代码:

    try {
        // do something;
    } catch (FileNotFoundException e1) {
        // 没有问题,e1不是final,可以重新赋值
        e1 = new FileNotFoundException("xxx");      
    } catch (IOException | ParseException e2) {
        // 语法错误,e2是final的,不能赋值
        e2 = new IOException("xxx");
    }
  3. 我们覆写(override)父类的方法的时候,如果这个方法有抛出受检异常,那我们覆写后抛出的异常不能比父类抛出的异常更宽泛,也就是说只能抛出父类抛出的异常或者其子类异常。当然,父类抛异常,子类覆写后不抛异常也是完全OK的。
  4. 我们用throws声明抛出一个受检异常,实际抛的时候,抛出声明的异常的子类异常也是OK的。捕获到异常的人可以使用e.getClass().getTypeName()获取抛出的真正异常类别。

使用异常的一些技巧

最后附一下Core Java Volume I一书中关于使用异常的一些技巧,为了保持原汁原味就直接附上英文了。

  1. Exception handling is not supposed to replace a simple test. 这个意思就是不要把try...catch当成分支判断去使用。

    // 假设我们需要遍历一个栈的元素,如何处理栈空的情况呢?
    
    // 正确的方式是每次先判断栈是否空,如下:
    if (!s.empty()) { s.pop(); }
    
    // 错误的方式是捕获栈空时抛出的异常(这是一个非受检异常),如下
    try {
        s.pop();
    } catch (EmptyStackException e) {
        // do something
    }
  2. Do not micromanage exceptions. 就是别写太多小的try...catch。如下是一个错误的示例(正确的方式是把for循环里面的两个小try...catch合并为一个大的try...catch):

    PrintStream out;
    Stack s;
    for (i = 0; i < 100; i++) {
        try {
            n = s.pop();
        } catch (EmptyStackException e) {
            // stack was empty
        }
    
        try {
            out.writeInt(n);
        } catch (IOException e) {
            // problem writing to file
        }
    }
  3. Make good use of the exception hierarchy. 意思就是要合理使用异常的层次结构,尽量不要直接抛顶级异常,比如Throwable、Exception、RuntimeException等,尽量找一个比较合适的子类异常。如果没有,可以自定义异常。
  4. Do not squelch exceptions. 不要压制异常。
  5. When you detect an error, “tough love” works better than indulgence. 当你发现错误时,“强硬的爱”比放纵更有效。
  6. Propagating exceptions is not a sign of shame. 把异常抛出去并不可耻。

4、5、6三条要表达的核心意思就是该出手时就出手,该抛异常就抛异常。别觉得这个异常发生的几率很低,就自己默默地捕获然后忽略了,或者没有正确处理。把异常抛给更适合处理异常的上层代码并没有什么不合适的。早点抛出,晚点捕获(throw early, catch late)。

Reference

  • Core Java Volume I
本文链接:http://niyanchun.com/java-exception.html,转载请注明出处。
扫码关注我的微信公众号:
赞赏


微信赞赏

支付宝赞赏

添加新评论

友情提醒:填写邮箱是当别人回复你的评论时,会给邮箱发邮件提醒。