异常的处理机制可以确保我们程序的健壮性,提高系统可用率

基本概念

定义

异常情形是指阻止当前方法或者作用域继续执行的问题
Java 中采用“类”去模拟异常,类可以创建对象

1
2
3
4
5
NullPointerException e = 0x1234;
e 是引用类型, e 中保存的内存地址指向堆中的“对象”
这个对象一定是 NullPointerException 类型
这个对象就表示真实存在的异常事件
NullPointerException 则表示一类异常

出现异常的情况:只有在当前的环境下程序无法正常运行,这时它就会从当前环境中跳出,并抛出异常

  • 用户输入了非法数据
  • 要打开的文件不存在
  • 网络通信时连接中断,或者 JVM 内存溢出

异常处理机制的作用

程序发生异常事件之后,为我们输出详细的信息,程序员通过这个信息,可以对程序进行处理,使程序更加健壮

eg. JVM 自动创建 ArithMeticException 类型的对象包含了对象的详细信息

1
2
3
4
5
6
7
8
9
10
11
public class ExceptionTest01 {

public static void main(String[] args) {

int a = 10;
int b = 0;

int c = a/b; //JVM 自动创建了对象
//ArithMeticException e = 0x2356;
}
}

异常的用途

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Calculator {
public static void main(String[] args) {
double i = devide(10, 0);
System.out.println(i);
}
public static int devide(int num1, int num2) {
//判断除数是否为0
if(num2 == 0) {
throw new IllegalArgumentException("除数不能为零");
}

return num1/num2;
}
}

以上程序编译通过了,但是运行时出现了异常,表示发生某个异常事件
JVM 向控制台输出了如下信息:

1
2
Exception in thread "main" java.lang.ArithmeticException: / by zero
at com.day032801.ExceptionTest01.main(ExceptionTest01.java:10)

本质:程序执行过程中发生了算数异常事件,JVM 为我们创建了一个 ArithMeticException 类型的对象
并且这个对象中包含了详细的异常信息,并且 JVM 将这个对象中的信息输出到控制台

异常的体系结构

通过以上示例,可以看到 Java 给我们提供了这样的体系结构:当出现问题的时候,它会告诉我们,并且把错误的详细信息也告诉我们了,这就是异常的体系结构,这样我们的程序就会更健壮,我们可以把这个信息再进行处理一下,告诉用户。从上面大家还可以看到,Java 异常都是类,在异常对象中会携带一些信息给我们,我们可以通过这个异常对象把信息取出来

异常类的层次结构

异常类的层次结构

常见的异常类型

常见的异常类型

常见异常类型的处理方案

1
1. java.lang.NullPointerException(空指针异常)

空指针异常发生在对象为空,但是引用了这个对象的方法

1
2
String s = null;//此时 s 为空
int length = s.length();//发生空指针异常

1
2. java.lang.ClassNotFoundException(指定的类型不存在)

检查类的名称和路径是否正确,比如调用 Class.forName() ,类的名称不正确

1
3.java.lang.NumberFormatException(字符串转换为数字异常)

当试图将一个 String 转换为指定的数字类型,而该字符串却不满足数字类型要求的格式时,抛出该异常
如现在讲字符型的数据 123456 转换为数值型数据时,是允许的
但是如果字符型数据中包含了非数字型的字符,如 123#56 ,此时转换为数值型时就会出现异常。系统就会捕捉到这个异常

1
4.java.lang.IndexOutOfBoundsException(数组下标越界异常)

调用数组超过数组的长度,最好先查看一下数组的length,以免出现这个异常

1
5.java.lang.IllegalArgumentException(方法的参数错误)

比如 g.setColor(int red,int green,int blue) 这个方法中的三个值,如果有超过 255 的也会出现这个异常,因此一旦发现这个异常,我们要做的,就是赶紧去检查一下方法调用中的参数传递是不是出现了错误

1
6.java.lang.IllegalAccessException(没有访问权限)

当应用程序要调用一个类,但当前的方法即没有对该类的访问权限便会出现这个异常。对程序中用了 Package 的情况下要注意这个异常

1
7.java.lang.ArithmeticException(数学运算异常)

当算术运算中出现了除以零这样的运算就会出这样的异常

1
8.java.lang.ClassCastException(数据类型转换异常)

当试图将对某个对象强制执行向下转型,但该对象又不可转换为其子类的实例时将引发该异常

1
9.java.lang.FileNotFoundException(文件未找到异常)

