本文主要是介绍10分钟帮你重新梳理 Java 异常 —— Throwable、Error、Exception,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
目录
一. 前言
二. 异常的体系结构
2.1. 异常的分类
2.2. 常见编译时异常(受检异常)
2.3. 常见运行时异常(非受检异常)
2.4. 常见错误(Error)
2.5. Throwable 类
2.6. 自定义异常
2.7. 重写方法的异常抛出规则
三. 异常的捕获
3.1. 代码示例
3.2. try...catch...finally
3.3. 异常声明 throws
3.4. 手动抛出异常 throw
3.5. 开发中使用 try...catch...finally 还是 throws?
一. 前言
程序运行时,发生的不被期望的事件,它阻止了程序按照程序员的预期正常执行,这就是异常。在 Java 中,即 Java 在编译、运行或者运行过程中出现的错误。异常发生时,是任程序自生自灭,立刻退出终止,还是预判性的对其进行捕获处理,显然后者更具保障。
异常处理机制能让程序在异常发生时,按照代码预先设定的异常处理逻辑,针对性地处理异常,让程序尽最大可能恢复正常并继续执行,且保持代码的清晰。
Java 中的异常可以是函数中的语句执行时引发的,也可以是程序员通过 throw 语句手动抛出的,只要在 Java 程序中产生了异常,就会用一个对应类型的异常对象来封装异常,JRE 就会试图寻找异常处理程序来处理异常。
二. 异常的体系结构
2.1. 异常的分类
异常就是有异于常态,和正常情况不一样,有错误出现。在 Java 中,将程序执行过程中的不正常的情况称之为异常,开发过程中的语法错误和逻辑错误不是异常,发生异常时,Java 会阻止当前方法或作用域的情况。
异常的体系结构:
Throwable,是 Java 中所有异常和错误的超类,其两个子类为 Error(错误)和 Exception(异常),Exception 又分为编译时异常,又称受检异常(Checked Exception)和运行时异常 (Runtime Exception),又称非受检异常(Unchecked Exception)。
1. Error:是程序中无法处理的错误,表示运行应用程序中出现了严重的错误。此类错误一般表示代码运行时 JVM 出现问题。通常有 Virtual MachineError(虚拟机运行错误)、NoClassDefFoundError(类定义错误)等。比如说当 JVM 耗完可用内存时,将出现OutOfMemoryError。此类错误发生时,JVM 将终止线程。非代码性错误。因此,当此类错误发生时,应用不应该去处理此类错误。
2. Exception:是程序本身可以捕获并且可以处理的异常。其中可分为运行时异常和编译时异常:
- 运行时异常(RuntimException):又叫非受检异常(Unchecked Exception),RuntimeException 类及其子类表示 JVM 在运行期间可能出现的错误。编译器不会检查此类异常,并且不要求处理异常,比如用空值对象的引用(NullPointerException)、数组下标越界(ArrayIndexOutBoundException)。此类异常属于不可查异常,一般是由程序逻辑错误引起的,在程序中可以选择捕获处理,也可以不处理。
- 编译时异常:又叫受检异常(Checked Exception),Exception 中除 RuntimeException 及其子类之外的异常都属于编译时异常。编译器会检查此类异常,如果程序中出现此类异常,比如说 IOException,则必须对该异常进行处理,要么使用 try...catch 捕获,要么使用 throws 语句抛出,否则编译不通过。
从程序执行的过程来看编译时异常和运行时异常:
- 编译时异常:程序在编译时发生的异常(javac 源文件名 .java)。
- 运行时异常:程序在运行时发生的异常(java 字节码文件名)。
2.2. 常见编译时异常(受检异常)
异常名称 | 异常说明 |
---|---|
ClassNotFoundException | 当试图加载某个类时,但找不到该类时,会抛出ClassNotFoundException。常见的情况是未正确配置类路径或引入依赖库。处理该异常可以通过检查类路径或引入正确的库来解决。 |
IOException | 当发生输入或输出操作失败时,比如文件读写错误或网络连接问题,会抛出IOException。处理该异常可以使用try...catch语句捕获并处理异常,或者在方法声明中使用throws关键字声明抛出该异常。 |
SQLException | SQLException是处理数据库操作时可能发生的异常,如连接数据库失败、执行SQL语句错误等。处理方法与IOException类似,可以使用try-catch语句捕获并处理异常,或在方法声明中声明抛出SQLException。 |
FileNotFoundException | 当试图打开指定路径名表示的文件失败时,抛出此异常。 |
InterruptedException | 线程中断异常 |
2.3. 常见运行时异常(非受检异常)
异常名称 | 异常说明 |
---|---|
ClassCastException | 当试图将对象强制转换为不是实例的子类时,抛出该异常。 |
ArithmeticException | 当出现异常的运算条件时,抛出此异常。例如,一个整数"除以零"时,抛出此类的一个实例。 |
IllegalArgumentException | 抛出的异常表明向方法传递了一个不合法或不正确的参数。 |
ArrayIndexOutOfBoundsException | 用非法索引访问数组时抛出的异常。如果索引为负或大于等于数组大小,则该索引为非法索引。 |
IndexOutOfBoundsException | 指示某排序索引(例如对数组、字符串或向量的排序)超出范围时抛出。 |
NullPointerException | 当应用程序试图在需要对象的地方使用 null 时,抛出该异常。 |
NumberFormatException | 当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常。 |
StringIndexOutOfBoundsException | 此异常由 String 方法抛出,指示索引或者为负,或者超出字符串的大小。 |
UnsupportedOperationException | 当不支持请求的操作时,抛出该异常。 |
ClassNotFoundException | 应用程序试图加载类时,找不到相应的类,抛出该异常。 |
IllegalAccessException | 拒绝访问一个类的时候,抛出该异常。 |
NoSuchMethodException | 请求的方法不存在。 |
2.4. 常见错误(Error)
错误名称 | 错误说明 |
---|---|
OutOfMemoryError | 又称OOM,表示内存溢出错误,通常发生在内存泄露或内存不足的情况。 |
StackOverflowError | 表示栈溢出错误,通常发生在递归过深的情况。 |
NoClassDefFoundError | 解释器找不到在主方法使用到了的类的文件的时候。 |
NoSuchMethodFoundError | 尝试调用类的方法而该方法在类里面没有定义时,出现此错误 |
ClassFormatError | 链接错误,发生在一个类文件不能被读取或者解释为一个类文件的时候。 |
ExceptionInInitializerError | 静态初始化有问题时发生此错误。 |
NoSuchFieldError | 一个应用程序尝试去访问一个对象中的某个域,可是指定域在类中不复存在时发生此错误。 |
2.5. Throwable 类
以下是 Throwable 类的主要方法:
public class Throwable implements Serializable {/** use serialVersionUID from JDK 1.0.2 for interoperability */private static final long serialVersionUID = -3042686055658047285L;private transient Object backtrace;private String detailMessage;private Throwable cause = this;private StackTraceElement[] stackTrace = UNASSIGNED_STACK;private List<Throwable> suppressedExceptions = SUPPRESSED_SENTINEL;...public Throwable() {fillInStackTrace();}public Throwable(String message) {fillInStackTrace();detailMessage = message;}public Throwable(String message, Throwable cause) {fillInStackTrace();detailMessage = message;this.cause = cause;}public Throwable(Throwable cause) {fillInStackTrace();detailMessage = (cause==null ? null : cause.toString());this.cause = cause;}protected Throwable(String message, Throwable cause,boolean enableSuppression,boolean writableStackTrace) {if (writableStackTrace) {fillInStackTrace();} else {stackTrace = null;}detailMessage = message;this.cause = cause;if (!enableSuppression)suppressedExceptions = null;}/*** 返回关于发生的异常的详细信息。这个消息在Throwable 类的构造函数中初始化了。*/public String getMessage() {return detailMessage;}/*** 返回一个Throwable 对象代表异常原因*/public synchronized Throwable getCause() {return (cause==this ? null : cause);}/*** 使用getMessage()的结果返回类的串级名字*/public String toString() {String s = getClass().getName();String message = getLocalizedMessage();return (message != null) ? (s + ": " + message) : s;}/*** 打印toString()结果和栈层次到System.err,即错误输出流*/public void printStackTrace() {printStackTrace(System.err);}/*** 返回一个包含堆栈层次的数组。下标为0的元素代表栈顶,最后一个元素代表方法调用堆栈的栈底*/public StackTraceElement[] getStackTrace() {return getOurStackTrace().clone();}/*** 用当前的调用栈层次填充Throwable 对象栈层次,添加到栈层次任何先前信息中。*/public synchronized Throwable fillInStackTrace() {if (stackTrace != null ||backtrace != null /* Out of protocol state */ ) {fillInStackTrace(0);stackTrace = UNASSIGNED_STACK;}return this;}
}
Throwable 类说明如下:
1. public String getMessage():返回关于发生的异常的详细信息。这个消息在 Throwable 类的构造函数中初始化了。
2. public Throwable getCause():返回一个 Throwable 对象代表异常原因。
3. public String toString():使用 getMessage() 的结果返回类的串级名字。
4. public void printStackTrace():打印 toString() 结果和栈层次到 System.err,即错误输出流。
5. public StackTraceElement [] getStackTrace():返回一个包含堆栈层次的数组。下标为0的元素代表栈顶,最后一个元素代表方法调用堆栈的栈底。
6. public syncronized Throwable fillInStackTrace():用当前的调用栈层次填充 Throwable 对象栈层次,添加到栈层次任何先前信息中。
2.6. 自定义异常
在 Java 中,你可以自定义异常。如果要自定义异常类,则扩展 Exception 类即可,因此这样的自定义异常都属于受检异常(Checked Exception)。如果要自定义非受检异常,则扩展自RuntimeException。
按照国际惯例,自定义的异常应该总是包含如下的构造函数:
- 一个无参构造函数;
- 一个带有 String 参数的构造函数,并传递给父类的构造函数;
- 一个带有 String 参数和 Throwable 参数,并都传递给父类构造函数;
- 一个带有 Throwable 参数的构造函数,并传递给父类的构造函数。
package com.lm.it.ex.demo;public class MyException extends RuntimeException {static final long serialVersionUID = -1234719074324978L;public MyException() {}public MyException(String message) {super(message);}public MyException(String message, Throwable cause) {super(message, cause);}public MyException(Throwable cause) {super(cause);}public static void main(String[] args) {throw new MyException("自定义运行时异常");/*Exception in thread "main" com.lm.it.ex.demo.MyException: 自定义运行时异常at com.lm.it.ex.demo.MyException.main(MyException.java:15)*/}
}
2.7. 重写方法的异常抛出规则
子类重写的方法抛出的异常类型不能大于父类被重写的方法抛出的异常类型。
下面代码中,main() 方法中向 display() 方法中传入了 SuperClass 的子类对象。那么到 display() 方法中调用 s.method 调用的就是 SpuerClass 的子类 SubClass 重写的 method 方法。如果这个方法抛出的异常范围大于父类 SuperClass 所抛出的异常的话,那么在 display() 方法中对异常的 catch 处理就会 catch 不到这个异常:
package com.lm.it.ex.demo;import java.io.FileNotFoundException;
import java.io.IOException;public class OverrideTest {public void display(SuperClass s) {try {s.method();} catch (IOException e) {e.printStackTrace();}}public static void main(String[] args) {OverrideTest overrideTest = new OverrideTest();overrideTest.display(new SubClass());}
}class SuperClass {public void method() throws IOException {System.out.println("super");}
}class SubClass extends SuperClass {public void method() throws FileNotFoundException {System.out.println("sub");}
}
三. 异常的捕获
异常的抛出:如果程序在运行过程中执行某段代码时发生了异常,那么系统(JVM)将会根据异常的类型,在异常代码处创建对应的异常类型的对象并抛出,抛出给程序的调用者。一旦抛出对象以后,其后的代码不再运行,程序终止。
异常的抛出:分为系统向外抛出异常和手动向外抛出异常(throw)。
异常的抓取:异常的抓取可以理解为异常的处理方式,有 try-catch-finally 和 throws 两种方式。
3.1. 代码示例
package com.lm.it.ex;import java.util.Scanner;public class CommonEx {static void ArithmeticExceptionDemo() {int a = 10;int b = 0;int c = a / b;/*Exception in thread "main" java.lang.ArithmeticException: / by zeroat com.lm.it.ex.ArithmeticEx.main(ArithmeticEx.java:7)*/}static void ClassCastExceptionDemo() {Object obj = new Double(1);String str = (String)obj;/*Exception in thread "main" java.lang.ClassCastException: class java.lang.Double cannot be cast to class java.lang.String (java.lang.Double and java.lang.String are in module java.base of loader 'bootstrap')at com.lm.it.ex.ClassCastEx.main(ClassCastEx.java:7)*/}static void InputMismatchExceptionDemo() {Scanner scan = new Scanner(System.in);int num = scan.nextInt();System.out.println(num);/*asdException in thread "main" java.util.InputMismatchExceptionat java.base/java.util.Scanner.throwFor(Scanner.java:939)at java.base/java.util.Scanner.next(Scanner.java:1594)at java.base/java.util.Scanner.nextInt(Scanner.java:2258)at java.base/java.util.Scanner.nextInt(Scanner.java:2212)at com.lm.it.ex.InputMismatchEx.main(InputMismatchEx.java:8)*/}static void NullPointerExceptionDemo() {int[] arr = null;System.out.println(arr[3]);/*Exception in thread "main" java.lang.NullPointerException: Cannot load from int array because "arr" is nullat com.lm.it.ex.NullPointerEx.main(NullPointerEx.java:6)*/}static void NumberFormatExceptionDemo() {String str = "abc";int a = Integer.parseInt(str);/*Exception in thread "main" java.lang.NumberFormatException: For input string: "abc"at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:67)at java.base/java.lang.Integer.parseInt(Integer.java:660)at java.base/java.lang.Integer.parseInt(Integer.java:778)at com.lm.it.ex.NumberMismatchEx.main(NumberMismatchEx.java:6)*/}static void ArrayIndexOutOfBoundExceptionDemo() {int[] arr = new int[3];System.out.println(arr[3]);/*Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3at com.lm.it.ex.XIndexOutOfBoundEx.main(XIndexOutOfBoundEx.java:6)*/String str = "abc";System.out.println(str.charAt(3));/*Exception in thread "main" java.lang.StringIndexOutOfBoundsException: String index out of range: 3at java.base/java.lang.StringLatin1.charAt(StringLatin1.java:48)at java.base/java.lang.String.charAt(String.java:711)at com.lm.it.ex.XIndexOutOfBoundEx.main(XIndexOutOfBoundEx.java:11)*/}public static void main(String[] args) {ArrayIndexOutOfBoundExceptionDemo();}
}
3.2. try...catch...finally
将可能出现异常的代码放到 try{} 中,运行时,如果代码发生了异常的话,就会生成一个对应的异常类的对象。这个产生的异常对象会与 catch 的异常类型相匹配,匹配成功后就被 catch 捕获,然后运行catch {} 中的代码,一般 catch 中的代码为处理异常的代码,比如返回异常的信息和返回异常的详细信息等,一旦异常处理完成,就会跳出当前的 try...catch 结构。无论有没有发生异常finally 中的代码都会最后被执行。
注意点:
- catch 多个异常类型的时候,如果有子父类关系,小的范围写上面大的范围写下面;如果没有子父类关系,谁在上谁在下无所谓。
- 在 try 结构中生命的变量,在出了 try 结构以后,就不能在被调用。如果想避免这种情况,就需要在 try 之前声明变量并初始化,在 try 中赋值。
- 如果 finally 里面有 return 那么返回的一定是 finally 里面的。
- try...catch...finally 结构可以相互嵌套。
- 使用 try...catch...finally 处理编译时异常,是让程序在编译时就不再报错,但是运行时仍然有可能报错。相当于我们使用 try...catch 将一个编译时可能出现的异常,延迟到运行时出现。
- 在开发中,运行时异常比较常见,此时一般不用 try...catch 去处理,因为处理和不处理都是一个报错,最好办法是去修改代码。针对编译时异常,我们一定要考虑异常处理。
package com.lm.it.ex.demo;public class TryCatchFinally {public static void main(String[] args) {String str = "abc";try {int i = Integer.parseInt(str);} catch (NumberFormatException e) {e.printStackTrace();} catch (Exception e) {System.out.println(e.getMessage());} finally {System.out.println("运行完毕");}}
}
3.3. 异常声明 throws
throws 一般用于方法中可能存在异常时,需要将异常情况向方法之上的层级抛出,由抛出方法的上一级来解决这个问题。如果方法的上一级无法解决的就会再将异常向上抛出,最终会抛给main() 方法。这样一来,main() 方法中调用了这个方法的时候,就需要解决这个可能出现的异常。当然 main() 方法也可以不解决异常,将异常往上抛出给 Java 虚拟机,如果 Java 虚拟机也无法解决的话,那么程序就直接崩溃了。
throws + 异常类型写在方法的声明处,指明此方法执行时可能会抛出的异常类型。一旦方法体执行时出现异常,仍会在异常代码处生成一个异常类的对象,此对象满足 throws 后异常类型时,就会被抛出。异常代码的后续的代码,就不再执行。
package com.lm.it.ex.demo;import java.io.FileNotFoundException;public class ThrowsEx {public void setAge2(int age) throws FileNotFoundException {if (age < 0) {throw new FileNotFoundException("输入的年龄小于0");}}public void TestsetAge2() throws FileNotFoundException {setAge2(2);}public static void main(String[] args) {try {ThrowsEx.throwsExTest();} catch (FileNotFoundException e) {e.printStackTrace();}}
}
注意:将异常向上抛出也算是一种处理异常的方法。
3.4. 手动抛出异常 throw
手动抛出的异常有两种,分别为运行时异常和编译时异常。抛出运行时异常时,可以不用处理抛出的这个异常;抛出编译时异常时,必须要处理抛出的这个异常。
请看下面的代码:
package com.lm.it.ex.demo;import java.io.FileNotFoundException;public class ThrowEx {// 手动抛出运行时异常public void setAge(int age) {if (age < 0) {throw new NullPointerException("输入的年龄小于0");}}/** 此方法手动抛出了运行时异常* 运行时异常可以不用处理*/// 手动抛出编译时异常public void setAge2(int age) throws FileNotFoundException {if (age < 0) {throw new FileNotFoundException("输入的年龄小于0");}}/** 此方法手动抛出的了编译时异常,编译时异常需要被处理* 这里采用了 throws 这个异常,也就是说方法并没有处理这个异常,而是将异常抛给了调用者* 这样一来调用了这个方法的人就必须要处理这个异常才可以。* 注意:在这里并不用自己使用 try catch 处理这个异常,自己在方法里抛出异常,方法再自己处理没有任何作用。* 所以方法中的异常需要抛给调用者去处理.*/public static void main(String[] args) {ThrowEx throwEx = new ThrowEx();throwEx.setAge(-5);}
}
3.5. 开发中使用 try...catch...finally 还是 throws?
如果分类中被重写的方法没有 throws 方式处理异常,则子类重写的方法也不能使用 throws。意味着如果子类重写的方法中有异常,必须使用 try...catch...finally 方式处理。
执行的方法中,先后又调用了另外的几个方法,这几个方法是递进关系执行的。我们建议这几个方法使用 throws 的方式处理。而执行的方法 a 可以考虑使用 try...catch...finally 方式进行处理。
因为如果在 a 方法要调用 d 方法时,如果在 b 方法内 try...catch,当 b 方法异常时,并不会给方法 a 返回所需要的数据。因此最好使用 throws 将异常集合到一块再处理。
注意:try...catch 和 throws 在方法中不要同时使用,因为只要使用 try...catch 就已经将异常处理掉了,再 throws 没有任何意义。
这篇关于10分钟帮你重新梳理 Java 异常 —— Throwable、Error、Exception的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!