当前位置: 首页 > java>阅读正文

java的异常体系

2021.10.2 朱丰华 418 次 留下评论 5879字

java程序异常,指的是程序出现了预料之外的事情。

  • 编译期异常:也称为检测异常,不符合编译规则,无法转换为字节码文件
  • 运行期异常:在程序运行期间出现了异常。

java中的异常是类,异常的根类是java.lang.Throwable继承于Object,子类有:

  • Error:严重的异常,也表示错误
  • Exception:一般的异常
    • RuntimeException:运行期异常
    • 其他Exception

异常处理机制

对于RuntimeException及其子类,是运行期异常,也就是在运行中才可能出现,所以你可以不处理,一旦发生异常JVM会捕获,并终止程序执行。当然你也可以手动捕获异常,编写处理方式,这样它不会终止程序而是继续运行。

对于“其他Exception”,这些异常是编译期异常,你必须处理,否则无法通过编译,对此你有2种处理方式:

  • 捕获异常对象并处理
  • 声明抛出异常对象

编译通过后,程序出现异常根据处理代码进行处理,并继续运行。

对于“Error”,一定会导致程序终止,只能修改代码。

详细处理流程

程序出现异常后,JVM会创建一个异常对象,该对象中保存了异常的相关信息。

JVM判断该异常代码是否有捕获处理机制,如果该机制捕获了异常,则由它进行处理,否则判断调用它的上一级是否有处理机制,不断向上传递,最终到main方法,如果main中还未处理,则交给JVM处理

JVM对异常的处理非常简单,就是打印错误信息,终止程序。

throw关键字

throw用于“创建异常”,为什么要创建异常??异常本身就是为了辅助程序正常运行的、认为制造的,所以首先要创建异常。也有的称之为“抛出异常”,不过这种说法容易产生歧义。

语法格式:

throw new xxxException("异常产生的原因");
  • throw 必须写在方法体内
  • throw new 后面的对象应该是 Exception 或它的子类(最大Throwable)
  • 如果是 RuntimeException 可以不手动处理,JVM 也会处理
  • 对于其他的 Exception 子类,都是编译期异常,必须手动处理

例子:

public static void main(String[] args) {
    int[] a = null;
    throwException(a);
}

private static void throwException(int[] a) {
	if(a==null) {
		throw new NullPointerException("数组为 null");
	}
}

注释:一旦 throw 了异常,无论它是什么异常,只要你没有立刻捕获它,后续就不能再写代码了,返回值也不能再写了,因为异常了就要走异常处理机制,该代码无法按照正常的流程执行。

throws关键字

和throw很像,但完全不同,throws用于“声明抛出异常”,声明两字很重要,只使用“抛出异常”容易和throw产生歧义。

声明抛出异常,指的是不处理异常,让调用它的人去处理,当然调用它的人也可以继续声明抛出。不过异常对象就只有一个,无论谁抓住了它就不会再往上抛出了。

基本语法:

修饰符 返回值类型 方法名(参数列表) throws AAAException,BBBException...{
    throw new  AAAException("抛出原因");
    method(); // 抛出BBBException
    ...
}
  • throws 必须写在方法声明处
  • throws 抛出的应该是 Exception 或其子类(最大Throwable)
  • RuntimeException异常可以不抛出,也不处理
  • 如果声明抛出的多个 Exception 有父子关系,只需要声明父Exception
  • 子类方法覆盖无法声明抛出父类方法不存在的异常

更通俗的说,子类异常比父类异常更详细,范围更小,那么throws抛出异常时,抛出一个大异常,显然就包括了小异常。而对于覆盖的方法,你本应该进一步处理它,所以你若再抛出,就不能抛出更大的异常。

public static void main(String[] args) throws IOException {
    check("c:\\a.txt");
}
public static void check(String fileName) throws IOException {
	if(fileName.equals("d:\\a.txt")) {
		throw new FileNotFoundException();
	}
	File f = new File(fileName);
	f.createNewFile();  //throw IOException
}

try-catch-finally

这是java捕获异常,并处理异常的机制。

try{
  //可能出现异常的代码
}catch(异常对象){
  //处理异常对象
}finally{
  //无论是否出现异常,都要执行的代码块;
}

主要注意的是,try可以随意与catch或finally搭配,这意味着try可以与catch、finally中随意的一个,或2个一起搭配使用。

多个catch模块

try{
  //可能产生异常的语句;
}catch(异常对象){
  //处理异常对象的语句
}catch(异常对象2){
  //处理异常对象2的语句
}...

一但try中出现异常,那么跳出try后续内容,直接到catch,在捕获时,可以捕获多个异常对象,同级异常没有顺序,否则你应该先捕获小异常,再捕获大异常,或者一次抓一个大的就包括了所有。

由于异常对象只有一个,那么也就说明了异常对象只会被一个catch模块所捕获,无论有多少个catch,因为try只会产生一个异常的,导致了只有一个catch模块生效。

finally的存在是必要的,如果try中没有异常,那么catch模块中的内容就不会被执行,那么对于一些释放资源操作,无论是catch处理,还是不处理,它都应该如期释放,以免浪费内存,我们不应该在try中写一次,而catch中也写一次,我们知道try和catch处于两个不同的作用域。所以对于这样的操作,直接放在finally中即可。

public static void main(String[] args) {
	try{
	    int[] a = new int[3];
	    System.out.println(a[4]);  //出现异常的语句
	    System.out.println("后续代码");
	}catch(Exception e) {
		System.out.println(e);
	}finally {
		System.out.println("finally 中的后续代码");
	}
}

throwable类

