logo头像

不破不立

Java异常的抓抛选取和自定义异常

Java异常这是个很常用的东西,对于它的分类和处理方式我们来学习一番,特别是他的处理方式(捕获和抛出)的应用以及应用场景很重要,需要好好理解。

内容大纲

  什么是异常
  异常的分类
  异常的处理(重点)
  自定义异常

什么是异常

  在Java中,异常是指程序在运行出错时创建的一种特殊的运行时错误对象,并定义一个基类java.lang.Throwable作为所有异常的超类。

异常的分类

  在Java API中已经定义了许多异常类(称之为预定义异常),这些类如下图,

  Throwable下分为Error和Exception,
    1. Error是指不能通过程序的代码解决或者指程序自身解决不了的错误,其描述了Java运行时系统的内部错误和资源耗尽错误;
    2. Exception是指可以通过程序的代码进行捕获来处理或者指可以通过程序自身解决的异常。

  在Exception中又分为两类:RuntimeException和其他异常,
    1. 由程序错误导致的异常属于RuntimeException;
    2. 程序本身没有问题,但由于像I/O错误这类问题导致的异常属于其他异常。

  一条相当有道理的规则:如果出现RuntimeException异常,那么一定是你的问题。

  Java语言规范将派生于Error类和RuntimeException类的所有异常称之为未检查异常(unchecked exception),所有其他的异常称之为已检查异常(checked exception)。而Java编译器要求必须对所有已检查异常提供异常处理,否则会出现编译错误。

注意:RuntimeException这个名字容易让人混淆,实际上,所有的错误都发生在运行时。————Java核心技术 卷一

  下面举一个例子,我们通过这个例子来学习异常的处理方式以及异常抓抛的应用场景。这里我们的例子可以模拟成一个web项目,client表示处理用户请求的控制器层,service为业务层,dao为数据持久层,结构如下图,


  代码如下:

1
2
3
4
5
6
7
8
9
public class UserDAO {

public Date strToDate(String str) throws ParseException {
Date date = null;
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
date = simpleDateFormat.parse(str);
return date;
}
}

