先上图:
Java的顶级异常类是 Throwable
类,下面分为 Error
和 Exception
两大子类。 Error
及其子异常代表的是Java运行时系统内部错误,资源耗尽等情况。如果这种异常发生了,我们只能让自己的程序退出。而 Exception
及其分支异常则是我们写代码时需要关注的。
上面是按照类的继承来分的。另外一种更重要的分法是把 Error
和 RuntimeException
及其子类异常称为 非受检异常(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.
捕获多个异常时,后面的异常不能是前面异常的子类,否则会报语法错误。比如下面的就是错误的,因为后面的 FileNotFoundException
是前面 IOException
的子类:
try { // do something; } catch (IOException ioe) { // do something; } catch (FileNotFoundException e) { // do something; }
从Java SE7开始,可以在一个catch里面捕获多个异常,但要注意多个异常之间不能有继承关系,比如不能在一个catch里面捕获 FileNotFoundException
和 IOException
,因为他两个有继承关系。使用语法为: 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"); }
throws
声明抛出一个受检异常,实际抛的时候,抛出声明的异常的子类异常也是OK的。捕获到异常的人可以使用 e.getClass().getTypeName()
获取抛出的真正异常类别。 最后附一下 Core Java Volume I 一书中关于使用异常的一些技巧,为了保持原汁原味就直接附上英文了。
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 }
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 } }
4、5、6三条要表达的核心意思就是该出手时就出手,该抛异常就抛异常。别觉得这个异常发生的几率很低,就自己默默地捕获然后忽略了,或者没有正确处理。把异常抛给更适合处理异常的上层代码并没有什么不合适的。早点抛出,晚点捕获(throw early, catch late)。