作为异常的根类,它给我们什么可继承方法?

  1. public String getMessage : 获得异常消息字符串
  2. public String toString() : 获得异常对象名和异常消息
  3. public void printStrackTrace() : 打印最详细的错误信息

最简单的getMessage,可以得到异常消息的字符串描述。

如果直接打印异常对象,因为继承于Object,那么会触发toString方法。

而printStrackTrace,则表示“打印异常跟踪的堆栈信息”,会打印出初始错误,以及一连串的调用关系,这是最详细的错误信息。

public static void main(String[] args) {
    try {
		check("d:\\b.txt");
	} catch (FileNotFoundException e) {
		System.out.println(e.getMessage());
		System.out.println(e);
		e.printStackTrace();
	}
}
public static void check(String fileName) throws FileNotFoundException  {
	if(!fileName.equals("d:\\a.txt")) {
		throw new FileNotFoundException("文件位置不存在");
	}
	File f = new File(fileName);
	try {
		f.createNewFile();
	} catch (IOException e) {
		e.printStackTrace();
	}
}

对于输出结果:

文件位置不存在
   //getMessage
java.io.FileNotFoundException: 文件位置不存在
  //toString
java.io.FileNotFoundException: 文件位置不存在
  //printStackTrace
	at test.Test.check(Test.java:22)
	at test.Test.main(Test.java:13)

自定义异常类

现有的异常不够我们使用,可以手动自定义

1.如何自定义?

创建一个类,类名 XXXException

继承 Exception 或 RuntimeException

创建一个空参构造

创建一个有参构造,String 类的 message ,直接传给父类构造方法处理

2.例子

下面定义了一个 MxException,有一个空参,和一个有参构造

public class MxException extends Exception {
	public MxException() {
		super();
	}
	public MxException(String message) {
		super(message);
	}
}

JDK新特性

jdk7新特性

把可能出异常的变量,定义在 try() 的小括号中,作用域只在 try 中, 不需要finally

try(FileWriter fw = new FileWriter("D:\\a.txt",true);){
    //可能会产生异常的代码
    for(int i=0;i<10;i++){
        fw.write("java教程"+i+"\r\n");
    }
}catch(IOException e){
    //处理异常
    System.out.println(e);
}

jdk9新特性

在 try() 的小括号中,引入外部定义的变量,它的作用域也会限定在 try 中

FileWriter fw = new FileWriter("D:\\a.txt",true); //定义抛出异常
try(fw){
 //多个变量用  分号;  隔开
    //可能会产生异常的代码
    for(int i=0;i<10;i++){
        fw.write("java教程"+i+"\r\n");
    }
}catch(IOException e){
    //处理异常
    System.out.println(e);
}
//一旦超出了 try 定义域,流会自动关闭,
//虽然 fw 作用域在外部,但使用会报错

常见的异常

java的异常非常多,所幸你不需要记住它们,这里仅给新手一些异常查阅

异常名称异常描述
ParseException转换异常
FileNotFoundException文件未找到异常
IOExceptionIO流异常
ClassNotFoundException类未找到异常
NullPointerException空指针异常
NumberFormatException数值转换失败异常
ArrayIndexOutOfBoundsException数组下标越界异常
StringIndexOutOfBoundsException字符串下标越界异常
ArithmeticException除数为0异常
RuntimeException运行期异常
ClassCastException类实例化异常

异常自测题

1.throw和throws有什么区别?

2.发生异常后,如何跟踪堆栈异常信息?

3.一个方法声明抛出异常,调用者如果不进行任何处理,程序能否运行?

4.在一个方法中,能否既throw抛出异常,又把它捕获?如果可以,方法调用者是否需要处理该异常?如果不可以,为什么不可以?

5.以下代码能否运行?如果错了,哪行错了?为什么错?

//import java.io.*;
public static void main(String[] args){
    try(FileWriter fw = new FileWriter("D:\\a.txt",true);){
        for(int i=0;i<10;i++){
        fw.write("abc"+i+"\r\n");
        }
    }catch(IOException e){
        System.out.println(e);
    }finally{
        fw.close();
    }
}

6.以下checkPassWord方法代码是否错误?如果错了,哪行错了?为什么错?如果正确,运行结果是什么?

public static void main(String[] args){

    String pwd = "12345678";
    System.out.println("密码"+pwd+"是否符合要求?"+checkPassWord(pwd));
}

public static boolean checkPassWord(String pwd){
// 检测pwd字符串,至少包含8个字符,如果不符合则抛出一个java.lang.NumberFormatException
    if(pwd.length()<8){
        throw new NumberFormatException("密码错误");
        return false;
    }
    else {
        return true;
    }
}

7.以下代码执行结果是什么?

public static void main(String[] args){
    int[] a = new int[10];
    try{
        System.out.println(a[10]);
        System.out.println(a[1]);
    }catch (Exception e){
        System.out.println(1);
    }
    try{
        System.out.println(a[10]);
    }catch (Exception e){
        System.out.println(2);
    }
    System.out.println(a[1]);
}

8.如果在main中声明抛出异常,一旦发生异常,main会将异常抛给谁?程序是否会被终止?这说明了什么?

9.在使用try-catch时,能否捕获编译期异常?能否捕获运行期异常?能否捕获Error?能否捕获Throwable?能否捕获Object?

10.方法覆盖后,能否抛出抛出(throw)更大的异常?能否声明抛出(throws)更大的异常?

11.调用方法时,不捕获异常,能否抛出(throw)更小的异常?能否声明抛出(throws)更小的异常?

12.方法中抛出(throw)异常时,本身也不捕获,是否需要声明抛出(throws)异常?

本篇完,还有疑问?留下评论吧

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注