一、异常的概念
1.定义(什么是异常?)
Java在运行过程中,程序突然中断。软件开发过程中,很多情况都会导致异常的产生,例如对负数开平方根、对字符串做算术运算、操作数超出范围、数组下标越界等。
2.异常简单例子
(1)除数为零的例子
public class Example6_1 {
public static void main(String[] args){
int a= 0;
System.out.println(5/a);
}
}
输出结果:由于除数不能为0,所以程序运行时出现了除以0溢出的异常事件。
Exception in thread "main" java.lang.ArithmeticException: / by zero
at com.chapter6.Example6_1.main(Example6_1.java:11)
(2)类型转换错误
public class Example6_2 {
public static void main(String[] args){
String str = "jack";
System.out.println(str+"年龄是:");
String s = "20L";
int age = Integer.parseInt(s);
System.out.println(age);
}
}
输出结果为:报出NumberFormatException (数字格式异常),程序提示了消息,可见提示的消息代码正常执行,而变量age没有执行,故s转换为age的时候程序异常了。
jack年龄是:
Exception in thread "main" java.lang.NumberFormatException: For input string: "20L"
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.lang.Integer.parseInt(Integer.java:580)
at java.lang.Integer.parseInt(Integer.java:615)
at com.chapter6.Example6_2.main(Example6_2.java:13)
上面的两个程序抛出了两种异常,这两种异常是一个个的异常对象,异常对象的产生取决于产生异常的类型。此时的异常可以概括为:
异常是程序由于各种原因导致无法正常运行的一个事件,这个事件是由一个异常对象来代表的,这个对象的产生取决于产生异常的类型,可能由应用程序本身产生,也可能由Java虚拟机产生。
二、异常类的层次
1.异常类的层次
2.异常事件类型
异常类都是内置类Throwable的子类。Throwable类有两个子类:Error(错误)和Exception(异常)
- Error(错误):通常是灾难性的致命错误,不是程序(程序猿)可以控制的,如内存耗尽、JVM系统错误、堆栈溢出等。应用程序不应该去处理此类错误,且程序员不应该实现任何Error类的子类。
- Exception(异常):用户可能捕获的异常情况,可以使用针对性的代码进行处理,如:空指针异常、网络连接中断、数组下标越界等。
3.Exception(异常)分类
Exception(异常)又分为两类:运行时异常和编译时异常。
(1)运行时异常
RuntimeException 为Java虚拟机在运行时自动生成的异常,如被零除和非法索引、操作数超过数组范围、打开文件不存在等。此类异常的出现绝大数情况是代码本身有问题应该从逻辑上去解决并改进代码。
RuntimeException 类及其子类称为非检查型异常,Java编译器会自动按照异常产生的原因引发相应类型的异常,程序中可以选择捕获处理也可以不处理,虽然Java编译器不会检查运行时异常,但是也可以去进行捕获和抛出处理。RuntimeException类和子类以及Error类都是非受检异常。
(2)编译时异常
Exception中除RuntimeException 及其子类之外的异常,该异常必须手动在代码中添加捕获语句来处理该异常。编译时异常也称为受检异常,一般不进行自定义检查异常。
4.常见的异常类
异常类 | 说明 |
---|
ClassCastException | 类型转换异常 | ArrayIndexOutOfBoundsException | 数组越界异常 | NegativeArraySizeException | 指定数组维数为负值异常 | ArithmeticException | 算数异常 | InternalException | Java系统内部异常 | NullPointerException | 空指针异常 | IllegalAccessException | 类定义不明确所产生的异常 | IOException | 一般情况下不能完成I/O操作产生的异常 | EOFException | 打开文件没有数据可以读取的异常 | FileNotFoundException | 在文件系统中找不到文件路径或文件名称时的异常 | ClassNotFoundException | 找不到类或接口所产生的异常 | CloneNotSupportedException | 使用对象的clone方法但无法执行Cloneable所产生的异常 |
5.异常类的常用方法
- getMessage():返回Throwable对象的详细信息,如果该对象没有详细信息则返回null
- getLocalizedMessage() :返回Throwable的本地化描述,子类可能会覆盖该方法以便产生一个特定于本地的消息,对于未覆盖该方法的子类,默认返回调用getMessage()的结果
- printStackTrace():将Throwable和它的跟踪情况打印到标准错误流
- toString():返回Throwable对象的类型与性质
三、Java异常处理过程
1.什么叫异常处理机制?
- 定义:前面定义过:异常是程序由于各种原因导致无法正常运行的一个事件,这个事件是由一个异常对象来代表的,这个对象的产生取决于产生异常的类型,可能由应用程序本身产生,也可能由Java虚拟机产生。异常事件是 Throwable 类或其子类的实例(异常对象),异常处理机制就是当一个方法出现异常后便抛出一个异常对象,该对象中包含有异常信息,调用这个对象的方法可以捕获到这个异常并可以对其进行处理,异常处理机制并不能真正的修复程序的bug,只能说是防止程序崩溃,要修复程序的bug时需要我们在catch语句中对异常进行一些处理。
- 限制:Exception 中除 RuntimeException 及其子类之外的异常一般需要进行异常处理
2.Java异常关键字
Java 的异常处理是通过 5 个关键词来实现的:try、catch、throw、throws 和 finally
? try – 用于监听。将要被监听的代码(可能抛出异常的代码)放在try语句块之内,当try语句块内发生异常时,异常就被抛出。 ? catch – 用于捕获异常。catch用来捕获try语句块中发生的异常。 ? finally – finally语句块总是会被执行。 ? throw – 用于抛出异常。 ? throws – 用在方法签名中,用于声明该方法可能抛出的异常。
3.异常处理机制类型
- 在Java应用中,异常的处理机制分为声明异常,抛出异常和捕获异常。
- 异常处理机制类型与异常关键字的对应关系
4.Java异常处理过程——捕获异常catch
(1)定义
异常对象被系统沿着方法的调用栈逐层回溯,交给能够匹配这种异常对象的异常引用,接着对异常对象进行处理,这一过程就是捕获(catch)异常。
(2)结构语法
try {
}
catch (ExceptionType1 e){
}
catch (ExceptionType2 e){
}
...
final{
}
try 语句中存放的是可能发生异常的语句。当异常抛出时,异常处理机制负责搜寻参数与异常类型相匹配的第一个处理程序,然后进入catch语句中执行,此时认为异常得到了处理。如果程序块里面的内容很多,前面的代码抛出了异常,则后面的正常程序将不会执行,系统直接catch捕获异常并且处理
catch 语句可以有多个,用来匹配多个异常,捕获异常的顺序与catch语句的顺序有关,当捕获到对应的异常对象时,剩下的catch语句不再进行匹配,因此在安排catch语句的顺序时,首先应该捕获最特殊的异常,然后一般化。catch的类型是Java语言定义的或者程序员自己定义的,表示抛出异常的类型。异常的变量名表示抛出异常的对象的引用,如果catch捕获并匹配了该异常,那么就可以直接用这个异常变量名来指向所匹配的异常,并且在catch语句中直接引用。部分系统生成的异常在Java运行时自动抛出,也可通过throws关键字声明该方法要抛出的异常,然后在方法内抛出异常对象。
final 语句为异常提供一个统一的出口,一般情况下程序始终都要执行final语句,final在程序中可选。final一般是用来关闭已打开的文件和释放其他系统资源。try-catch-final 可以嵌套。有4种特殊情况,finally块不会被执行:finally语句块中发生了异常,前面的代码中执行了System.exit()退出程序,程序中所在的线程死亡,关闭CPU
(3)举例
class Scratch {
public static void main(String[] args) {
try {
int i =Integer.parseInt(args[0]);
int a = 10/i;
System.out.println("a="+a);
}
catch (ArrayIndexOutOfBoundsException e){
System.out.println(e);
}finally {
System.out.println("finally block");
}
}
}
程序输出结果为:
java.lang.ArrayIndexOutOfBoundsException: 0
finally block
输出信息表明程序中捕获到了一个ArrayIndexOutOfBoundsException类的运行时的异常,是由于main的参数args[]字符串数组没有接收到任何输入造成的。
右击程序包,选中Run As->Run Configurations,打开Run Configurations界面,在Arguments一栏填充数据“2”,再次运行,运行结果为:
a=5
finally block
重新输入参数“0”,再次以运行此程序,程序输出结果为:抛出ArithmeticException异常,ArrayIndexOutOfBoundsException异常并没有被捕获。
finally block
Exception in thread "main" java.lang.ArithmeticException: / by zero
at com.chapter6.Example6_1.main(Example6_1.java:20)
可以将程序改为:
public static void main(String[] args) {
try {
int i =Integer.parseInt(args[0]);
int a = 10/i;
System.out.println("a="+a);
}
catch (ArrayIndexOutOfBoundsException e){
System.out.println(e);
}
catch (ArithmeticException e){
System.out.println(e);
}
finally {
System.out.println("finally block");
}
}
运行结果为:抛出ArithmeticException
java.lang.ArithmeticException: / by zero
finally block
public class Example6_1 {
public static void main(String[] args) {
try {
int i =Integer.parseInt(args[0]);
int a = 10/i;
System.out.println("a="+a);
}
catch (ArrayIndexOutOfBoundsException e){
System.out.println("没有输入数字串");
}
catch (ArithmeticException e){
System.out.println("分母不能为0");
}
catch (NumberFormatException e){
System.out.println("输入格式不正确");
}
catch (Exception e){
System.out.println("异常"+e);
}
}
}
考虑到该程序运行时可能产生的异常有:当没有任何输入时,会捕获到ArrayIndexOutOfBoundsException 异常;当输入值为0时,会捕获到ArithmeticException 异常,当输入格式不是整数时,会捕获NumberFormatException异常。
通常,为捕获到所有可能出现的异常,可以在处理异常的末尾,加上Exception 类,这样即可以使所有异常都被捕捉到,也可以防止想捕获具体异常时被它提前捕获。
public class Example6_1 {
public static void main(String[] args) {
try {
int i = Integer.parseInt(args[0]);
int a = 10 / i;
System.out.println("a=" + a);
} catch (ArrayIndexOutOfBoundsException | NumberFormatException e) {
System.out.println("没有输入数字串或输入格式不正确");
} catch (ArithmeticException e) {
System.out.println("分母不能为0");
} catch (Exception e) {
System.out.println("运行异常" + e);
} finally {
System.out.println("finally block");
}
}
}
运行程序,输出结果为:可以看出finally子句总能被执行。
有输入数字串或输入格式不正确
finally block
(4)关于return和finally的关系
public class Example6_2 {
public static void show(int i) {
try {
if (i == 1) {
System.out.println("one");
return;
}
if (i == 2) {
System.out.println("two");
}
return;
}catch (Exception e){
System.out.println(e);
}finally {
System.out.println("finally block");
}
}
public static void main(String[] args) {
for (int i=1;i<3;i++){
show(i);
}
}
}
输出结果为:
one
finally block
two
finally block
从程序运行结果可以看出,无论在什么位置添加return ,finally 子句都会被执行。
public class Example6_6 {
public static int add(int a, int b) {
try {
System.out.println(2 / 0);
return a + b;
} catch (Exception e) {
System.out.println("catch语句模块");
return 1;
} finally {
System.out.println("finally语句模块");
}
}
public static void main(String[] args) {
System.out.println(add(1, 2));
}
}
输出结果为: catch语句模块
finally语句模块
1
当try中抛出异常且catch中有return语句,finally中没有return语句,java先执行catch中非return语句,再执行finally语句,最后执行return语句。若try中没有抛出异常,则程序不会执行catch体里面的语句,java先执行try中非return语句,再执行finally语句,最后再执行try中的return语句
public class Example6_6 {
public static int add(int a,int b){
try {
System.out.println(2/0);
return a+b;
}catch (Exception e){
System.out.println("catch语句模块");
return 5;
}finally {
System.out.println("finally语句模块");
return 2;
}
}
public static void main(String[] args) {
System.out.println(add(1,2));
}
}
输出结果为: catch语句模块
finally语句模块
2
finally中有return时,会覆盖掉try和catch中的return
- 例4:finally中没有return语句,但是改变了返回值
先来看第一个例子 public class Example6_7 {
public static int set() {
int a = 1;
try {
return a;
} catch (Exception e) {
System.out.println("catch语句");
} finally {
a = 200;
}
return a;
}
public static void main(String[] args) {
System.out.println("a="+set());
}
}
可以先猜猜输出的结果是什么,接着来看第2个例子: public class Example6_7 {
public int a;
public Example6_7 set() {
try {
this.a=10;
} catch (Exception e) {
System.out.println("catch语句");
} finally {
System.out.println("finally语句块");
this.a=200;
}
return this;
}
public static void main(String[] args) {
Example6_7 t=new Example6_7();
t.set();
System.out.println(t.a);
}
}
可以看到两段代码运行后的结果如下: a=1
finally语句块
200
如果return数据保留的i基本数据类型或文本字符串,则在finally中对该基本数据的改变不起作用,try中的return语句依然会返回进入finally块中之前保存的值;如果时引用类型,则finally中的语句会起作用,try中改变的值就是在finally中改变后该属性的值。 (5)使用场景 针对需要如何处理的异常,采用捕获的方式去处理异常。 5.Java异常处理过程——声明异常throws (1)定义
- 如果一个方法可能会出现异常,但没有能力处理这种异常,可以在方法声明处用throws子句来声明抛出异常。用它修饰的方法向调用者表明该方法可能会抛出异常(可以是一种类型,也可以是多种类型,用逗号隔开)(位置: 写在方法名 或方法名列表之后 ,在方法体之前。)
(2)结构语法 static void pop() throws Exception1,Exception2,Exception3{
}
(3)throws 与throw 的区别
- throw 在方法体内使用,throws 函数名后或者参数列表后方法体前
- 意义 : throw 强调动作,而throws 表示一种倾向、可能但不一定实际发生
- throws 后面跟的是异常类,可以一个,可以多个,多个用逗号隔开。throw 后跟的是异常对象。
(4)举例
public class Example6_3 {
static void pop() throws NegativeArraySizeException{
int[] arr = new int[-5];
}
public static void main(String[] args){
try {
pop();
}catch (NegativeArraySizeException e){
System.out.println("pop()方法抛出的异常");
}
}
}
使用throws 关键字将异常抛给调用者后,如果调用者不想处理该异常,可以继续向上抛出,但最终要有能够处理该异常的调用者。pop方法中没有处理异常NegativeArraySizeException,而是main函数来处理。 (5)使用场景
- 非检查异常(运行时异常)可以不使用throws关键字来声明要抛出的异常,编译也可顺利通过,但在运行时会被系统抛出。
- 受检异常(编译时异常),必须使用try-catch/throws处理,否则会导致编译错误。
- 仅当抛出了异常,该方法的调用者才必须处理或重新抛出该异常。当方法的调用者无力处理该异常时,应该继续抛出而不是直接调用方法。
- 调用方法必须遵循一个原则:若覆盖一个方法,则不能声明与覆盖方法不同的异常,声明的任何异常必须是被覆盖方法所声明异常的同类或子类。如下例子:
public class Example6_3 {
void method1() throws IOException {
}
void method2() {
method1();
}
void method3() throws IOError {
method1();
}
void method3() throws IOException {
method1();
}
void method4() throws Exception {
method1();
}
void method5() {
try {
method1();
}catch (IOException e){
}
}
void method6() {
try {
method1();
}catch (IOException e){
throw new Exception();
}
}
void method7() throws Exception {
try {
method1();
}catch (IOException e){
throw new Exception();
}
}
}
6.Java异常处理过程——抛出异常throw (1)定义 Java程序的当前方法或自身不去处理异常,选择在方法内部 使用throw 抛出一个Throwable类型的异常。 (2)结构语法
- 关键字是
throw ,throw语句抛出的是异常类对象,因此需要new 关键字创建这一异常实例,而且只能抛出一个异常实例。 throw 在方法体中,程序会在throw语句后立即终止,它后面的语句执行不到,然后在包含它的所有try块中(可能在上层调用函数中)从里到外寻找含有与其匹配的catch子句的try块。- new 的异常类名称必须是Throwable或其子类
throw new 异常类名称([描述信息])
(3)举例
public class Example6_5 {
public static void main(String[] args) {
int a = 6;
int b = 0;
try {
System.out.println(a / b);
throw new ArithmeticException();
}catch (ArithmeticException e){
System.out.println("除数不能为0");
}
}
}
运行结果: 除数不能为0
上面的例子中,b=0,引发了ArithmeticException异常,因此创建了ArithmeticException对象,并由throw语句将异常抛给Java运行时系统,系统寻找匹配的异常处理器catch并运行相应的异常处理代码,打印输出”除数不能为0“。try-catch语句结束。 实际上,除数为0等于ArithmeticException,是RuntimeException的子类,运行时异常将由系统自动抛出,不需要使用throw语句,上面的代码等价于如下代码: public class Example6_5 {
public static void main(String[] args) {
int a = 6;
int b = 0;
try {
System.out.println(a / b);
}catch (ArithmeticException e){
System.out.println("除数不能为0");
}
}
}
public class Example6_5 {
public static boolean main() throws ArithmeticException {
int a = 6;
int b = 0;
try {
if (b == 0) {
throw new ArithmeticException();
}
System.out.println(a / b);
}catch (ArithmeticException e){
System.out.println("除数不能为0");
throw e;
}finally {
return false;
}
}
public static void main(String[] args) {
try {
System.out.println(main());
}catch (Exception e){
System.out.println("捕获");
}
}
}
程序运行的结果为: 除数不能为0
false
可以看到finally语句块中的return会覆盖掉catch里面的throw抛出的异常;
public class Example6_5 {
public static boolean main() throws ArithmeticException {
int a = 6;
int b = 0;
try {
if (b == 0) {
throw new ArithmeticException();
}
System.out.println(a / b);
}catch (ArithmeticException e){
System.out.println("除数不能为0");
return true;
}finally {
throw new ArithmeticException();
}
}
public static void main(String[] args) {
try {
System.out.println(main());
}catch (Exception e){
System.out.println("捕获");
}
}
}
可以猜测一下程序的运行结果: 除数不能为0
捕获
可以看到同样finally里面的throw会覆盖catch里面的return (4)使用场景
- 针对不知道如何处理的异常,采用抛出的方式去处理。
- throw可抛出系统自定义异常,通常情况下,是用来抛出用户自定义的异常。
- throw后面不能跟其它代码块,否则编译不能通过,但是可以在finally语句块中有return语句,finally语句可以让throw和return共存
try {
System.out.println("除数不能为0")
}catch (ArithmeticException e){
throw new ArithmeticException();
System.out.println(a / b);
}finally {
return false;
}
return true;
四、自定义异常 1.定义 继承Throwable或者他的子类Exception的用户自己定义的异常类。前面的内容提到的都是系统有的异常类。 2.在程序中使用自定义异常的步骤
- 创建自定义异常类
- 在方法中通过throw抛出异常对象
- 如果在当前方法中对抛出的异常对象作处理,可以使用try-catch语句块捕获抛出异常对象并且处理,否则要在方法的声明处通过throws关键字指明要抛出给方法调用者的异常。
- 在出现异常方法的调用者中捕获并处理异常
3.结构语法 class UserException extends Exception {
UserException(){
super();
...
}
}
throw关键字通常用在方法体中,并且抛出一个异常对象 4.举例
public class Example6_4 {
static int quotient(int x,int y) throws Exception{
if(y<0){
throw new MyException("除数不能是负数");
}
return x/y;
}
public static void main(String[] args) {
int a= 3;
int b=0;
try {
int result = quotient(a,b);
System.out.println(result);
}catch (MyException e){
System.out.println(e.getMessage());
}catch (ArithmeticException e) {
System.out.println("除数不能为0");
}catch (Exception e){
System.out.println("程序发生了其它的异常");
}
}
}
class MyException extends Exception{
String message;
public MyException(String ErrorMessage){
this.message = ErrorMessage;
}
@Override
public String getMessage() {
return message;
}
}
输出结果是: 除数不能为0
- 例2
创建自定义异常类AgeException,继承自类Exception。使用Throw关键字抛出异常对象。 input方法用于接收从键盘输入的姓名和年龄,如果输入为负数,则会抛出AgeException异常并积极捕获处理,如果输入正确,则会将姓名和年龄输出同时结束整个程序。
import java.util.Scanner;
class AgeException extends Exception {
public AgeException(String message) {
super(message);
}
}
public class Example6_9 {
public static void input() {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入姓名:");
String name = scanner.next();
System.out.println("请输入年龄:");
while (scanner.hasNext()) {
try {
int age = scanner.nextInt();
if (age < 0) {
throw new AgeException("年龄不能为负数");
}
System.out.println("姓名" + name);
System.out.println("年龄" + age);
break;
} catch (AgeException e) {
System.out.println(e.getMessage() + "请重新输入:");
}
}
}
public static void main(String[] args) {
input();
}
}
输出结果是: 请输入姓名:
jack
请输入年龄:
-20
年龄不能为负数请重新输入:
-34
年龄不能为负数请重新输入:
-5
年龄不能为负数请重新输入:
11
姓名jack
年龄11
五、Try-With-Resources
package com.chapter6;
import java.io.*;
public class Example6_8 {
public static void main(String[] args) {
copy("E:/MyFirst.java","F:/First.Java");
}
private static void copy(String src, String des) {
InputStream in = null;
OutputStream out = null;
try{
in = new FileInputStream(src);
out = new FileOutputStream(des);
byte[] buff = new byte[1024];
int n;
while ((n=in.read(buff))>=0){
out.write(buff,0,n);
}
}catch (IOException e){
e.printStackTrace();
}finally {
if(in!=null){
try{
in.close();
}catch (IOException e){
e.printStackTrace();
}
}
if(out!=null){
try{
out.close();
}catch (IOException e){
e.printStackTrace();
}
}
}
}
}
从上例可以看出,这种实现非常的杂乱冗长。 Java7之后,推出了Try-With-Resources声明代替之前的方式,try后跟括号"(",括号内的部分称为资源规范头。**资源规范头中可以包含多个定义,通过分号进行分隔。**规范头中定义的对象必须实现java.lang.AntoCloseable 接口,这个接口中有一个close() 方法,因此无论是否正常退出try语句块,这些对象都会在try语句块运行结束之后调用close方法,从而替代以前的在finally中关闭资源的功能,且不需要冗长的代码,另外,Try-With-Resources中的try语句可以不包含catch或者finally语句块而独立存在。 public class Example6_9 {
public static void main(String[] args) {
copy("E:/MyFirst.java","F:/First.Java");
}
private static void copy(String src, String des) {
try (InputStream in = new FileInputStream(src);
OutputStream out = new FileOutputStream(des)) {
byte[] buff = new byte[1024];
int n;
while ((n=in.read(buff))>=0){
out.write(buff,0,n);
}
}catch (IOException e){
e.printStackTrace();
}
}
}
六、异常的使用原则 Java异常强制用户去考虑程序的健壮性和安全性,异常处理不用来控制程序的正常流程,其主要作用是捕获程序在运行时发生的异常并进行相应的处理。编写代码处理某个异常时可遵循以下的一些原则:
- 在当前方法声明中使用try-catch捕获异常
- 一个方法被调用时,调用它的方法必须抛出相同的异常或异常的父类;
- 如果父类抛出多个异常,则覆盖方法必须抛出多个异常的一个子集,不能抛出新的异常
- 不要过度使用异常,不要使用异常代替流程控制,不要抛出简单异常代替所有错误
- 不要使用庞大的try语句,否则会增加编程的复杂度,可以把大块的try语句分成多个可能出现异常的程序段落,分别放在单独的try模块中
- 避免一个catch处理很多异常
- 不要忽略捕捉到的异常,对于捕捉到的异常尽可能在catch语句块解决
- 尽可能使用try-with-resources
|