异常
一、什么是异常
概念:程序在运行过程中出现的不正常现象。出现异常不处理将终止程序运行。
异常处理的必要性:任何程序都可能存在大量的未知问题、错误;如果不对这些问题进行正确的处理,则可能导致程序的中断,造成不必要的损失。
异常处理:java编程语言使用异常处理机制为程序提供了异常处理的能力。
二、异常的分类
Throwable:可抛出的,一切错误或异常的父类,位于java.lang
包中。
异常的两大类:
Error
:JVM、硬件、执行逻辑错误,不能手动处理。
StackOverflowError
:栈内存溢出
OutOfMemoryError
:内存溢出
Exception
:程序在运行和配置中产生的问题,可处理。
RuntimeException
:运行时异常,可处理,可不处理。
CheckedException
:检查时异常,必须处理。实际不存在此类,Exception
子类中除了RuntimeException
外其它子类都是检查时异常。
2.1 常见的运行时异常
类型 |
说明 |
NullPointerException |
空指针异常 |
ArrayIndexOutOfBoundsException |
数组越界异常 |
ClassCastException |
类型转换异常 |
NumberFormatException |
数字格式转换异常 |
ArithmeticException |
算术异常 |
RuntimeException
以及其所有子类都是运行时异常。RuntimeException
是Exception
异常的子类。
而Exception
类中除了RuntimeException
,其它子类都是检查时异常。
2.2 演示常见运行时异常
运行时异常:
1 2 3
| String name = null; System.out.println(name.equals("aaa"));
|
数组越界异常
1 2 3
| int[] arr = {10,20,30}; System.out.println(arr[3]);
|
类转换异常
1 2 3
| Object str = "hello"; Integer i = (Integer) str;
|
数字格式异常
1 2
| int n = Integer.parseInt("100a");
|
算术异常
2.3异常的产生与传递
1、异常的产生
当程序员在运行时遇到不符合规范的代码或结果时,会产生异常或程序员使用throw
关键字手动抛出异常。
1、自动抛出异常:当程序在运行时遇到不符合规范的代码或结果时,会产生异常。
2、手动抛出异常:程序员使用throw
抛出异常,语法throw new 异常类型(参数)
3、产生异常结果:相当于遇到return
语句,导致程序因异常而终止。
2、异常的传递
按照方法的调用链反向传递,如始终没有处理异常,最终会由JVM进行默认异常处理(打印堆栈跟踪信息)。
1、检查时异常(受检查异常):throws
声明异常,修饰在方法参数列表后端。
2、运行时异常(不受检查异常):因可处理可不处理,无需声明异常。
案例1:异常的产生
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class Test2 { public static void main(String[] args) {
Scanner scanner = new Scanner(System.in); System.out.println("请输入第一个数字:"); int n1 = scanner.nextInt(); System.out.println("请输入第二个数字"); int n2 = scanner.nextInt(); int result = n1 / n2; System.out.println("结果为:"+result); System.out.println("程序执行完毕"); } }
|
运行结果:
当输入15 3,程序正确计算出结果,并正常结束。
当输入15 0,当执行到int result = n1 / n2;
程序抛出异常,我们并没有处理,程序异常结束。
当输入 15 a,当执行到int n2 = scanner.nextInt();
程序抛出异常,异常结束。
案例2:异常的传递
将案例1的代码分为两个函数执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| public class Test3 { public static void main(String[] args) {
operation(); } public static void operation(){ divide(); } public static void divide(){ Scanner scanner = new Scanner(System.in); System.out.println("请输入第一个数字:"); int n1 = scanner.nextInt(); System.out.println("请输入第二个数字"); int n2 = scanner.nextInt(); int result = n1 / n2; System.out.println("结果为:"+result); System.out.println("程序执行完毕"); } }
|
运行结果:
当输入15 0时,可以看到抛出的异常是按照方法的调用顺序,反方向向上传递。divide函数交给operation,operation交给main,前面两个函数没有对异常做处理,最终由JVM进行默认异常处理,也就是打印运行结果中的堆栈跟踪信息。
当输入15 a时,
三、异常的处理
- Java的异常处理是通过5个关键字来实现的:
- try:执行可能产生异常的代码
- catch:捕获异常,并处理
- finally:无论是否发生异常,代码总能执行
- throw:手动抛出异常
- throws:声明方法可能要抛出的各种异常
语法:
1 2 3 4 5 6 7
| try{ }catch(Exception e){ }finally{ }
|
3.1 异常处理 try…catch
1 2 3 4 5
| try{ }catch(Exception e){
}
|
会发生三种情况:
- 正常,没有发生异常
- 出现异常并处理
- 异常类型与捕获的异常不匹配
案例1:try…catch的使用
将可能发生异常的语句书写在try
当中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public class Test4 { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); System.out.println("请输入第一个数字:"); int result = 0; try { int n1 = scanner.nextInt(); System.out.println("请输入第二个数字"); int n2 = scanner.nextInt(); result = n1 / n2; } catch (Exception e) { e.printStackTrace(); } System.out.println("结果为:"+result); System.out.println("程序执行完毕"); } }
|
没有发生异常,与没写try…catch效果一致。
虽然发生了异常,但是程序执行完毕
正常输出,程序正常退出。
案例2:try…catch捕获的异常与实际发生的不符
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public class Test5 { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); System.out.println("请输入第一个数字:"); int result = 0; try { int n1 = scanner.nextInt(); System.out.println("请输入第二个数字"); int n2 = scanner.nextInt(); result = n1 / n2; } catch (NullPointerException e) { e.printStackTrace(); } System.out.println("结果为:"+result); System.out.println("程序执行完毕"); } }
|
运行结果:
发生了异常,不是空指针异常,未被捕获,程序异常退出。
案例3:使用自定义打印异常信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public class Test6 { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); System.out.println("请输入第一个数字:"); int result = 0; try { int n1 = scanner.nextInt(); System.out.println("请输入第二个数字"); int n2 = scanner.nextInt(); result = n1 / n2; } catch (Exception e) { System.out.println("异常信息:"+e.getMessage()); } System.out.println("结果为:"+result); System.out.println("程序执行完毕"); } }
|
运行结果:
没有打印红红的堆栈信息了。
3.2 异常处理try…catch…finally
语法:
1 2 3 4 5 6 7
| try{ }catch{ }finally{ }
|
1、finally
内代码是否发生异常都会执行,可以用于释放资源。
2、finally
内代码不执行的唯一情况,就是在此之前退出了java虚拟机。
案例1:try…catch…finally的使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public class Test7 { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); System.out.println("请输入第一个数字:"); int result = 0; try { int n1 = scanner.nextInt(); System.out.println("请输入第二个数字"); int n2 = scanner.nextInt(); result = n1 / n2; } catch (Exception e) { System.out.println("异常信息:"+e.getMessage()); }finally { System.out.println("释放资源..."); } System.out.println("结果为:"+result); System.out.println("程序执行完毕"); } }
|
运行结果:
1、发生异常,执行了释放资源...
2、未发生异常,执行了释放资源...
案例2:未正确捕获到异常,finally仍会执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public class Test8 { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); System.out.println("请输入第一个数字:"); int result = 0; try { int n1 = scanner.nextInt(); System.out.println("请输入第二个数字"); int n2 = scanner.nextInt(); result = n1 / n2; } catch (NullPointerException e) { System.out.println("异常信息:"+e.getMessage()); }finally { System.out.println("释放资源..."); } System.out.println("结果为:"+result); System.out.println("程序执行完毕"); } }
|
运行结果:
案例3:finally不会执行的情况。
手动退出虚拟机。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| public class Test8 { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); System.out.println("请输入第一个数字:"); int result = 0; try { int n1 = scanner.nextInt(); System.out.println("请输入第二个数字"); int n2 = scanner.nextInt(); result = n1 / n2; System.exit(0); } catch (Exception e) { System.out.println("异常信息:"+e.getMessage()); }finally { System.out.println("释放资源..."); } System.out.println("结果为:"+result); System.out.println("程序执行完毕"); } }
|
运行结果:
3.3 异常处理 多重catch
语法:
1 2 3 4 5 6 7 8 9
| try{ }catch(异常类型1){ }catch(异常类型2){ }catch(异常类型3){ }
|
1、子类异常在前,父类异常在后;否则父类会将子类匹配到,子类永远无法匹配到。
2、发生异常时,按顺序逐个匹配。
3、只执行第一个与异常类型匹配的catch
语句。
4、finally
根据需要可写可不写。
案例1:多重catch的使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| public class Test10 { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); System.out.println("请输入第一个数字:"); int result = 0; String str = null; try { int n1 = scanner.nextInt(); System.out.println("请输入第二个数字"); int n2 = scanner.nextInt(); result = n1 / n2; if (n1 != 1){ str = "abc"; } str.equals("a"); } catch (ArithmeticException e){ System.out.println("算术异常..."); } catch (InputMismatchException e){ System.out.println("输入不匹配异常..."); } catch (NullPointerException e){ System.out.println("未知异常..."); }catch (Exception e) { System.out.println("异常信息:"+e.getMessage()); }finally { System.out.println("释放资源..."); } System.out.println("结果为:"+result); System.out.println("程序执行完毕"); } }
|
运行结果:
1、算术异常
2、输入不匹配异常
3、未知异常
3.4 异常处理try…finally
try...finally
不能捕获异常,仅仅用来当发生异常时,用来释放资源。
- 一般用在底层代码,只释放资源不做异常处理,把异常向上抛出。
语法:
案例1:异常向上抛出给了main
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public class Test11 { public static void main(String[] args) { divide(); } public static void divide(){ Scanner scanner = new Scanner(System.in); System.out.println("请输入第一个数字:"); int result = 0; String str = null; try { int n1 = scanner.nextInt(); System.out.println("请输入第二个数字"); int n2 = scanner.nextInt(); result = n1 / n2; } finally { System.out.println("释放资源..."); } System.out.println("结果为:"+result); System.out.println("程序执行完毕"); } }
|
运行结果:
改进:在main
中捕获异常。
1 2 3 4 5 6 7 8 9 10
| ... public static void main(String[] args) { try { divide(); } catch (Exception e) { System.out.println(e.getMessage()); } } ...
|
运行结果:捕获到异常,正确退出。
四、声明异常
如果在一个方法体中抛出了异常,如何通知调用者呢?
throws关键字:声明异常。
使用原则:底层代码向上声明或者抛出异常,最上层一定要处理异常,否则程序中断。
语法:在方法名后使用throws
关键字声明异常。
作用:告诉表示此处可能会出现异常,告诉调用者要处理。
可抛出多个异常:
1 2
| public void test() throws Exception,NullPointerException, InputMismatchException { }
|
案例1:使用throws声明异常。
运行结果:
必须对其捕获或向上声明
案例2:案例1捕获异常
案例3:案例1向上声明
这个相当于最终异常还是抛给了java虚拟机处理。
五、抛出异常
除了系统自动抛出异常外,有些问题需要程序员自行抛出异常。
throw
关键字:抛出异常。
语法:throw 异常对象
案例1:Person类设置年龄,不符合要求则抛出异常
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| public class Test1 { public static void main(String[] args) { Person p1 = new Person(); p1.setName("小明"); p1.setAge(130); System.out.println(p1); } } class Person { private String name; private int age;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { if (age >= 0 && age <= 120){ this.age = age; }else{ throw new RuntimeException("年龄不符合要求"); } }
@Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + '}'; } }
|
运行结果:
设置年龄130,不符合要求,抛出了运行时异常,提示信息为:年龄不符合要求。
案例2:抛出检查时异常
当抛出检查时异常时,这时候需要处理异常,我们可以在这里使用try...catch
捕获异常或throws
声明异常。
使用try...catch
捕获异常
使用throws
声明异常
如果不处理则报错,
六、自定义异常
对于上面两个案例中,抛出的是RuntimeException
和Exception
,我们无法通过抛出的类名见名知意。
自定义异常:声明继承Exception或其子类的类。
AgeException.java
继承Exception或其子类都可以,只需要写构造方法即可,不需要其他的代码,自定义异常的目的就是更改抛出异常类的名字。
1 2 3 4 5 6 7 8
| public class AgeException extends Exception{ public AgeException() { }
public AgeException(String message) { super(message); } }
|
Person.java
setAge方法
1 2 3 4 5 6 7
| public void setAge(int age) throws Exception{ if (age >= 0 && age <= 120){ this.age = age; }else{ throw new AgeException("年龄不符合要求"); } }
|
运行结果:
这样我们就能够见名知意。
七、重写声明异常的方法
带有异常声明的方法重写:
- 子类不能抛出比父类更多、更宽泛的检查时异常。
案例1:
不能抛出比父类更宽泛的检查时异常。
1 2 3 4 5 6 7 8 9 10 11
| public class Animal { public void eat() throws FileNotFoundException{ System.out.println("父类eat..."); } } class Dog extends Animal{ @Override public void eat() throws Exception { System.out.println("子类eat..."); } }
|
运行结果:
案例2:子类可以抛出父类的子类检查时异常
1 2 3 4 5 6 7 8 9 10 11
| public class Animal { public void eat() throws Exception{ System.out.println("父类eat..."); } } class Dog extends Animal{ @Override public void eat() throws FileNotFoundException{ System.out.println("子类eat..."); } }
|
案例3:对于运行时异常,不存在以上规则
1 2 3 4 5 6 7 8 9 10 11
| public class Animal { public void eat() throws NumberFormatException{ System.out.println("父类eat..."); } } class Dog extends Animal{ @Override public void eat() throws RuntimeException{ System.out.println("子类eat..."); } }
|
注意:对于运行时异常没有上面的规则,而对于检查时异常,子类方法不能抛出比父类更更多、更宽泛的异常。