当程序试图打开一个不存在的文件进行读写时将会引发该异常。该异常由 FileInputStream,FileOutputStream,RandomAccessFile 的构造器声明抛出
即使被操作的文件存在,但是由于某些原因不可访问,比如打开一个只读文件进行写入,这些构造方法仍然会引发异常

1
10.java.lang.NoSuchMethodException(方法不存在异常)

当程序试图访问(修改或读取)某个方法,但是该方法不存在就会引发异常

1
11.java.lang.InstantiationException(实例化异常)

当试图通过 Class 的 newInstance() 方法创建某个类的实例,但程序无法通过该构造器来创建该对象时引发

  • Class 对象表示一个抽象类,接口,数组类,基本类型
  • 该 Class 表示的类没有对应的构造器
1
12.java.lang.OutOfMemoryException(内存不足错误)

当可用内存不足以让 Java 虚拟机分配给一个对象时抛出该错误

1
13.java.lang.NoClassDefFoundException(未找到类定义错误)

当 Java 虚拟机或者类装载器试图实例化某个类,而找不到该类的定义时抛出该错误

1
2
3
4
5
6
7
14  违背安全原则异常:SecturityException

15 操作数据库异常:SQLException

16 输入输出异常:IOException

17 通信异常:SocketException

异常处理机制的主要组成

1
2
3
4
5
try:监控有可能产生异常的语句块
catch:以合理的方式捕获并处理异常
finally:不管有无异常,一定会执行的语句块(一般用来释放资源等)
throw:手动引发异常
throws:指定由方法引发的异常

throw\throws

1
2
3
public static void main(String[] args) throws Exception {

}

try…catch…finally

1
2
3
4
5
6
7
try{
//所监控的有可能产生异常的语句块
}catch(Exception e){//捕获异常,e 就是所捕获的异常对象
//异常处理:打印异常信息、日志记录等
}finally{
//不管有无异常,一定会执行的语句块(一般用来释放资源)
}

catch 语块可以写多个,但是必须从上到下,从小到大(子类 –> 父类)

深入 throws

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import java.io.*;

public class ExceptionTest03 {
public static void main(String[] args) throws FileNotFoundException{
m1();
//不会输出
System.out.println("Wihieree");
}

public static void m1() throws FileNotFoundException{
m2();
}

public static void m2() throws FileNotFoundException{
m3();
}

public static void m3() throws FileNotFoundException{
new FileInputStream("c:/ab.txt");
//FileInputStream 构造方法声明位置上使用 throws(向上抛)
}
}

输出结果:

1
2
3
4
5
6
7
8
9
Exception in thread "main" java.io.FileNotFoundException: c:\ab.txt (系统找不到指定的文件。)
at java.base/java.io.FileInputStream.open0(Native Method)
at java.base/java.io.FileInputStream.open(FileInputStream.java:219)
at java.base/java.io.FileInputStream.<init>(FileInputStream.java:157)
at java.base/java.io.FileInputStream.<init>(FileInputStream.java:112)
at com.day032801.ExceptionTest03.m3(ExceptionTest03.java:20)
at com.day032801.ExceptionTest03.m2(ExceptionTest03.java:16)
at com.day032801.ExceptionTest03.m1(ExceptionTest03.java:12)
at com.day032801.ExceptionTest03.main(ExceptionTest03.java:6)

使用 throws 处理异常不是真正处理异常而是推卸责任,谁调用的就会抛给谁
因为 m1 方法出现异常采用的是上抛,给了 JVM , JVM 遇到这个异常就会退出 JVM ,下面的程序不会执行
所以不会输出 Wihieree
出现上面输出结果的原因是:

1
2
3
在程序运行过程中发生了 FileNotFoundException 类型的异常
JVM 为我们创建了一个 FileNotFoundException 类型的对象
该对象中携带上面的信息,JVM 负责将该对象的信息打印到控制台,并且 JVM 停掉了程序的运行

深入 try…catch…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import java.io.FileInputStream;
import java.io.FileNotFoundException;

public class ExceptionTest04 {
public static void main(String[] args) {
try{
//程序执行到此处发生了 FileNotFoundException 类型的异常
//JVM 会自动创建一个 FileNotFoundException 类型的对象
//将该对象的内存地址赋值给 catch 语句块中的 e 变量
FileInputStream fis = new FileInputStream("abc");
//上面的代码出现了异常,try 语句块的代码不再继续执行,直接进入 catch 语句块中执行
System.out.println("WiHieree");
}catch(FileNotFoundException e){
//e 内存地址指向堆中的那个对象是 “FileNotFoundException 类型的”事件
System.out.println("读取的文件不存在!");
//FileNotFoundException 将 Object 中的 toString 方法重写
System.out.println(e);//java.io.FileNotFoundException: abc (系统找不到指定的文件。)
}
}
}

getMessage() 和 printStackTrace() 方法

printStackTrace() 输出信息更详细,更适合用于调试程序

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

public class ExceptionTest04 {
public static void main(String[] args) {
try{
FileInputStream fis = new FileInputStream("abc");
}catch(FileNotFoundException e){

//打印异常堆栈信息
//一般情况下都会使用该方式去调试程序
e.printStackTrace();
/*
java.io.FileNotFoundException: abc (系统找不到指定的文件。)
at java.base/java.io.FileInputStream.open0(Native Method)
at java.base/java.io.FileInputStream.open(FileInputStream.java:219)
at java.base/java.io.FileInputStream.<init>(FileInputStream.java:157)
at java.base/java.io.FileInputStream.<init>(FileInputStream.java:112)
at com.day032801.ExceptionTest04.main(ExceptionTest04.java:9)
*/

String msg = e.getMessage();
System.out.println(msg); //abc (系统找不到指定的文件。)上面方法的简略
}
//这段代码会执行
System.out.println("WiHieree");//WiHieree
}
}

finally 关键字

finally 语句块可以直接和 try 语句块联用 try…finally…
try…catch…finally 也可以
在 finally 语句块中的代码是一定会执行的,但只要在 finally 之前退出了 JVM,finally 语句就不会执行
eg. System.exit(0);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//深入 finally 语句块
public class ExceptionTest05 {
public static void main(String[] args) {
int i = m1();
System.out.println("main 的 i --> " + i);//10
}
public static int m1(){
int i = 10;
try{
return i; //10
}finally {
i++;
System.out.println("finally 的 i --> " + i);//11
}
}
}

//输出结果
finally 的 i --> 11
main 的 i --> 10

注意 mian 方法返回的 i 是 10,接受的是 try 里 return 的 i

try…catch…finally

finally 语句块是一定会执行的,所以通常在程序中
为了保证某资源一定会释放,所以一般在 finally 语块中释放资源

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

public class ExceptionTest06 {

public static void main(String[] args) {

//必须在外面声明
FileInputStream fis = null;
try{
fis = new FileInputStream("E:/theme-66.epf");
}catch(FileNotFoundException e){
e.printStackTrace();
}finally {
//避免空指针异常,保证资源一定会释放
if (fis!=null){
try{
fis.close();
}catch(IOException e){
e.printStackTrace();
}
}
}
}
}

//输出结果

输出结果为空,因为没有异常,在 finally 已经释放资源

自定义异常

异常的完整应用,其中包含手动抛出异常
eg. 需求自定义注册“无效名字异常”

1
2
3
4
5
6
7
8
9
10
11
//自定义一个异常
public class IllegalNameException extends Exception{//编译时异常
//public class IllegalNameException extends Exception{//运行时异常

//定义异常一般提供两个构造方法
public IllegalNameException(){}

public IllegalNameException(String msg){
super(msg);
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//顾客相关的业务
public class CustomerService {

//对外提供一个注册的方法
public void register(String name) throws IllegalNameException{

//完成注册
if (name.length()<6){
//异常
//创建异常对象
//IllegalNameException e = new IllegalNameException("用户名长度不能少于 6 位");
//手动抛出异常
//throw e;
throw new IllegalNameException("用户名长度不能少于 6 位");
}

//如果代码能执行到此处,证明用户名是合法的
System.out.println("注册成功!");
}
}
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 void main(String[] args) {

//加入用户提供的用户名如下
String username = "wihie";
//注册
CustomerService cs = new CustomerService();
try{
cs.register(username);
}catch(IllegalNameException e){
System.out.println(e.getMessage());
}

}
}

//输出结果
用户名长度不能少于 6

方法的重写与异常

重写的方法不能比被重写的方法抛出更宽泛的异常

1
2
3
4
5
6
7
8
9
class A{
public void m1(){}
}

class B extends A{

//子类永远无法抛出比父类更多的异常
public void m1() throws Exception{}
}

以上编译不通过

1
2
3
4
5
6
7
8
9
10
11
import java.io.FileNotFoundException;
import java.io.IOException;

class A{
public void m1() throws IOException {}
}

class B extends A{

public void m1() throws FileNotFoundException{}
}

以上程序编译通过


 Comments