Java - 异常处理
目录
异常处理(exceptions handing)
在程序编译或运行过程中出现问题,这就是异常。在编译阶段出现异常叫编译异常,运行阶段异常叫运行异常,前者出现时不做处理代码阶段无法通过,后者在程序运行中违反语法会直接停止运行。特别是在代码量大的程序中,很难避免运行异常,所以提前做好异常处理有利于提高程序健壮性。
Java 把常见异常给做成一个个类来使用:
代码出现问题时生成异常类对象,直接抛出,只有 Exception 异常是需要主动处理的,而 Error 则是系统问题,比如堆栈溢出,爆出来就爆了,除了问题你只能回去改代码。这俩类下面还有一堆子类,都是具体实现,我们只需要关注 Exception 类即可。
Error 和 Exception 下 RuntimeException 子类是 Unchecked Exception,表明在运行时代码逻辑出问题抛出异常,除此之外 Exception 和其子类都是 Checked Exception,需要必须编译前手动处理否则编译时异常。
常见运行时异常:
- NullPointerException,空指针异常
- ArrayIndexOutOfBoundsException,数组索引越界异常
- ArithmeticException,数学操作异常
- ClassCastException,类型转换异常
- NumberFormatException,数学格式异常
- InputMismatchException,输入格式匹配异常
常见编译时异常:
- ClassNotFoundException,类没找到异常
创建 ExceptionCatch.java:
import java.util.Scanner;
public class ExceptionCatch {
public static void main(String[] args) {
// ArrayIndexOutOfBoundsException,数组索引越界异常
String[] a = {"1"};
System.out.println(a[1]);
// ArithmeticException,数学操作异常
System.out.println(10 / 0);
// ClassCastException,类型转换异常
Object str = "12a";
int numStr = (int)str;
// NumberFormatException,数学格式异常
Integer.parseInt("1A");
// NullPointerException,空指针异常
String str2 = null;
str2.length();
// InputMismatchException,输入格式匹配异常。
Scanner scanner = new Scanner(System.in);
scanner.nextInt();
// ClassNotFoundException,类没找到异常。
Class.forName("java.lang.String").getName();
}
}
try-catch-finally
代码生成异常对象后会在 catch 匹配对应异常类型,匹配成功就执行catch 代码块中内容,执行完如果有 finally 代码块就去执行,没有就往下执行代码。因为处理了异常下面代码不会结束运行。
try {
// 可能会出现异常的代码。
} catch(ExceptionType name) {
// 异常处理代码。
} catch (ExceptionType name) {
// 异常处理代码。
} finally {
// 不管有没异常都会执行。
}
Java 7 以后 catch 支持捕获多个异常,再需要一起处理时很方便。
try {
// 可能会出现异常的代码。
} catch(ExceptionType | ExceptionType name) {
// 异常处理代码。
} finally {
// 不管有没异常都会执行。finally 是可选的,不是异常处理时必须写上。
}
异常对象有以下方法:
getMessage
,获取错误信息printStackTrace()
,打印出错误堆栈信息
将 ExceptionCatch.java 用 try-catch 改造下:
import java.util.Scanner;
public class ExceptionCatch {
public static void main(String[] args) {
try {
// ArrayIndexOutOfBoundsException,数组索引越界异常
String[] a = {"1"};
System.out.println(a[1]);
// ArithmeticException,数学操作异常
System.out.println(10 / 0);
// ClassCastException,类型转换异常
Object str = "12a";
int numStr = (int)str;
// NumberFormatException,数学格式异常
Integer.parseInt("1A");
// NullPointerException,空指针异常
String str2 = null;
str2.length();
// InputMismatchException,输入格式匹配异常。
Scanner scanner = new Scanner(System.in);
scanner.nextInt();
// ClassNotFoundException,类没找到异常。
Class.forName("java.lang.String").getName();
} catch(Exception e) {
e.printStackTrace();
}
System.out.println("处理完异常下面代码正常运行");
}
}
输出:
java.lang.ArrayIndexOutOfBoundsException: Index 1 out of bounds for length 1
at exceptionTest.ExceptionCatch.main(ExceptionCatch.java:12)
处理完异常下面代码正常运行
使用 Exception 捕获后,在处理编译时异常时,确实不报错了,在实际运行过程中任然有可能再次出现异常。拿 Class.forName()
举例,你处理了,在实际中要是找不到这个类呢?还是一样爆运行异常。
使用 try-catch 处理异常时有几点注意事项:
try 代码块中的变量是局部变量,不能在外面使用。非要使用就在外面创建变量并初始化好值。
public class ExceptionCatch { public static void main(String[] args) { // Object str; 不赋值就是 null String[] str = null; try { str = new String[]{"str1", "str2"}; } catch(Exception e) { e.printStackTrace(); } // System.out.println(str); // 直接使用可能会没有赋值,所有产生未初始化的异常 System.out.println(str.length); } }
catch 异常类父类时,下面不能在 catch 异常子类,否则报错。再说也没意义,你都捕获异常父类了,当产生异常子类时肯定会捕获到。
public class ExceptionCatch { public static void main(String[] args) { // Object str; 不赋值就是 null String[] str = null; try { str = new String[]{"str1", "str2"}; } catch(Exception e) { e.printStackTrace(); } catch(RuntimeException e) { // 捕获异常父类后再捕获异常子类则报错。java: 已捕获到异常错误java.lang.RuntimeException e.printStackTrace(); } // System.out.println(str); // 直接使用可能会没有赋值,所有产生未初始化的异常 System.out.println(str.length); } }
finally
finally 不管什么情况,就算 catch 中再次出现异常情况,一定会在 catch 最后一行代码运行前执行最后才结束 try-catch。而 finally 一般用在释放 IO流、数据库连接等资源。
创建 FinallyTest.java:
try {
System.out.println(10 / 0);
} catch (ArithmeticException e) {
System.out.println(1);
return;
} finally {
System.out.println("finally 代码块一定会执行");
}
运行返回
1
finally 代码块一定会执行
try-catch-finally,catch 里 return 一个变量,在 finally 里修改 return 变量的值会失败。可以理解为要 return 的变量的值已经取出来,等 finally 执行完就返回,因此 finally 里去改已经没用了。
创建 FinallyTest2.java:
public static int finallyReturnVarTest() {
int tmpInt = 0;
try {
throw new RuntimeException();
} catch (RuntimeException e) {
return tmpInt;
} finally {
tmpInt = 2;
}
}
运行输出:
0
真实开发中运行时异常很少处理,遇到回去改代码就行,都只会处理编译时异常,不然代码都没法运行呐。
try-catch-resource🔨
Java 7 中的新语法。
省略 finally,自动关闭资源。
try(
资源一;
资源二;
资源三
) {
资源一.动作;
......
} catch {
捕获异常;
......
}
throws
异常另一种处理办法是自己不想处理甩出去让别人处理。throws 是不去 catch 捕获,有任何异常直接往调用方抛,由调用方 try-catch 处理,或者调用方不处理就继续 throws 往上抛。
语法是 throws Expression
,写在代码块左花括号左边:
public class ExceptionCatch {
public static void main(String[] args) {
try {
ExceptionCatch exceptionCatch = new ExceptionCatch();
exceptionCatch.func();
} catch (ClassNotFoundException e) {
System.out.println("类名未找到");
}
}
public void func() throws ArithmeticException, ClassNotFoundException {
Class.forName("java.lang.String1");
}
}
方法 func() 不会再报编译时错误,因为抛给调用者了。只要记住谁调用谁处理就行。
运行输出:
类名未找到
那运行时异常呢?也是一样往上抛不用在当前处理,交给调用者处理即可:
public class ExceptionCatch {
public static void main(String[] args) {
try {
ExceptionCatch exceptionCatch = new ExceptionCatch();
System.out.println(exceptionCatch.func1());
} catch (ArithmeticException e) {
System.out.println("除 0 错误");
}
}
public int func1() throws ArithmeticException {
return 10 / 0;
}
}
运行输出:
除 0 错误
抛父类和子类两个异常接的时候只 catch 父类异常可以吗?
public class ExceptionCatch {
public static void main(String[] args) {
try {
ExceptionCatch exceptionCatch = new ExceptionCatch();
System.out.println(exceptionCatch.func1());
} catch (Exception e) {
System.out.println("除 0 错误");
}
}
public int func1() throws ArithmeticException, Exception {
return 10 / 0;
}
}
运行输出:
除 0 错误
结果证明是没问题的。
throws 使用上有个注意事项,子类重写父类方法时,throws 的异常不能大于父类,这点也适用于抽象类和接口上。
public class ExceptionCatch {
public static void main(String[] args) {
new ExceptionCatch().func1();
}
public void func1() throws RuntimeException {
}
}
class SubClass extends ExceptionCatch {
// java: exceptionTest.SubClass中的func1()无法覆盖exceptionTest.ExceptionCatch中的func1()
// 被覆盖的方法未抛出 java.lang.Exception
public void func1() throws Exception {
super.func1();
}
}
这里父类 func1() 方法 throws RuntimeException,子类重写后则直接 throws Exception,而 Exception 是 RuntimeException 父类,这关系不能颠倒。当然子类 func1() 方法也可以不去抛出也是没问题的,不是说父类抛异常,子类继承/重写后就必须也要抛。
那父类这个异常什么时候抛呢?还以一样,谁通过这个类的实例调用它的 func1() 方法,谁就必须要处理,比如 new ExceptionCatch().func1()
,调用这个方法的地方就需要处理异常。
编译输出:
java: exceptionTest.SubClass中的func1()无法覆盖exceptionTest.ExceptionCatch中的func1()
被覆盖的方法未抛出java.lang.Exception
throw
throw 是手动抛异常,和 throws 不同,后者是往上报送异常,前者是直接手动生成一个异常对象抛出。但是抛你也不能瞎选异常,比如明明是索引超出范围那就应该抛 ArrayIndexOutOfBoundsException,而不是 IOException。
public class ExceptionCatch {
public static void main(String[] args) {
new ExceptionCatch().func1();
}
public void func1() {
throw new RuntimeException("手动生成异常信息");
}
}
运行输出:
Exception in thread "main" java.lang.RuntimeException: 手动生成异常信息
at exceptionTest.ExceptionCatch.func1(ExceptionCatch.java:9)
at exceptionTest.ExceptionCatch.main(ExceptionCatch.java:5)
可以发现错误信息是自定义的。
处理异常一方可以通过异常对象的 getMessage()
方法获得异常信息:
public class ExceptionCatch {
public static void main(String[] args) {
try {
new ExceptionCatch().func1();
} catch (RuntimeException e) {
System.out.println(e.getMessage());
}
}
public void func1() throws RuntimeException {
throw new RuntimeException("手动生成异常信息");
}
}
运行输出:
手动生成异常信息
custom exception
现有的异常类不能描述业务错误信息时,就需要自己创建个异常类。现有异常类是通过继承得来的,那么自己也能创建个类去继承,然后就成了自定义异常类了。继承时需要分清是 Exception 编译时异常还是运行时异常,类命名上也需要明确指明是什么异常。
创建 PrintStringCharException.java:
public class PrintStringCharException extends RuntimeException {
PrintStringCharException() {
super();
}
PrintStringCharException(String errorMsg) {
super(errorMsg);
}
//public PrintStringCharException(String message, Throwable cause) {
// cause 参数是说当前这个异常是哪个异常引起的,直接传入就行。
// super(message, cause);
//}
}
创建 ExceptionCatch.java:
public class ExceptionCatch {
public void func() throws PrintStringCharException {
throw new PrintStringCharException("字符串输出异常");
}
public static void main(String[] args) {
try {
new ExceptionCatch().func();
} catch (PrintStringCharException e) {
System.out.println(e.getMessage());
}
System.out.println("看起来一切都好");
}
}
运行输出:
字符串输出异常
看起来一切都好
这里使用以下方法创建异常:
- 使用 Alt+ Insert 生成 Constructor。
- 照着 Java 官方异常类抄。抄的时候要注意
static final long serialVersionUID
改个其他值,不能和其他异常类重复。此知识点见序列化一节。
这里创建了个运行时异常,具体使用什么异常自己决定。再真正使用是手动 throw 异常自己不处理最后抛出去,交给 main 方法 catch,最终运行没有爆错误。
最近更新:
发布时间: