1 异常简述
1.1 异常简述
异常是程序中的一些错误,但并不是所有的错误都是异常,并且错误有时候是可以避免的。
比如说,代码少了一个分号,那么运行出来结果是 java.lang.Error
的错误;
而System.out.println(10/0);
,是因为用0做了除数,会抛出 java.lang.ArithmeticException
的异常。
- 用来查询bug的关键参考信息。当程序出现bug时,可通过异常快速找出问题所在。
- 异常可作为方法内部的一种特殊的返回值 ,以告知调用者底层的执行情况。详见下文 《自定义异常》 。
1.2 异常体系
-
Error
:系统级别的严重问题,我们开发人员不需要处理。 -
Exception
:可处理异常的顶级父类。表示程序本身可以处理的问题。RuntimeException及其子类
:运行时异常 。在编译期不检查,运行时才会出现异常提醒。非RuntimeException
:编译时异常 。编译时就会提醒处理,否则程序不能通过编译。
2 编译时异常和运行时异常
Java异常被分为两大类:
- 编译时异常(受检异常)。 编译期 :Java文件通过Javac命令编译成字节码文件
- 运行时异常(非受检异常)。 运行期 :字节码文件通过Java命令运行
2.1 运行时异常
运行时异常是指 RuntimeException
类及其子类。
特点: 在编译期不检查,运行时才会出现异常提醒。
2.2 编译时异常
除了运行时异常的异常都是编译时异常。
特点: 必须显示处理,否则程序就会发成错误,无法通过编译
3 Throwable常用方法
以下方法均不会让程序停止运行。
方法 | 说明 |
---|---|
public String getMessage() | 返回此throwable的详细信息字符串(异常原因) |
public String toString() | 返回此可抛出简短描述(异常的类名+异常原因) |
public void printStackTrace() | 以红色字体把异常的错误信息输出在控制台(异常的类名+异常原因+位置) |
- public String
getMessage()
//返回此throwable的详细信息字符串(异常原因)
public class ExceptionDemo7 {
public static void main(String[] args) {
try {
System.out.println(1 / 0);
} catch (ArithmeticException e) {
String message = e.getMessage();
System.out.println(message);
}
System.out.println("最后一条输出语句");
/*运行结果:
/ by zero
最后一条输出语句
*/
}
}
- public String
toString()
//返回此可抛出简短描述(异常的类名+异常原因)
public class ExceptionDemo7 {
public static void main(String[] args) {
try {
System.out.println(1 / 0);
} catch (ArithmeticException e) {
String s = e.toString();
System.out.println(s);
}
System.out.println("最后一条输出语句");
/*运行结果:
java.lang.ArithmeticException: / by zero
最后一条输出语句
*/
}
}
- public void
printStackTrace()
//以红色字体把异常的错误信息输出在控制台(异常的类名+异常原因+位置)
public class ExceptionDemo7 {
public static void main(String[] args) {
try {
System.out.println(1 / 0);
} catch (ArithmeticException e) {
e.printStackTrace();
}
System.out.println("最后一条输出语句");
/*运行结果:
java.lang.ArithmeticException: / by zero
at do22Exception.ExceptionDemo7.main(ExceptionDemo7.java:9)
最后一条输出语句
*/
}
}
4 异常的处理方式
4.1 JVM的默认处理方案
如果程序出现问题,而我们没有做任何处理,最终JVM会做默认的处理。
默认处理:
- 在控制台输出:异常的类名、异常原因、异常出现位置。
- 程序在遇到异常后就停止运行 ,异常下的代码就不会运行。
4.2 捕获异常
格式:
try {
可能出现异常的代码;
} catch (异常类名 变量名) {
遇到异常后的处理代码;
}
目的: 处理运行时异常、编译时异常时,抛出异常且程序不会终止 。
案例:
public class ExceptionDemo4 {
public static void main(String[] args) {
System.out.println("第一条输出语句");
try {
//可能出现异常的代码
System.out.println(1 / 0);
} catch (ArithmeticException e) {
System.out.println("0不能做除数, " + e);
}
System.out.println("第二条输出语句");
System.out.println("第三条输出语句");
}
}
运行结果:
第一条输出语句
0不能做除数, java.lang.ArithmeticException: / by zero
第二条输出语句
第三条输出语句
解释: 当 try
中的代码出现异常后,程序会创建一个该异常的对象,再把该异常对象与 catch
中的变量对比。
如果 catch
中的变量可以接收该异常(即两个异常一样),就表示该异常被捕获,程序便会执行 catch
中的代码,且 try...catch
下的代码也会运行。
如果两个异常不一样,还是会执行JVM的默认处理方案。
可见,遇到可能会发生的异常并成功捕获后,程序会抛出我们给定的处理方案,并且异常不会使程序停止运行。
捕获异常其他细节
- 只有
try
中出现了异常,才会执行异常对应catch
中的代码。 try
中可能会遇到多个异常,如果遇到的第一个异常在catch
中没有捕获,还是会执行JVM的默认处理方式。如下,遇到的第一个异常是索引越界异常,但是catch
中只捕获了算数异常。
- 所以我们要写所有对应的异常捕获
catch
。
public class ExceptionDemo5 {
public static void main(String[] args) {
int[] arr = {1, 2, 3};
try {
System.out.println(arr[3]);
System.out.println(1 / 0);
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("索引越界");
System.out.println("第一条输出语句");
} catch (ArithmeticException e) {
System.out.println("0不能做除数");
System.out.println("第二条输出语句");
}
System.out.println("第三条输出语句");
}
}
运行结果:
索引越界
第一条输出语句
第三条输出语句
- 且异常中如果存在父子关系,父类一定要写在最下面。
- 如果
try
中遇到了异常,那么从try
遇到异常的语句往下的语句就不会执行。
public class ExceptionDemo6 {
public static void main(String[] args) {
try {
System.out.println("第一条输出语句");
System.out.println(1 / 0);
System.out.println("第二条输出语句");
} catch (ArithmeticException e) {
System.out.println("0不能做除数");
}
System.out.println("第三条输出语句");
}
}
运行结果:
第一条输出语句
0不能做除数
第三条输出语句
4.3 抛出异常
当在方法中出现了异常,该方法已经没有执行下去的意义了。此时可以采取抛出异常并终止方法。
4.3.1 throws
有时会出现没有权限使用 try...catch...
来进行异常处理,就需要用到 throws
throws 写在方法定义处,表示声明一个异常。告知调用者,使用本方法可能会出现哪些异常。
格式: throws 异常类名1, 异常类名2, 异常类名3 ...
public void 方法() throws 异常类名1, 异常类名2, 异常类名3 ... {
...
}
注意:
- 这个格式是跟在方法的括号的后面的
- 编译时异常:必须要写
- 运行时异常:可以省略不写
处理编译时异常时:
- 仅抛出异常,程序会异常终止。
- 谁调用该方法,谁就用
try...catch...
捕获异常并处理。
4.3.2 throw
throw写在方法体内,手动抛出异常对象交给调用者。且throw下面的代码不会再执行。
格式: throw 异常
public void 方法() {
throw new ArrayIndexOutOfBoundsException();
//只要抛出了异常,往下的代码就不会在执行
}
处理编译时异常时:
- 仅抛出异常,程序会异常终止。
- 谁调用该方法,谁就用
try...catch...
捕获异常并处理。
4.3.3 throws 和 throw 的区别
throws | throw |
---|---|
用在方法声明后 ,跟的是异常类名 | 用在方法体内 ,跟的是异常对象名 |
表示抛出异常,由该方法的调用者来处理 | 表示抛出异常,由方法体内的语句处理 |
表示出现异常的一种可能性,并不一定发生 | 执行 throw 一定抛出了某种异常 |
4.4 综合应用
案例: 键盘录入姓名和年龄。姓名长度在3-10位;年龄范围18-25岁。超出这个范围是异常数据,需要重新录入,直到录入正确。
import java.util.Scanner;
public class ExceptionTest {
public static void main(String[] args) {
Student student = new Student();
Scanner sc = new Scanner(System.in);
while (true) {
try {
System.out.println("请输入姓名:");
String name = sc.nextLine();
student.setName(name);
System.out.println("请输入年龄:");
String ageStr = sc.nextLine();
int age = Integer.parseInt(ageStr);
student.setAge(age);
//只要执行到这一步,说明没有任何异常,跳出循环。
break;
} catch (NumberFormatException e) {
System.out.println("年龄格式有误,请输入数字");
} catch (RuntimeException e) {
System.out.println("姓名长度有误或年龄范围有误");
}
}
System.out.println(student);
}
}
class Student {
private String name;
private int age;
//省略空参、带参构造,get方法。
public void setName(String name) {
//姓名长度在3-10位
if (name.length() < 3 || name.length() > 10) {
throw new RuntimeException();
}
this.name = name;
}
public void setAge(int age) {
//年龄范围18-25岁
if (age < 18 || age > 25) {
throw new RuntimeException();
}
this.age = age;
}
public String toString() {
return "Student{name = " + name + ", age = " + age + "}";
}
}
6 自定义异常
6.1 自定义异常简述
在4.4 案例中,虽然抛出、捕获并处理了异常,但是Java并没有提供一个能完美描述”姓名长度有误”,“年龄范围有误“的异常。我们只能通过 RuntimeException
统一处理,显然这是不够直观的,此时我们可以自定义一个异常来描述。
自定义异常的目的: 让控制台的报错信息更加直观,见名知意。
6.2 自定义异常书写步骤
- 定义异常类。名字要见名知意。
- 提供继承关系。运行时异常继承
RuntimeException
,编译时异常直接继承Exception
- 空参、带参构造。
- 在出现异常的地方用
throw new
自定义异常对象抛出
格式:
public class 异常类名 extends 异常 {
无参构造方法;
带参构造方法;
}
案例: 使用自定义异常改写”键盘录入姓名和年龄。姓名长度在3-10位;年龄范围18-25岁。超出这个范围是异常数据,需要重新录入,直到录入正确。“
自定义异常类
//自定义异常:姓名格式化异常
public class NameFormatException extends RuntimeException {
public NameFormatException() {
}
public NameFormatException(String message) {
super(message);
}
}
//自定义异常:年龄越界异常
class AgeOutOfBoundsException extends RuntimeException {
public AgeOutOfBoundsException() {
}
public AgeOutOfBoundsException(String message) {
super(message);
}
}
JavaBean类
public class Student {
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
//限制姓名长度应在3-10位
if (name.length() < 3 || name.length() > 10) {
throw new NameFormatException("姓名长度应在3-10位");
}
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
//限制年龄范围应在18-25岁
if (age < 18 || age > 25) {
throw new AgeOutOfBoundsException("年龄范围应在18-25岁");
}
this.age = age;
}
public String toString() {
return "Student{name = " + name + ", age = " + age + "}";
}
}
测试类
public class ExceptionTest {
public static void main(String[] args) {
Student student = new Student();
Scanner sc = new Scanner(System.in);
while (true) {
try {
System.out.println("请输入姓名:");
String name = sc.nextLine();
student.setName(name);
System.out.println("请输入年龄:");
String ageStr = sc.nextLine();
int age = Integer.parseInt(ageStr);
student.setAge(age);
//只要执行到这一步,说明没有任何异常,跳出循环。
break;
} catch (NumberFormatException e) {
e.printStackTrace();
} catch (NameFormatException e) {
e.printStackTrace();
} catch (AgeOutOfBoundsException e) {
e.printStackTrace();
}
}
System.out.println(student);
}
}
运行测试: