本章内容:

1.异常的产生过程解析
2.异常的处理
3.异常的注意事项
4.自定义异常

1.异常的产生过程解析

异常产生的原理(示意图):
Ⅰ. jvm的getElement方法把异常对象抛给main方法
Ⅱ. main方法没有异常的处理逻辑会继续把对象抛给JVM

2.异常的处理

异常的处理常涉及到五个关键字:

throw关键字

作用:可以使用throw关键字在指定方法中抛出指定的异常
使用格式:throw new xxxException(“异常的产生原因”)
注意:

  • throw关键字必须写在方法的内部
  • throw关键字后边new的对象必须是Exception或者其子类对象(参考Exception类的常见情况
  • throw关键字后边创建的是RuntimeException或者其子类对象时,我们可以交给jvm处理(jvm会中断程序)。throw关键字后边若是编译异常,要么throws,要么try catch。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.Alibaba;

public class ExceptionDemo {
public static int getElement(int[] array,int index){
if(index<0 || index>array.length-1){
throw new ArrayIndexOutOfBoundsException("索引值超过数组范围");
}else if (array == null){
throw new NullPointerException("数组不能为空");
}
return array[index];
}
public static void main(String[] args) {
int[] aa = null;
int[] cc = {1,2,3};
// int bb = getElement(aa,1);
// System.out.println(bb);
System.out.println(getElement(cc,3));
}
}

throws关键字

throws关键字:异常处理的第一种方式,交给别人处理

作用:
当方法内部抛出异常对象的时候,那么我们就必须处理这个异常对象
可以使用throws关键字处理异常对象,会把异常对象声明抛出给方法的调用者处理(自己不处理,给别人处理),最终交给JVM处理–>中断处理

使用格式:在方法声明时使用

1
2
3
4
5
修饰符 返回值类型 方法名(参数列表) throws AAAExcepiton,BBBExcepiton...{ // 有几个异常就写几个
throw new AAAExcepiton("产生原因");
throw new BBBExcepiton("产生原因");
...
}

注意:
1.throws关键字必须写在方法声明处
2.throws关键字后边声明的异常必须是Exception或者是Exception的子类
3.方法内部如果抛出了多个异常对象,那么throws后边必须也声明多个异常:如果抛出的多个异常对象有子父类关系,那么直接声明父类异常即可
4.调用了一个声明抛出异常的方法,我们就必须的处理声明的异常:要么继续使用throws声明抛出,交给方法的调用者处理,最终交给JVM;要么try…catch自己处理异常,(参考下列实例代码11-13行)

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
41
package com.bytedance.demo01.Exception;

import java.io.FileNotFoundException;
import java.io.IOException;

public class Demo05Throws {
/*
FileNotFoundException extends IOException extends Excepiton
如果抛出的多个异常对象有子父类关系,那么直接声明父类异常即可
*/
//public static void main(String[] args) throws FileNotFoundException,IOException {
//public static void main(String[] args) throws IOException {
public static void main(String[] args) throws Exception {
readFile("c:\\a.tx");

System.out.println("后续代码");
}

/*
定义一个方法,对传递的文件路径进行合法性判断
如果路径不是"c:\\a.txt",那么我们就抛出文件找不到异常对象,告知方法的调用者
注意:
FileNotFoundException是编译异常,抛出了编译异常,就必须处理这个异常
可以使用throws继续声明抛出FileNotFoundException这个异常对象,让方法的调用者处理
*/
public static void readFile(String fileName) throws FileNotFoundException,IOException{
if(!fileName.equals("c:\\a.txt")){
throw new FileNotFoundException("传递的文件路径不是c:\\a.txt");
}

/*
如果传递的路径,不是.txt结尾
那么我们就抛出IO异常对象,告知方法的调用者,文件的后缀名不对
*/
if(!fileName.endsWith(".txt")){
throw new IOException("文件的后缀名不对");
}

System.out.println("路径没有问题,读取文件");
}
}

try…catch 关键字

try…catch:异常处理的第二种方式,自己处理异常
格式:

1
2
3
4
5
6
7
8
9
try{
可能产生异常的代码
}catch(定义一个异常的变量,用来接收try中抛出的异常对象){
异常的处理逻辑,异常异常对象之后,怎么处理异常对象
一般在工作中,会把异常的信息记录到一个日志中
}
...
catch(异常类名 变量名){
}

注意:
1.try中可能会抛出多个异常对象,那么就可以使用多个catch来处理这些异常对象
2.如果try中产生了异常,那么就会执行catch中的异常处理逻辑,执行完毕catch中的处理逻辑,继续执行try…catch之后的代码。如果try中没有产生异常,那么就不会执行catch中异常的处理逻辑,执行完try中的代码,继续执行try…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
public class Demo01TryCatch {
public static void main(String[] args) {
try{
//可能产生异常的代码
readFile("d:\\a.tx");
System.out.println("资源释放");
}catch (IOException e){//try中抛出什么异常对象,catch就定义什么异常变量,用来接收这个异常对象
//异常的处理逻辑,异常异常对象之后,怎么处理异常对象
System.out.println("catch - 传递的文件后缀不是.txt");

// throwable类出来try catch中的异常
System.out.println(e.getMessage());
System.out.println(e.toString());
e.printStackTrace();
}
System.out.println("程序后续代码");
}

/*
如果传递的路径,不是.txt结尾
那么我们就抛出IO异常对象,告知方法的调用者,文件的后缀名不对
*/
public static void readFile(String fileName) throws IOException {

if(!fileName.endsWith(".txt")){
throw new IOException("文件的后缀名不对");
}
System.out.println("路径没有问题,读取文件");
}
}

Throwable类

Throwable 类是 Java 语言中所有错误或异常的超类(终极父类)。

try…catch代码块处理异常时可以使用的Throwable类中定义了3个异常处理的方法:

  • String getMessage() 返回此 throwable 的简短描述。
  • String toString() 返回此 throwable 的详细消息字符串。
  • void printStackTrace() JVM打印异常对象,默认此方法,打印的异常信息是最全面的

finally关键字

finally代码块
格式:

1
2
3
4
5
6
7
8
9
10
11
12
try{
可能产生异常的代码
}catch(定义一个异常的变量,用来接收try中抛出的异常对象){
异常的处理逻辑,异常异常对象之后,怎么处理异常对象
一般在工作中,会把异常的信息记录到一个日志中
}
...
catch(异常类名 变量名){

}finally{
无论是否出现异常都会执行
}

注意:
1.finally不能单独使用,必须和try一起使用
2.finally一般用于资源释放(资源回收),无论程序是否出现异常,最后都要资源释放(IO)

举例说明:

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
import java.io.IOException;

public class Demo02TryCatchFinally {
public static void main(String[] args) {
try {
//可能会产生异常的代码
readFile("c:\\a.tx");
} catch (IOException e) {
//异常的处理逻辑
e.printStackTrace();
} finally {
//无论是否出现异常,都会执行
System.out.println("资源释放");
}
}

/*
如果传递的路径,不是.txt结尾
那么我们就抛出IO异常对象,告知方法的调用者,文件的后缀名不对

*/
public static void readFile(String fileName) throws IOException {
if(!fileName.endsWith(".txt")){
throw new IOException("文件的后缀名不对");
}
System.out.println("路径没有问题,读取文件");
}
}

3.异常的注意事项

  • 多个异常使用捕获又该如何处理呢?
  1. 多个异常分别处理。
  2. 多个异常一次捕获,多次处理。
  3. 多个异常一次捕获一次处理。

一般我们是使用一次捕获多次处理方式,格式如下:

1
2
3
4
5
6
7
8
9
try{
编写可能会出现异常的代码
}catch(异常类型A e){ 当try中出现A类型异常,就用该catch来捕获.
处理异常的代码
//记录日志/打印异常信息/继续抛出异常
}catch(异常类型B e){ 当try中出现B类型异常,就用该catch来捕获.
处理异常的代码
//记录日志/打印异常信息/继续抛出异常
}

注意:这种异常处理方式,要求多个catch中的异常不能相同,并且若catch中的多个异常之间有子父类异常的关系,那么子类异常要求在上面的catch处理,父类异常在下面的catch处理。(Exception类是IO异常,Runtime异常,error错误的父类)

  • 运行时异常被抛出可以不处理。即不捕获也不声明抛出。

  • 如果finally有return语句,永远返回finally中的结果,避免该情况.

  • 如果父类抛出了多个异常,子类重写父类方法时,抛出和父类相同的异常或者是父类异常的子类或者不抛出异常。

  • 父类方法没有抛出异常,子类重写父类该方法时也不可抛出异常。此时子类产生该异常,只能捕获处理,不能声明抛出

4.自定义异常

  1. 为什么需要自定义异常类:

我们说了Java中不同的异常类,分别表示着某一种具体的异常情况,那么在开发中总是有些异常情况是SUN没有定义好的,此时我们根据自己业务的异常情况来定义异常类。例如年龄负数问题,考试成绩负数问题等等。

在上述代码中,发现这些异常都是JDK内部定义好的,但是实际开发中也会出现很多异常,这些异常很可能在JDK中没有定义过,例如年龄负数问题,考试成绩负数问题.那么能不能自己定义异常呢?

  1. 什么是自定义异常类:

在开发中根据自己业务的异常情况来定义异常类。比如自定义一个业务逻辑异常: RegisterException:一个注册异常类。

  1. 异常类如何定义:
  • 自定义一个编译期异常: 自定义类 并继承于java.lang.Exception。(编译异常一定要throws或者try catch处理
  • 自定义一个运行时期的异常类:自定义类 并继承于java.lang.RuntimeException。(运行期的异常RuntimeException可以不用处理,直接交给JVM处理-中断程序)
    格式:
    1
    2
    3
    4
    public class XXXExcepiton extends Exception(编译异常) | RuntimeException{
    添加一个空参数的构造方法
    添加一个带异常信息的构造方法
    }

举例说明:定义一个登陆异常类RegisterException:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.alibaba;

public class RegisterException extends /*Exception*/ RuntimeException{
//添加一个空参数的构造方法
public RegisterException(){
super();
}
/*
添加一个带异常信息的构造方法
查看源码发现,所有的异常类都会有一个带异常信息的构造方法,方法内部会调用父类带异常信息的构造方法,让父类来处理这个异常信息
*/
public RegisterException(String message){
super(message);
}
}
  1. 注意事项:

  2. 自定义异常类一般都是以Exception结尾,说明该类是一个异常类

  3. 自定义异常类,必须的继承Exception或者RuntimeException
    继承Exception:那么自定义的异常类就是一个编译期异常,如果方法内部抛出了编译期异常,就必须处理这个异常,要么throws,要么try…catch
    继承RuntimeException:那么自定义的异常类就是一个运行期异常,无需处理,交给虚拟机处理(中断处理)

  4. 自定义异常的练习

需求:我们模拟注册操作,如果用户名已存在,则抛出异常并提示:亲,该用户名已经被注册。

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
41
42
43
import java.util.Scanner;

/*
分析:
1.使用数组保存已经注册过的用户名(数据库)
2.使用Scanner获取用户输入的注册的用户名(前端,页面)
3.定义一个方法,对用户输入的中注册的用户名进行判断
遍历存储已经注册过用户名的数组,获取每一个用户名
使用获取到的用户名和用户输入的用户名比较
true:
用户名已经存在,抛出RegisterException异常,告知用户"亲,该用户名已经被注册";
false:
继续遍历比较
如果循环结束了,还没有找到重复的用户名,提示用户"恭喜您,注册成功!";
*/
public class Demo02RegisterException {
// 1.使用数组保存已经注册过的用户名(数据库)
static String[] usernames = {"张三","李四","王五"};

public static void main(String[] args) {
//2.使用Scanner获取用户输入的注册的用户名(前端,页面)
Scanner sc = new Scanner(System.in);
System.out.println("请输入您要注册的用户名:");
String username = sc.next();
checkUsername(username);

}

//3.定义一个方法,对用户输入的中注册的用户名进行判断
public static void checkUsername(String username) {
//遍历存储已经注册过用户名的数组,获取每一个用户名
for (String name : usernames) {
//使用获取到的用户名和用户输入的用户名比较
if(name.equals(username)){
//true:用户名已经存在,抛出RegisterException异常,告知用户"亲,该用户名已经被注册";
throw new RegisterException("亲,该用户名已经被注册");//抛出运行期异常,无需处理,交给JVM处理,中断处理
}
}

//如果循环结束了,还没有找到重复的用户名,提示用户"恭喜您,注册成功!";
System.out.println("恭喜您,注册成功!");
}
}

参考文章


 评论

联系我 | Contact with me

Copyright © 2019-2020 谁知你知我,我知你知深。此恨经年深,比情度日久

博客内容遵循 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 协议