Java异常处理机制
Q7nl1s admin

异常机制

◼ 异常机制是指当程序出现错误后,程序如何处理。

◼ 在异常引发后,应用程序应该能够转移到一个安全状态,使得系统能够恢复控制权或降级运行 或正常结束程序运行,不至于使系统崩溃或死机,并且尽可能地保存数据、避免损失。

没有异常处理示例:

1
2
3
4
5
6
7
8
9
10
11
public class Test {
public static void main (String args[]) {
int i = 0;
String[ ] greetings = { "Hello world!", "No, I mean it!", "HELLO WORLD!!" };
while ( i < 4 ) {
System.out.println ( greetings[i] );
i++;
}
System.out.println ("运行完毕");
}
}

exception_0

添加异常处理

1
2
3
4
5
6
7
8
9
try {
while ( i < 4 ) {
System.out.println( greetings[i] ); // 可能会产生异常的代码(块)
i++;
}
} catch ( Exception e ) {
System.out.println( "有异常!"); //异常处理
}
System.out.println ("运行完毕");

Java 异常处理允许程序捕获异常并处理,异常处理 代码与正常流程代码分离,更易识别和管理

exception_1

更具体的异常处理(异常分类)

1
2
3
4
5
6
7
8
9
try {
while (i < 4) {
System.out.println(greetings[i]);
i++;
}
} catch ( ) {
System.out.println( "数组下标" + i + "越界啰!"); //异常处理
}
System.out.println ("运行完毕");
exception_17

异常产生的主要原因:

◼ Java 内部错误导致异常,即 Java 虚拟机产生的异常

◼ 编写的程序代码中的错误所产生的异常,例如空指针异常、数组越界异常等

◼ 通过 throw 语句手动生成的异常,一般用来告知该方法的调用者一些必要信息


异常处理

◼ Java异常处理涉及五个关键字:

​ try、catch、finally

​ throws、throw (这两个见后序)

try 和 catch 语句

1
2
3
4
5
6
7
try {
// 可能会产生异常的代码
}
catch ( 异常类型 异常变量名 ) { // 通用异常处理方法: catch ( Exception e ) { }
// 某个特定类型的异常处理代码
// 即使catch块是空的,也算是处理情况
}

注意:一个try块可搭配多个catch块,每一catch块可处理不同的异常

多个 catch 块示例

多个catch匹配原则:由上到下只匹配其中 一个 异常,如匹配则不会再执行其他 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
public class Test {
public static void main(String[] args) {
int i = 0;
String[] data = {"5", "0", "aaa"};
while (i < 4) {
try {
int a = Integer.parseInt(data[i]);
System.out.println( reciprocal(a) );
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("数组下标越界:" + i);
} catch (NumberFormatException e) {
System.out.println("数据格式不正确");
} catch (ArithmeticException e) {
System.out.println("除0异常");
} catch (Exception e) {
System.out.println("有异常");
}
i++;
}
System.out.println("运行完毕");
}
public static int reciprocal(int a) {
return 1/a;
}
}
exception_18

finally 语句

◼ finally 语句定义一个总是要执行的代码块,而不考虑异常是否被捕获。

◼ 基本框架:

1
2
3
4
5
6
7
8
9
10
try {
...
}
catch (...) {
// 异常处理
}
finally { // 非必须模块

// 不管是否有异常发生,这里代码都要执行
}
exception_2

finally 示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Test {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
System.out.println("Windows 系统已启动!");
String[] pros = { "记事本", "计算器", "浏览器" };
try {
for (int i = 0; i < pros.length; i++) {
System.out.println(i + 1 + ":" + pros[i]);
}
System.out.print("是否运行程序(y/n):");
String answer = input.next();
if (answer.equals("y")) {
System.out.println("请输入程序编号:");
int no = input.nextInt();
System.out.println("正在运行程序[" + pros[no - 1] + "]");
}
} catch (Exception e) {
e.printStackTrace(); //输出详细异常信息
} finally {
System.out.println("谢谢使用!");
}
}
}
exception_3

注意:

◼ try、catch、finally 三个语句块均不能单独使用

◼ 三者可以组成 try...catchtry...catch...finallytry...finally 三种结构

◼ catch 语句可以有一个或多个,finally 语句最多一个

注意:try、catch、finally 三个代码块中变量的 作用域为代码块内部,分别独立而不能相互访问。如果要在三个块中都能访问,则需要将变量定义到这些块的外面。

关于 getMessage() 和 printStackTrace()

exception_4


异常分类

◼ Java异常分为两大类,错误类 Error 和异常类 Exception

◼ java.lang.Throwable 作为所有异常的超类

exception_5

异常分类:

exception_6

常见的 RuntimeException 异常