1
2
3
4
5
6
7
public class UserService {

public Date strToDate(String string) throws ParseException {
UserDAO userDAO=new UserDAO();
return userDAO.strToDate(string);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class UserClient {
private static Logger logger = Logger.getLogger("UserClient");
public static void main(String[] args) {
// String str = "2018-03-23 00:30:16";
String str = "201803-323 00:30:16";
UserService userService = new UserService();
Date date =null;
try {
date = userService.strToDate(str);
} catch (ParseException e) {
// e.printStackTrace();
logger.info(e.getMessage()+"(字符串格式错误,无法解析!)");
}finally {
logger.info(str+"->"+date);
}
}
}

  上述的代码就表示一个用户请求服务器进行特定格式字符串转化成日期的API。

异常的处理

  我们看UserDAO,可以看到如果去掉throws ParseException,此时simpleDateFormat.parse(str)这里就会出现编译错误,这就是前面所说的已检查异常,程序必须为其提供异常处理方式。

  异常的处理方式有两种,抓(即采用try catch finally)或者抛(即throws exception)。而UserDAO这里我们为何采用抛?并且在UserService也采用来抛,而在UserClient处又为何采用了抓?

  解答:我们是这么约定的,将异常在最初发起此请求的地方进行抓取,并对其处理,而在其他地方对其往上抛出,不进行处理。因为只有发起此请求的地方知道怎么应对出现的异常进行处理。

  举个例子,小明去银行取一个亿,银行的前台需要向上级经理请示,经理通知金库看守员取一个亿现金,看守员一看金库发现没有一个亿现金,于是这里就出问题了,小明要取十个亿,但是金库没有十个亿,这怎么办,金库看守员解决不了这个问题,就汇报给经理,金库目前没有这么多现金,这里就相当于DAO层将异常抛出给Service层,因为他解决不了这个问题,经理一看,他也搞不定啊,这一下子从哪掏出这么多钱,他就把这个问题丢给了前台,前台接到这个问题后,没办法,他得解决,他不可能把这个问题丢给小明,那样这个银行就不用开了,于是前台就去想办法,怎么解决呢,他发现以前也有这种情况,处理方式就是告诉小明,金库对于一下取一百万现金有个规定,就是先要预约,不然资金周转没有那么快,于是乎小明收到这个通知后就知道了,要预约。
  这个例子就完了,可能会有疑问,说经理也可以解决这个问题啊,直接告诉小明,让小明通知顾客要预约,这样不就是Service层也可以捕获这个异常了吗?这里呢我也是有点疑惑,如果你有好的解决办法请联系我,一起探讨。

  上面这个出现的是已检查异常,必须对其进行异常处理,这里还举个未检查异常,就是经常说的除零运算,代码如下:

1
2
3
public int divide(int a ,int b){
return a/b;
}

  这个方法是可能会出现除数为零的异常的(java.lang.ArithmeticException: / by zero),但是他是RuntimeException的子类,属于未检查异常,即不必须对其进行异常处理。不过这种问题作为程序员也是必须要解决的,因为“如果出现RuntimeException异常,那么一定是你的问题。”

自定义异常

  我们先来看一个例子,假设我们现在有个程序,要录入学生的姓名和年龄学生姓名已经录入成功,年龄等待我们录入,不过有些要求,年龄的输入限制范围是0~150,如果不在这个范围则需要重新录入。

  学生实体类代码如下:

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 Student {
private String name;
private int age;
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}

  学生信息录入代码如下:

1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) {
Student student = new Student();
Scanner scanner = new Scanner(System.in);
String name = "十六子";
student.setName(name);
System.out.println("请输入年龄:");
int age = scanner.nextInt();
student.setAge(age);
System.out.println(student.toString());
}

  我们现在需要将年龄录入这个地方修改以满足需求。
  你可能这样操作,在int age = scanner.nextInt();后面加入以下代码:

1
2
3
4
while(age>150 || age<0){
System.out.println("请重新输入年龄:");
age = scanner.nextInt();
}

  或者说在setAge方法中进行if判断等。
  但是,仔细想以下,这样的扩展性好不好,如果其他地方也要录入了呢,是不是又要这样操作一次,而如果编码人员没有注意到忘记了呢。

  有没有更好的方法?
  那就是我们采用Java自定义异常来进行。
  我们定义一个AgeException类,代码如下:

1
2
3
4
5
6
7
8
9
/**
* 年龄异常类,用于判断录入年龄是否符合要求
*/
public class AgeException extends Exception {

public AgeException(String message) {
super(message);
}
}

  然后修改setAge方法,

1
2
3
4
5
6
7
public void setAge(int age) throws AgeException {
if (age < 0 || age > 150) {
throw new AgeException("年龄不合法,请输入0~150之间的数字!");
} else {
this.age = age;
}
}

  然后就会发现录入年龄setAge处会提示已检查异常编译错误,需要进行异常处理,因此我们采用try catch,修改后的录入代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static void main(String[] args) {
Student student = new Student();
Scanner scanner = new Scanner(System.in);
String name = "十六子";
student.setName(name);
while(true){
System.out.println("请输入年龄:");
int age = scanner.nextInt();
try {
student.setAge(age);
break;
} catch (AgeException e) {
// e.printStackTrace();
System.out.println(e.getMessage());
}
}
System.out.println(student.toString());
}

  对于异常的抓抛需要好好选取处理方式,同时在web应用中,我们也需要自定义一些异常来给予用户更好的体验。

上一篇

评论系统未开启,无法评论!

如果有好的建议或疑问等可以发送邮件至:panhainan@yeah.net,或者添加QQ:1016593477,将你的建议或者疑问告诉作者,作者会对你的建议进行处理并补充到文章的尾部,谢谢大家的谅解!