异常分类
图示梳理
各类含义
Throwable
:异常的基类,所有异常都继承自java.lang.Throwable
类,Throwable
类有两个直接子类:Error
类和Exception
类。Error
:是 Java 应用程序本身无法恢复的严重错误,应用程序不需要捕获、处理这些严重错误。通常情况下,程序员无需处理此类异常。Exception
:由 Java 应用程序抛出和处理的非严重错误(即异常),也是我们本章重点学习的对象。异常可分为运行时异常(RuntimeException
)和检查时异常(CheckedException
)两种。RuntimeException
:运行时异常,即程序运行时抛出的异常,程序员在编程时即使不做任何处理,程序也能通过编译。前面数组下标越界异常和除数为 0 的异常都是运行时异常。CheckedException
:检查时异常,又称为非运行时异常,这样的异常要求程序员必须在编程时进行处理,否则就会编译不通过。需要特别注意的是,在 JDK 的异常定义体系中(即在所有Throwable
的子类中),并不存在真正的CheckedException
类。也就是说,上图中的所有类名,都能在 JDK 中找到对应的 API,但唯独CheckedException
类并不是真实存在的。一般而言,如果一个类继承自RuntimeException
,就称此类为运行时异常;反之,如果一个类没有继承RuntimeException
,但继承了Exception
或Throwable
,就称此类为检查时异常。
异常的两种处理方式
捕获异常并处理
先在一段可能抛出异常的代码外,用
trycatch结构包裹起来。如果运行时发生了异常,那么此次异常就会进入
catch代码块;如果一直没有异常发生,程序就不会进入
catch代码块。并且
catch可以针对抛出的不同类型的异常捕获后进行分类处理。
基本语法格式
try {
//可能抛出异常的语句块
}catch(SomeException1 e) { // SomeException1 特指某些异常,非 Java 中具体异常,下同
//当捕获到 SomeException1 类型的异常时执行的语句块
} catch(SomeException2 e) {
//当捕获到 SomeException2 类型的异常时执行的语句块
} finally {
//无论是否发生异常都会执行的代码
}
还可以使用catch(Exception 基类)
一次性捕获所有类型的异常
try{
//可能抛出异常的语句块
} catch(Exception1 e) { // 一次性捕获所有类型的异常
//当捕获到异常时执行的语句块
} finally {
//无论是否发生异常都会执行的代码
}
例子
将孩子人数列出
public class exception {
public static void main(String[] args){
try{
String teachers[] = {"柳海龙", "孙传杰", "孙悦"};
for (int i = 0; i < 4; i++) {
System.out.println(teachers[i]);
}
}catch(Exception e){
System.out.println("数组下标越界,请修改程序!");
}
System.out.println("显示完毕!");
}
}
接下来进阶一下,了解孩子之后给孩子分苹果,将try...catch...插入其中进行捕获异常
import java.util.Scanner;
public class TestEx6 {
public static void main(String[] args) {
try {
String teachers[] = {"柳海龙", "孙传杰", "孙悦"};
for (int i = 0; i < teachers.length; i++) {
System.out.println(teachers[i]);
}
int appleNum = 0; //苹果数
int stuNum = 0; //学生数
System.out.println("**_现在给孩子们分苹果_**");
Scanner input = new Scanner(System.in);
System.out.print("请输入桌子上有几个苹果:");
appleNum = input.nextInt();
System.out.print("请输入班上有几个孩子:");
stuNum = input.nextInt();
System.out.println("班上每个孩子分得多少苹果:" + appleNum / stuNum);
System.out.println("孩子们非常开心!");
} catch (Exception e) {
System.out.println("数组下标越界,请修改程序!");
}
System.out.println("显示完毕!");
}
}
由图中运行的结果可知本应为数字0的算数异常,显示的确实数字下标异常,其原因为catch(Exception e)捕获了所有的异常,无论为啥异常都会抛出数字下标越界异常 。所以,不能因为快捷而一直使用catch(Exception)捕获异常,有时还需要对特殊情况进可能捕获不同类型的异常。
接下来我们对catch(Exception)
进行一些修改
tyr{
//.........省略
}catch (ArrayIndexOutOfBoundsException e) {
System.out.println("数组下标越界,请修改程序!");
}catch (ArithmeticException e){
System.out.println("算术异常");
}
出图中可以看到数字的算数异常现在不会被数组下标越界异常捕获
我们将代码完整性写出来,并加以修改
package Exception;
import javax.naming.AuthenticationException;
import java.util.Scanner;
public class exception2 {
public static void main(String[] args) {
try {
String teachers[] = {"柳海龙", "孙传杰", "孙悦"};
for (int i = 0; i < teachers.length; i++) {
System.out.println(teachers[i]);
}
input();
System.out.println("孩子们非常开心!");
}catch (ArrayIndexOutOfBoundsException e) {
System.out.println("数组下标越界,请修改程序!");
input();
}catch (ArithmeticException e){
System.out.println("算术异常");
input();
}catch(Exception e){
System.out.println("其他异常");
}
System.out.println("显示完毕!");
}
//出入数据入口
public static void input(){
int appleNum = 0; //苹果数
int stuNum = 0; //学生数
System.out.println("**_现在给孩子们分苹果_**");
Scanner input = new Scanner(System.in);
System.out.print("请输入桌子上有几个苹果:");
appleNum = input.nextInt();
System.out.print("请输入班上有几个孩子:");
stuNum = input.nextInt();
System.out.println("班上每个孩子分得多少苹果:" + appleNum / stuNum);
}
}
当特定的实现异常完成后通常在后面在接上catch(Exception e)
来排除其他异常
效果见图所示,当输入的数值异常被捕获在递归调用input方法实现重新输入
抛出异常
通过 throws关键字,将异常抛给上一级处理。
抛出异常的规则:如数组下标越界异常 ArrayIndexOutOfBoundsException
是运行时异常 RuntimeException
的子类,而运行时异常 RuntimeException
又是 Exception
异常的子类,在捕获异常的时候,应该按照“从小到大”的顺序捕获异常,这样才能保证逐层捕获。将上面的代码调整为先捕获数组下标越界异常ArrayIndexOutOfBoundsException
,再捕获运行时异常 RuntimeException
,最后,为了防止遗漏对某个异常的处理,一般建议在最后用catch (Exception e)
捕获剩余的异常
基本语法格式
修饰符 返回值类型 方法名(参数) throws 异常类名1,异常类名2…{ }
例子
import java.net.*; //导入 Java 网络包
import java.io.*; //导入 I/O 包
public class TestEx8 {
//声明服务器端套接字对象
public static ServerSocket ss = null;
//暂不理会throws IOException代码的含义,之后的课程会详细介绍
public static void main(String[] args) throws IOException {
try {
//实例化服务器端套接字,服务器端套接字等待请求通过网络传入
ss = new ServerSocket(5678); //其中5678为端口号
//侦听并接受此套接字的连接
Socket socket = ss.accept();
//省略其他代码
//当发生某种I/O异常时,抛出IOException异常
} catch (IOException e) {
//关闭此套接字
ss.close();
//省略其他代码
}
//省略其他代码
}
}
finally
在
try
语句块中实例化出一个服务器端套接字对象并调用对象的方法,如果try
语句块中出现IOException
异常,则catch
语句块进行捕获和处理,关闭这个服务器套接字,并执行其他操作。但如果程序没有抛出IOException
异常,正常执行,则关闭服务器端套接字的代码将不会执行,这个套接字不会被关闭,而是继续占用系统资源,这并不是程序开发人员希望的,所以finally
语句正是避免这种现象的存在。
使用 finally
语句块,保证无论是否发生异常,finally
语句块中的代码总被执行
package Exception;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class sockexception {
public static ServerSocket ss = null;
public static void main(String[] args) throws IOException {
try {
ss = new ServerSocket(5678);
Socket socket = ss.accept();
//省略其他代码
} catch (IOException e) {
//省略其他代码
} finally {
//关闭此套接字
ss.close();
}
//省略其他代码
}
}
思考?
如果在 try
语句块中或者 catch
语句块中存在 return 语句,finally
语句块中的代码还会执行吗?不是说 return 语句的作用是将结果返回给调用者,而不再执行 return 语句后面的代码吗?
答:Java 异常处理机制对这个问题的处理是,当 try
或 catch
语句块中有 return 语句时,先执行 try
或 catch
语句块中 return 语句之前的代码,再执行 finally
语句块中的代码,之后再返回。所以,即使在 try
或 catch
语句块中有 return 语句,finally
语句块中的代码仍然会被执行。在异常处理结构中,finally
语句块不执行的唯一一种情况就是如果在 catch
语句中出现 System.exit()
,该方法表示关闭 JVM
package Exception;
import java.util.Scanner;
public class exception3 {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
int no1 = 0;
int no2 = 0;
while (true){
try {
no1 = input.nextInt();
no2 = input.nextInt();
int num = no1 / no2;
}catch (ArithmeticException e){
System.out.println("算数错误");
}catch (Exception e){
System.out.println("出错");
//return ; finally语句还是能够执行
//System.exit(1) finally不会执行
}finally {
System.out.println("程序完成");
}
}
}
}
将return注释消掉,如图所示
将System.exit(1)注释消掉,如图所示
异常对象
在实际编程中,常用的异常对象的方法有两个:一个方法是 printStackTrace(),用于输出异常的堆栈信息,其中堆栈信息包括程序运行到当前类的执行流程,显示方法调用序列;另一个方法是 getMessage(),用于返回异常详细信息的字符串
下面看例子进行解释
package Exception;
public class exceptionObject {
public static void main(String[] args) {
String[] people = {"老王","李丽","小风","大兵"};
try{
for (int i = 0; i <= people.length; i++) {
System.out.println(people[i]);
}
}catch(ArrayIndexOutOfBoundsException e){
System.out.println("调用异常对象的getMessage()方法");
System.out.println(e.getMessage());
System.out.println("调用异常对象的 printStackTrace()方法");
e.printStackTrace();
}finally {
System.out.println("排除完毕");
}
}
}
程序捕获数组下标越界异常之后,先输出异常对象的 getMessage()
方法的结果,之后再调用异常对象的 printStackTrace()
方法输出堆栈信息
throw 和 throws 关键字
thow:
在 Java 语言中,可以使用 throw
关键字手工抛出一个异常,语法形式如下
//手工抛出一个算数异常的代码
throw new ArithmeticException();
简单例子
package Exception;
public class exception4 {
public static void main(String[] args) {
System.out.print("我");
try {
System.out.print("是");
throw new NullPointerException("你");
}catch (NullPointerException e){ //捕获抛出的异常
System.out.print(e.getMessage());
}
System.out.println("朋友");
}
}
当 catch
语句捕获到异常对象 e
并处理后,还可以在 catch
语句块中,继续使用 throw e;
语句再次将这个异常抛出
thows:
throws
用于声明一个方法会抛出异常,就是当方法本身不知道或者不愿意处理某个可能抛出的异常时,可以选择用 throws
关键字将该异常提交给调用该方法的方法进行处理
package Exception;
import java.util.Scanner;
public class exception5 {
public static void main(String[] args) {
try{
arithmetic();
}catch (ArithmeticException e){
System.out.println("算数错误");
}
}
//随便乱写一个方法
public static void arithmetic() throws ArithmeticException{
Scanner input = new Scanner(System.in);
int num1 = 0,num2 = 0;
num1 = input.nextInt();
num2 = input.nextInt();
System.out.println("除法结果" + (num1/num2));
}
}
arithmetic()
方法声明该方法可能抛出 ArithmeticExceptio
异常,调用 arithmetic()
方法的 main()
方法处理了 arithmetic()
方法中不处理并声明抛出的这个异常
自定义异常类
自定义异常,顾名思义,就是程序员自己定义的异常,当 Java 类库中的异常不能满足程序需求时,程序员可以自己定义并使用异常。自定义异常可以任意的继承一个异常类。但 Exception
类是 Java 中所有异常类的父类,所以在定义自定义异常类时,通常继承自该类
//自定义异常类,处理年龄大于 120 或小于 0 的 Person
class AgeException extends Exception {
private String message;
public AgeException(int age) //自定义异常类构造方法
{
message = "年龄设置为:" + age + "不合理!";
}
public String toString() //自定义异常类toString()方法
{
return message;
}
}
class Person {
private int age;
//声明setAge(int age)方法可能抛出AgeException自定义异常
public void setAge(int age) throws AgeException {
if (age <= 0 || age >= 120) {
throw new AgeException(age);//抛出AgeException自定义异常
} else {
this.age = age;
}
}
public int getAge() {
return age;
}
}
public class TestEx18 {
public static void main(String[] args) {
Person p1 = new Person();
Person p2 = new Person();
try {
p1.setAge(150); //会抛出 AgeException 自定义异常
System.out.println("正确输出年龄为:" + p1.getAge());
} catch (AgeException e) { //进行异常捕获处理
System.out.println(e.toString());
}
try {
p2.setAge(60); //不会抛出 AgeException 自定义异常
System.out.println("正确输出年龄为:" + p2.getAge());
} catch (AgeException e1) {
System.out.println(e1.toString());
}
}
}
异常类型
异常的类型Java中的异常分为两大类:
- Checked Exception(非Runtime Exception)
- Unchecked Exception(Runtime Exception)
名称 | 类型 |
---|---|
算数异常类 | ArithmeticExecption |
空指针异常类型 | NullPointerException |
类型强制转换类型 | ClassCastException |
数组负下标异常 | NegativeArrayException |
数组下标越界异常 | ArrayIndexOutOfBoundsException |
违背安全原则异常 | SecturityException |
文件已结束异常 | EOFException |
文件未找到异常 | FileNotFoundException |
字符串转换为数字异常 | NumberFormatException |
操作数据库异常 | SQLException |
输入输出异常 | IOException |
方法未找到异常 | NoSuchMethodException |
下标越界异常 | IndexOutOfBoundsExecption |
系统异常 | SystemException |
创建一个大小为负数的数组错误异常 | NegativeArraySizeException |
数据格式异常 | NumberFormatException |
安全异常 | SecurityException |
网络操作在主线程异常 | NetworkOnMainThreadException |
请求状态异常 | IllegalStateException |
父类 | IllegalComponentStateException |
网络请求异常 | HttpHostConnectException |
子线程Thread更新UI view 异常 | ViewRootImpl$CalledFromWrongThreadException |
证书不匹配的主机名异常 | SSLExceptionero |
反射Method.invoke(obj, args...)方法抛出异常 | InvocationTargetException |
EventBus使用异常 | EventBusException |
非法参数异常 | IllegalArgumentException |
参数不能小于0异常 | ZeroException |
总结
异常处理机制,用于处理程序中可能发生的异常,主要涉及了以下知识点:
- 异常处理机制可以将程序中的业务代码和异常代码相分离,从而使得开发者们可以更加专注于编写业务代码,并使程序更加优雅。
- 异常的继承结构:异常的顶层基类是
Throwable
;继承自RuntimeException
的类称为运行时异常,否则称为检查异常。 - 运行时异常在编译阶段没有提示,只是在运行时存在异常才抛出;而检查异常是会在编译时就提示开发者必须处理的异常。
- 在异常可能发生的地方,通常有两种预防性处理策略:如果开发者认为此时出现的异常最好立即进行处理,那么就可以采用
try...catch
的方式;反之,如果开发者认为此时出现的异常,暂时可以不进行处理,就可以通过throws
将异常抛出给上一级去处理。 - 异常对象常用方法有两个:一个方法是
printStackTrace()
,用于输出异常的堆栈信息;另一个方法是getMessage()
,用于返回异常详细信息字符串。 - 异常的 5 个关键字:
try
中编写可能存在异常的代码,catch
用于捕获异常并书写异常处理代码,finally
中的代码无论是否出现异常都会被执行(此前有System.exit()
除外),throws
用于声明方法可能会抛出的异常,而throw
表示手工抛出异常即制造异常并抛出。 - 自定义异常:如果 JDK 中已有的异常不能满足开发需求,就需要开发者自定义异常。自定义异常需要继承 JDK 中已有的异常,并且通常会结合
if
语句一起使用。 - 常见的异常类型有
NullPointerException
、ClassNotFoundException
、IllegalArgumentException
、InputMismatchException
、IllegalAccessException
、ClassCastException
、SQLException
和IOException
等,并且随着学习的深入,大家也会接触更多类型的异常。 - 如果某异常类继承自 RuntimeException,则该异常可以不显式的使用“try...catch...”或throws进行处理。