运行时异常类型 说明
ArithmeticException 算术错误异常,如以零做除数
ArraylndexOutOfBoundException 数组索引越界
ArrayStoreException 向类型不兼容的数组元素赋值
ClassCastException 类型转换异常
IllegalArgumentException 使用非法实参调用方法
lIIegalStateException 环境或应用程序处于不正确的状态
lIIegalThreadStateException 被请求的操作与当前线程状态不兼容
IndexOutOfBoundsException 某种类型的索引越界
NullPointerException 尝试访问 null 对象成员,空指针异常
NegativeArraySizeException 再负数范围内创建的数组
NumberFormatException 数字转化格式异常,比如字符串到 float 型数字的转换无效
TypeNotPresentException 类型未找到

常见非运行时异常

非运行时异常类型 说明
ClassNotFoundException 类未找到异常
NoSuchFieldException 字段未找到异常
NoSuchMethodException 方法未找到异常
InstantiationException 实例化异常
IllegalAccessException 没有访问权限
InterruptedException 线程被另一个线程中断的异常
IOException 线程被另一个线程中断的异常
SQLException 操作数据库sql异常
FileNotFoundException 文件未找到异常

示例:常见的运行时异常

◼ ArithmeticException:算术异常

​ ◼ 如:int k = 1/0; 整数除 0

◼ NullPointerException:空指针异常

​ ◼ 如:String s = null; boolean eq = s.equals("abc");

◼ NumberFormatException:数字格式异常

​ ◼ 如:int x = Integer.parseInt("12.34");

◼ ArrayIndexOutOfBoundsException:数组索引越界异常

​ ◼ 如:int[] a=new int[3]; int x=a[-1] 或 a[3];

◼ StringIndexOutOfBoundsException:字符串索引越界异常

​ ◼ 如:String s = "hello"; char c = s.chatAt(5);

◼ ClassCastException:类型转换异常

​ ◼ 如:A 不是 B 的父类或子类, A a = new A(); B b=(b)a;

◼ IllegalArgumentException:传递非法参数异常

​ ◼ 如:Color c = new Color(0,256,255); 颜色值只能 0-255


异常抛出

◼ 非 RuntimeException 异常,是必须进行处理的异常,如果不处理,程序就 不能编译通过

◼ 对于有些异常是 当前层不能或不想解决的 ,则可 抛出异常 ,由上层调用者来处理

◼ 上层可以使用 try ... catch 捕获处理,也可继续向上抛出…(最终会由 JVM 处理)

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Test {
public static void main(String[] args) {
String url ="jdbc:mysql://localhost:3306/school"; //数据库连接字符串
Class.forName("org.gjt.mm.mysql.Driver").newInstance(); //加载驱动程序
Connection conn= DriverManager.getConnection(url,"root","dba"); //建立连接
Statement stmt=conn.createStatement(); //创建SQL容器
String sql="select * from teacher"; //sql查询teacher表
ResultSet rs=stmt.executeQuery(sql); //获得结果集
while( rs.next() ) { //处理结果集
System.out.print(rs.getString("id")+" ");
System.out.print(rs.getString("name")+" ");
System.out.print(rs.getString("address")+" ");
System.out.print(rs.getString("year")+"\n");
}
rs.close(); stmt.close(); conn.close(); //依次关闭
}
}

非运行时异常,如果不处理,程序就不能编译通过

