如果没有使用异常处理机制,当抛出异常后,程序就退出了,下面的代码就不再执行。这样就导致一个不算致命的问题就会引起整个系统的崩溃,这使得程序的健壮性很差。因此,Java的设计者提供了一个 异常处理机制 来解决这个问题。
如果程序员认为一段代码可能出现异常/问题,可以使用 try-catch 异常处理机制来解决,将该代码块选中 -> 快捷键 ctrl + alt + t -> 选中 try-catch。这样即使出现异常,程序也会继续执行。
异常
基本概念
Java语言中,将程序执行中发生的不正常情况称为“异常”。(开发过程中的语法错误和逻辑错误不是异常)
Error(错误)
Java虚拟机无法解决的严重问题,如:JVM系统内部错误、资源耗尽(StackOverflowError栈溢出,out of memory内存溢出),Error是严重错误,程序会崩溃。
Exception(异常)
其它因编程错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理。例如空指针访问,试图读取不存在的文件,网络连接中断等等。Exception分为两大类:运行时异常(程序运行时发生的异常)和编译时异常(编程时,编译器检查出的异常)。
异常体系图
1. 异常分为两大类,运行时异常和编译时异常。
2. 运行时异常,编译器检查不出来。一般是指编程时的逻辑错误,是程序员应该避免其出现的异常。java.lang.RuntimeException类及它的子类都是运行时异常。
3. 对于运行时异常,可以不做处理,因为这类异常很普遍,若全处理可能会对程序的可读性和运行效率产生影响。
4. 编译时异常,是编译器要求 必须处置的异常。
常见的运行时异常
1. NullPointerException 空指针异常
当应用程序试图在需要对象的地方使用 null 时,抛出该异常。
public static void main(String[] args) {
String a = null;
System.out.println(a.length()); //抛出异常
}
2. ArithmeticException 数学运算异常
当出现异常的运算条件时,抛出此异常,比如一个整数除以零。
public static void main(String[] args) {
int a = 10;
int b = 0;
System.out.println(a/b);
}
3. ArrayIndexOutOfBoundsException 数组下标越界异常
用非法索引访问数组时抛出的异常。如果索引为负或大于等于数组大小,则该索引为非法索引。
public static void main(String[] args) {
int[] a = {1,2,3,4};
System.out.println(a[4]); //越界
}
4. ClassCastException 类型转换异常
试图将对象强制转换为不是实例的子类时,抛出该异常。
public class Test {
public static void main(String[] args) {
A a = new B();
B b = (B)a; // 向下转型,没问题
C c = (C)a; // a实际上是一个B类,而B类和C类没有关系
}
}
class A{}
class B extends A{}
class C extends A{}
5. NumberFormatException 数字格式不正确异常
当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常—— 使用该异常可以确保输入的是满足条件的数字。
public static void main(String[] args) {
String name = "青眼白龙";
System.out.println(Integer.parseInt(name));
}
常见的编译时异常
异常处理
异常处理就是当异常发生时,对异常的处理方式。有两种:1. try-catch-finally,程序员在代码中捕获发生的异常,自行处理。 2. throws,将发生的异常抛出,交给调用者来处理,最顶级的处理者就是JVM。
对于编译异常,程序中必须处理,比如 try-catch 或者 throws。对于运行异常,程序中如果没有处理,默认就是throws的方式处理。
1. try-catch-finally
注意事项:
1. 如果异常发生了,则异常发生后面的try代码块不会执行,直接进入到 catch 块。
public static void main(String[] args) {
try{
String a = "青眼白龙";
int b = Integer.parseInt(a);
System.out.println("异常后面的代码"); //这行直接不执行了
}catch(Exception e){
System.out.println("出现异常"); // 输出 出现异常
}
}
2. 如果异常没有发生,则顺序执行try的代码块,不会进入到catch。
3. 如果希望不管是否发生异常,都执行某段代码(比如关闭连接,释放资源等),则使用finally 语句(finally 可以省略)。
4. 可以有多个 catch 语句,捕获不同的异常(进行不同的业务处理),要求父类异常在后,子类异常在前。如果发生异常,只会匹配一个catch。
public static void main(String[] args) {
try {
Person person = new Person();
person = null;
System.out.println(person.getClass()); // NullPointerException
int n1 = 10;
int n2 = 0;
int res = n1 / n2; // ArithmeticException
} catch (NullPointerException e) { // 单独指定的异常,在前
System.out.println("空指针异常");
}catch (ArithmeticException e){ // 单独指定的异常
System.out.println("算术异常");
}catch (Exception e){ // 父类异常
System.out.println("其他异常");
}
}
5. 可以进行 try - finally 配合使用,这种用法相当于没有捕获异常,因此程序会直接崩掉。应用场景就是执行一段代码,不管是否发生异常,都必须执行某个业务逻辑。
6. 如果 catch 里有 return 语句,执行到 return 时不会立刻执行,而是先去执行finally(因为执行完return程序就结束了,而finally必须执行),如果finally没有return 语句,最后执行catch的return,如果有则执行finally的return,然后程序结束(不执行catch的return),throw语句也是同理。
catch{
...
return ++i; // 这里会用一个temp临时存储i的值,不管finally怎么修改i,最终还是返回temp
}
参考P451
2. throws
1. 如果一个方法可能产生某种异常,但是并不能确定如何处理这种异常,则此方法应显示地声明抛出异常,表明该方法将不对这些异常进行处理,而由该方法的的调用者负责处理。
2. throws语句可以抛出多个异常,可以是方法中产生的异常类型,也可以是它的父类。
class A{
public void f1() throws FileNotFoundException,Exception,ArithmeticException{}
}
3. 子类重写父类的方法时,所抛出的异常类型要么与父类一致,要么是父类抛出的异常类型的子类型。
class A{
public void f1() throws FileNotFoundException{}
}
class B extends A{
@Override
public void f1() throws FileNotFoundException { //相同或者为子类型
super.f1();
}
}
4. 在throws过程中,如果有方法 try-catch,就相当于处理异常,可以不必throws
public void f1() {
f2(); //报错
}
public void f2() throws FileNotFoundException{} //抛出一个编译异常
f2抛出了一个编译异常,这是必须处理的,由于调用者是f1,因此f1需要throws这个异常或者使用 try-catch 语句处理。
public void f1() { // 第一种方法
try {
f2();
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
public void f1() throws FileNotFoundException{ // 第二种方法
f2();
}
public void f1() {
f2(); //正确
}
public void f2() throws NullPointerException{}
这次f2抛出了一个运行异常,而运行异常并不要求程序员显示处理,因为有默认处理机制,因此不会报错。
自定义异常
当程序中出现了某些“错误”,但该错误信息并没有在Throwable子类中描述处理,这个时候可以自己设计异常类,用于描述该错误信息。
步骤
1. 定义类:自定义异常类名,继承Exception或RuntimeException
2. 如果继承Exception,属于编译异常。
3. 如果继承RuntimeException,属于运行异常(一般来说,继承RuntimeException,好处是可以使用默认处理机制,要不然还得在调用方法里加throws)
class AgeException extends RuntimeException{
public AgeException(String message) { //构造器
super(message);
}
}
public class Test {
public static void main(String[] args) {
Scanner a = new Scanner(System.in);
int b = a.nextInt();
if(b<18){
throw new AgeException("未成年"); //抛出异常
}
}
}
// throw new ArrayIndexOutOfBoundsException("数组过大")
// 也可以在已有的类型中写参数表示异常信息
throw和throws
接下来分析一段代码:
public static void main(String[] args) {
try {
throw new AgeException("未成年");
} catch(Exception e){
System.out.println("catch方法");
throw new AgeException("未成年");
} finally{
System.out.println("finally方法");
// return;
}
}
try里throw的异常被catch接收(因此不会输出异常),然后输出"catch方法",紧接着catch方法又throw了一个异常,但是因为如果执行throw的话,程序就终止了,而finally必须执行,因此先不执行throw方法,执行finally,输出"finally方法",然后回到throw方法,输出异常。
public static void main(String[] args) {
try {
throw new AgeException("未成年");
} catch(Exception e){
System.out.println("catch方法");
throw new AgeException("未成年");
} finally{
System.out.println("finally方法");
return; // 加上了return
}
}
执行到catch里的throw时,调用finally方法,然后由于finally里有return语句,因此程序结束,直接不输出异常了。 (如果catch里没有return和throw,那么按顺序执行)P458 示例
综合练习 P495