**添加异常处理:使用 try…catch **

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
String url = "jdbc:mysql://localhost:3306/school"; //数据库连接字符串
try {
Class.forName("org.gjt.mm.mysql.Driver").newInstance(); //加载驱动程序
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
try {
Connection conn = DriverManager.getConnection(url, "root", "dba"); //建立连接
Statement stmt = conn.createStatement(); //创建SQL容器

} catch (SQLException e) {
e.printStackTrace();
}

try…catch处理存在的不足:过多会影响程序完整性和可阅读性

更好的异常处理:抛出异常

1
2
3
4
5
6
7
8
9
10
public class Test {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, SQLException { // 当前方法不处理异常,向上层抛出(谁调用谁处理)
// 以上抛出可能的异常可简写为一个:throws Exception
String url ="jdbc:mysql://localhost:3306/school"; //数据库连接字符串
Class.forName("org.gjt.mm.mysql.Driver").newInstance(); //加载驱动程序
Connection conn= DriverManager.getConnection(url,"root","dba"); //建立连接
Statement stmt=conn.createStatement(); //创建SQL容器

}
}

代码结构看上去更完整

throws 关键字

◼ throws 关键字:

◼ 出现在方法的声明中,表示该方法可能会抛出的异常

◼ 允许 throws 后面跟着多个异常类型 (用逗号分隔)

◼ 这些异常类可以是方法中调用了可能拋出异常的方法而产生的异常,也可以是方法体中生成并拋出的异常

◼ main 方法也可使用 throws 抛出异常,该异常将交给 JVM 处理

注意:重写方法抛出异常的限制

◼ 规则:重写方法一定不能抛出新的非运行时异常,或者比被重写方法声明更加宽泛的非运行时异常

◼ 解释:子类方法抛出的异常类型应该是父类方法抛出的异常类型的子类或相同,子类方法抛出的异常 不允许比父类方法抛出的异常多。

示例1

1
2
3
4
5
6
7
8
9
10
class Father {
public void test() throws IOException {
}
}

class Son extends Father {
@Override
public void test() throws Exception { // 编译报错:子类方法声明抛出了比父类方法更大的异常
}
}

示例2:

1
2
3
4
5
6
7
8
9
public class A {
public void foo() throws EOFException {
}
}
class B extends A {
@Override
public void foo() throws FileNotFoundException { // 编译报错:重写方法不能抛出新的非运行时异常
}
}

throw关键字

◼ throw关键字:

​ ◼ throw出现在方法体中,用于明确抛出一个具体的异常对象

​ ◼ throw关键字不会单独使用 (通常要配合一些条件)

用法1:if (异常条件) throw 异常对象 ;

用法2:try {…}

​ catch { throw 异常对象; }

说明:当 throw 语句执行时,它后面的语句将不执行,此时程序转向调用者程序,寻找与之相匹配的 catch 语句,执行相应的异常处理程序。 如果没有找到相匹配的 catch 语句,则再转向上一层的调用程序。这样逐层向上,直到最外层的异常处理程序终止程序并打印出调用栈情况。

throw 示例1:

◼ 在某仓库管理系统中,要求管理员的用户名需要由 8 位以上的字母或者数字组成,不能含有其他的字符。

◼ 当长度在 8 位以下时拋出异常,并显示异常信息;

◼ 当字符含有非字母或者数字时,同样拋出异常,显示异常信息。

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
public class Test {
public static boolean validateUserName(String username) throws IllegalArgumentException{ // 如果在函数体内用 throw 某种异常,建议在函数名中加 throws 抛出异常(交给调用它的上层方法进行处理)
boolean confirm = false;
if ( username.length() > 8 ) {
// 判断用户名长度是否大于8位
for (int i = 0; i < username.length(); i++) {
char ch = username.charAt(i); // 获取每一位字符
if ((ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) {
confirm = true;
} else {
throw new IllegalArgumentException("用户名只能由字母和数字组成!"); // 自定义异常信息,抛出一个IllegalArgumentException异常对象(运行时异常)
}
}
} else {
throw new IllegalArgumentException("用户名长度必须大于 8 位!"); // 抛出一个IllegalArgumentException异常对象(运行时异常)
}
return confirm;
}
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
System.out.print("请输入用户名:");
String username = input.next();
try {
boolean confirm = validateUserName(username);
if (confirm) {
System.out.println("用户名输入正确!");
}
} catch (IllegalArgumentException e) {
System.out.println(e.getMessage());
}
}
}

注:当异常被抛出时,程序会跳出方法

exception_7

throws 示例2:自定义 sqrt

1
2
3
4
5
6
7
8
9
public class Test {
public static void main(String[] args) {
try {
System.out.println( Math.sqrt(-144) );
} catch( Exception e ) {
System.out.println("根号下不能负数"); // 无法捕捉异常,运行结果:NaN
}
}
}

自定义 MyMath.sqrt() 来改进 Math.sqrt() ,并对异常进行重新封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class MyMath {
public static double sqrt(String nStr) throws Exception { // 建议加 throws 抛出异常
if (nStr == null) {
throw new Exception("输入的字符不能为空!");
}
double n = 0;
try {
n = Double.parseDouble(nStr);
}
catch( NumberFormatException e ) {
throw new Exception("输入的字符串必须能够转化成数字!");
}
if ( n < 0 ){
throw new Exception("输入的字符串转化成的数字必须大于0!");
}
return Math.sqrt(n);
}
}

测试一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static void main(String[] args) {
boolean f = true;
while(f) { // 输入数据直至输入正确格式为止
try{
System.out.print("请输入值:");
String s=new Scanner(System.in).nextLine();
System.out.println( MyMath.sqrt(s) );
f=false;
} catch( Exception e ) {
System.out.println(e.getMessage());
f=true;
}
}
}
exception_8

throws 和 throw 几点区别

◼ throws 用在方法的声明处,而 throw 用在方法内部通过

◼ throws 用来声明方法可能抛出是哪种类型的异常,throw 则是拋出的一个具体的异常实例

◼ throws 抛出异常是一种可能出现的异常(并不一定会发生),throw 如果执行了,则一定是抛出了某种异常

◼ 两种抛出都不会由方法自身去处理,真正的处理由上层调用者处理


自定义异常类

◼ 用户自定义异常类是通过扩展Exception类来创建的

◼ 这种异常类可以包含一个“普通类”所包含的任何东西

示例

◼ 编写一个程序,对会员注册时的年龄进行验证,即检测是否在 0~100 岁

自定义异常类

1
2
3
4
5
6
7
class AgeRangeException extends Exception {
public AgeRangeException() {
}
public AgeRangeException(String msg) {
super(msg);
}
}

自定义异常类一般包含两个构造方法:

① 无参的默认构造函数

② 以字符串的形式接收一个定制的异常消息,并将该 消息传递给超类的构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Test {
public static void main(String[] args) {
int age;
Scanner input = new Scanner(System.in);
System.out.print("请输入年龄:");
try {
age = input.nextInt(); // 获取年龄
if (age < 0) {
throw new AgeRangeException("输入的年龄为负数!输入有误!");
} else if (age > 100) {
throw new AgeRangeException("输入的年龄大于100!输入有误!");
} else {
System.out.println("您的年龄为:" + age);
}
} catch ( AgeRangeException e ) {
System.out.println( e.getMessage() );
} catch ( InputMismatchException e ){
System.out.println("输入的年龄不是数字!");
}
}
}
exception_9

附录

try/catch/finally中的return

◼ 在 try 块、catch 块遇到 return 语句时, 当执行完 return 语句之后 ,并不会立即结束该方法,而是去寻找其是否包含 finally 块

◼ 如果没有 finally 块,方法终止,返回相应的返回值。如果有 finally 块,则开始执行 finally 块;

只有当 finally 块执行完成后,系统才会再次跳回来根据 return 语句结束方法;

◼ 如果在 finally 块里使用了 return 语句来导致结束方法,则 finally 块已经结束了方法,系统不会跳回去执行 try、catch 块里的任何代码。

示例 1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Test {
public static boolean foo() {
try {
int i = 10 / 0;
System.out.println("i = " + i);
return true;
} catch (Exception e) {
System.out.println(" -- catch --");
return false; // catch中有return
} finally {
System.out.println(" -- finally --");
}
}
public static void main(String[] args) {
System.out.println( foo() );
}
}
exception_10

示例 2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Test {
public static boolean foo () {
boolean b = true;
try {
int i = 10 / 0;
System.out.println("i = " + i);
return true;
} catch (Exception e) {
System.out.println(" -- catch --");
System.out.println("b:" + b);
return b = false; // catch中有return
} finally {
System.out.println(" -- finally --");
System.out.println("b:" + b);
}
}
public static void main(String[] args) {
System.out.println( foo() );
}
}
exception_11

示例 3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Test {
public static boolean foo() {
try {
int i = 10 / 0;
System.out.println("i = " + i);
return true;
} catch (Exception e) {
System.out.println(" -- catch --");
return false; // catch中有return
} finally {
System.out.println(" -- finally --");
return true; // finally中也有return
}
}
public static void main(String[] args) {
System.out.println( foo() );
}
}
exception_12

示例 4

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Test {
public static int foo() {
int count = 5;
try {
return ++count; // try中有return
} catch (Exception e) {
// TODO: handle exception
} finally {
System.out.println("finally()执行");
return count++; // finally中也有return
}
}
public static void main(String[] args) {
System.out.println( foo() );
}
}
exception_13

日志

◼ 日志用来记录程序的运行轨迹,方便查找关键信息,也方便快速定位解决问题。

◼ Java 自带的日志工具类:java.util.logging

日志级别

exception_14

日志用法示例

1
2
3
4
5
6
7
8
9
public class Test {
private static Logger log = Logger.getLogger( Test.class.toString() ); // getLogger()方法用于查找或创建记录器,getLogger参数:日志器名称串,一般使用当前类名

public static void main(String[] args) {
log.info("这是info级信息");
log.warning("这是warning级信息"); // 使用 info()等方法创建日志信息
log.severe("这是server级信息");
}
}
exception_15

断言

◼ 断言是一种调试方式,断言失败会抛出 AssertionError,只能在开发和测试阶段启用断言

◼ 基本用法:

assert boolean 表达式 : 错误信息

功能:

◼ 当表达式为真时,程序继续执行;

◼ 当表达式为假时,程序中断,显示 AssertionError 异常和错误信息:

如何开启断言

assert 默认是关闭的,开启方法:在运行配置中添加 VM 参数:-ea

exception_19

断言示例:

1
2
3
4
5
6
7
public class Test {
public static void main(String[] args) {
double x = 100;
assert x > 200 : "x must > 200";
System.out.println(x);
}
}

exception_16

 Comments
Comment plugin failed to load
Loading comment plugin
Powered by Hexo & Theme Keep
Unique Visitor Page View