Java异常机制

异常

异常指的就是程序编译或者运行的各种错误状况,如:找不到文件,把0做除数等.

Java中的错误事件分为两种:Error(错误)和Exception(异常),它们都继承自Throwable.

Java异常继承体系结构图如下:

image

1. 异常的分类

1.1 Error

Error通常是指虚拟机无法解决的严重问题,如 OutOfMemoryError (内存溢出错误)

public static void main(String[] args) {
    byte[] b1 = new byte[1024 * 1024 * 1024];
    byte[] b2 = new byte[1024 * 1024 * 1024];
    byte[] b3 = new byte[1024 * 1024 * 1024];
    byte[] b4 = new byte[1024 * 1024 * 1024];
}

image

如上所示,申请空间过大造成系统内存溢出,产生了OutOfMemoryError

Error是严重的错误,会造成系统的崩溃

1.2 Exception

Exception 指的是程序本身可以进行处理或者捕获的异常.

Exception可以分为两种:编译时异常(又称检查异常)和 RuntimeException

1.2.1 RuntimeException

运行时异常是程序运行时出现的异常,RuntimeException是所有运行时异常的父类,如NullPointerException(空指针异常)和IndexOutOfBoundsException(下标越界异常)等.

这种异常属于非检查性异常,程序可以选择捕获,也可以不做处理,一般由程序本身的逻辑错误引起

1.2.2 编译异常(Checked Exception)

是除RuntimeException以外的异常,这种异常必须显式地抛出或捕获,否则将不能编译通过.

如:IOException,SQLException

1.2.3异常的常用方法

Throwable 中提供了很多关于异常的方法

1.public String getMessage()
//返回关于发生的异常的详细信息。这个消息在Throwable 类的构造函数中初始化了。
2.public Throwable getCause()
//返回一个 Throwable 对象代表异常原因。
3.public String toString()
//返回此 Throwable 的简短描述。
4.public void printStackTrace()
//在控制台用标准错误流打印异常信息在程序中出错的位置及原因。
    

public class Exception04 {
    public static void main(String[] args) {
        test01(0);
    }
    public static void test01(int num) {
        try {
            int a = 10 / num;
        } catch (ArithmeticException e) {
            System.out.println("e.getMessage(): "+e.getMessage());
            System.out.println("e.getCause(): "+e.getCause());
            System.out.println("e.toString(): "+e.toString());
            System.out.print("e.printStackTrace(): ");
            e.printStackTrace();
        }
    }
}

image

2.异常的捕获

我们发现,如果发生异常时,如果不对异常进行任何处理,那么程序就会崩溃,而且出现异常后方的代码并不会被执行,这显然不是我们所希望的,这个时候就可以使用try catch去捕获异常

public static void main(String[] args) {
    System.out.println("程序开始执行");
    int a = 10 / 0;
    System.out.println("程序结束执行");//不会被执行

}

image

2.1 try catch

try catch关键字的含义:

  • try:用于监听try代码块中可能产生异常的代码,当产生异常时,异常就会被抛出
  • catch:用于捕获try代码块中产生的异常

try catch的用法如下:

try{
	//可能出现异常的代码
}catch(异常种类 对象名){
	//当try中代码产生异常时执行的操作
}

使用try catch后,可以捕获异常,产生异常后依然不影响系统继续运行,但是try中产生异常的代码后续内容将不会被执行

public static void main(String[] args) {
    System.out.println("程序开始执行");
    try{
        int a = 10 / 0;
        System.out.println("程序正在执行");
    }catch(Exception e){
        System.out.println("异常被捕获了");
    }
    System.out.println("程序结束执行");
}
//程序开始执行
//异常被捕获了
//程序结束执行

当我们希望能捕获多个异常时,可以使用多重catch

try{
	//可能出现异常的代码
}catch(异常种类1 对象名){
	//当try中代码产生异常1时执行的操作
}catch(异常种类2 对象名){
    //当try中代码产生异常1时执行的操作
}...
 catch(异常种类n 对象名){
     //当try中代码产生异常n时执行的操作
}
 public static void main(String[] args) {
     System.out.println("程序开始执行");
     try{
         String str = null;
         str.length();
     }catch(ArithmeticException e){
         System.out.println("异常1被捕获了");
     }catch (NullPointerException e){
         System.out.println("异常2被捕获了");
     }catch (Exception e){
         System.out.println("异常3被捕获了");
     }
     System.out.println("程序结束执行");
 }
//程序开始执行
//异常2被捕获了
//程序结束执行

注意:

在使用多层catch时,要注意优先将具体的异常类写在上面,即保证具体异常优先被捕获.

要保证在上层的异常是下层异常的子类或不具有继承关系.

public static void main(String[] args) {
    System.out.println("程序开始执行");
    try {
        String str = null;
        int length = str.length();
    }catch(RuntimeException e){
        System.out.println("异常1被捕获了");
        }
    } catch (NullPointerException e) {
        System.out.println("异常2被捕获了");
    }

}

如上述代码,NullPointerExceptionRuntimeException的子类,所以下面的catch始终不会捕获到异常,不能编译通过

2.2 try catch finally

当我们希望程序是否产生异常都要执行一些操作时,就可以使用try catch finally,不论如何,finally代码块始终会被执行

 public static void main(String[] args) {
         try{
             System.out.println("程序开始执行");
             int a = 10 / 0;
         }catch(Exception e){
             System.out.println("异常被捕获了");
         }finally {
             System.out.println("程序结束执行");
         }
         
}
//程序开始执行
//异常被捕获了
//程序结束执行

finally中通常用于关闭连接,释放资源,释放锁的操作.如InputStreamOutputStreamScanner 等的资源都需要我们调用close()进行手动关闭,此时就可以使用finally

//读取文本文件的内容
Scanner scanner = null;
try {
    scanner = new Scanner(new File("D://read.txt"));
    while (scanner.hasNext()) {
        System.out.println(scanner.nextLine());
    }
} catch (FileNotFoundException e) {
    e.printStackTrace();
} finally {
    if (scanner != null) {
        scanner.close();
    }
}

如果我们不想捕获异常,只希望在产生异常后执行某些操作的话,也可以使用try finally

try finally语法和try catch finally类似,只是去掉了catch块

public static void main(String[] args) {
    String str = null;
    try {
        str.length();
    } finally {
        System.out.println("我出现了空指针");
    }
}
2.3 try with resource

在jdk7后,引入了try with resource,可以更优雅地释放资源

public static void main(String[] args) {
    BufferedReader br = null;
    String line;
    try {
        System.out.println("Entering try block");
        br = new BufferedReader(new FileReader("test.txt"));
        while ((line = br.readLine()) != null) {
            System.out.println("Line =>"+line);
        }
    } catch (IOException e) {
        System.out.println("IOException in try block =>" + e.getMessage());
    } finally {
        System.out.println("Entering finally block");
        try {
            if (br != null) {
                br.close();
            }
        } catch (IOException e) {
            System.out.println("IOException in finally block =>"+e.getMessage());
        }
    }
}

如上述代码,多层嵌套,显得代码很繁琐,而使用try with resource可以优雅地解决,格式如下:

try(使用的资源){
    //执行的操作
}catch(异常种类 e){
    //产生异常执行的操作
}

上述代码可以用try with resource改写为

public static void main(String[] args) {
     String line;
     try(BufferedReader br = new BufferedReader(new FileReader("test.txt"));) {
         System.out.println("Entering try block");
         while ((line = br.readLine()) != null) {
             System.out.println("Line =>"+line);
         }
     } catch (IOException e) {
         System.out.println("IOException in try block =>" + e.getMessage());
} 

try with resource是一种语法糖,使用try with resource打开资源,可以确保在语句结束执行后每个资源都被关闭,而不需要我们进行手动关闭.

try with resource还可以同时开启多个资源,书写时用;进行分隔

 public static void main(String[] args) {
        String line;
        try(BufferedReader br1 = new BufferedReader(new FileReader("test1.txt"));
            BufferedReader br2 = new BufferedReader(new FileReader("test2.txt"))) {
            System.out.println("Entering try block");
            while ((line = br1.readLine()) != null) {
                System.out.println("Line1 =>"+line);
            }
            while ((line = br2.readLine()) != null) {
                System.out.println("Line1 =>"+line);
            }
        } catch (IOException e) {
            System.out.println("IOException in try block =>" + e.getMessage());
        } 
}
2.4 try catch finally的注意事项
2.4.1 执行流程

没有发生异常,先执行try代码块中的所有代码,然后执行finally代码块的所有代码,catch代码块会被跳过

发生异常,先执行try代码块中发生异常之前的代码,剩下的语句不再执行,然后执行catch代码块中的代码,最后执行finally代码块的代码

2.4.2 注意
  1. finally中的代码不一定所有情况都会执行,如发生以下情况,将不会执行

    在前面的代码中使用 System.exit() 退出程序

    finally代码块中产生了异常

  2. 不要在finally中使用rerurn,因为finally肯定会被执行,在finally中使用return会使try或catch中的return失效

3. 异常的声明和抛出

3.1 异常的声明(throws)

Java中,执行的语句必然属于某个方法,如果某个语句可能产生异常,我们可以选择不进行捕获,但是必须在方法上显式地声明异常,交由调用方法的调用者去进行处理,如果方法的调用者不去捕获异常,则要继续向上抛出

public void method() throws IOException,SQLException{
}

在方法中声明一个异常,需要在方法名后使用关键字throw,后面跟上我们需要声明的异常,如存在多个异常,中间用逗号进行分隔.

通常,对于知道如何处理的异常我们应该去捕获,对于不知道如何处理的异常,应该让它继续传递下去.

 public static void test(BufferedReader br) throws IOException {
     String line;
     while ((line = br.readLine()) != null) {
         System.out.println("Line =>"+line);
}

在用throws去向上抛出异常时,要遵循以下规则:

  • 如果是运行时异常,Error以及它们的子类,可以选择不使用throws声明要抛出的异常,此时编译仍然可以正常通过,但是发生异常时将会在运行时被抛出.
  • 如果是checked Exception(编译异常),则必须要显式地向上抛出或者用try catch语句进行捕获
  • 在进行方法重写时,如果父类中的方法声明了一个异常,则子类重写的方法也必须声明异常,声明的异常可以是这个异常,也可以是这个异常的子类
public class Exception01 {
    public void test() throws RuntimeException{
    }
}
public class Exception02 extends Exception01{
    @Override
    public void test() throws NullPointerException {
    }
}
3.2异常的抛出(throw)

如果代码可能出现某些错误,可以创建一个合适的异常类实例并抛出它,这就是抛出异常.

public static int divide(int num1, int num2) {
    if (num2 == 0) {
        throw new ArithmeticException("传入的被除数不能为0");
    }
    return num1 / num2;
}

通常情况下我们都不需要手动抛出异常,在Java中的方法要么已经声明了异常,要么就是对异常进行了处理.所以通常对于异常我们只需要捕获或者向上抛出

4.自定义异常

我们可以将项目中的异常封装成自己的一个异常类.通常自定义的异常类应该包含两个构造方法,一个无参构造,一个带有详细描述信息的构造

public class MyException extends Exception{
    public MyException() {
    }

    public MyException(String message) {
        super(message);
    }
    //...
}

提供有参构造可以让Throwable 的 toString 方法打印这些详细信息,让我们在调试时更清晰地了解异常的详细信息

public class ExceptionDemo {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int age = 0;
        System.out.println("欢迎光临荔枝网吧,请输入年龄");
        try {
            age = scanner.nextInt();
            if (age >= 0 && age < 18) {
                System.out.println("未成年不能上网");
            }
            if (age >= 18 && age <= 100) {
                System.out.println("已成年,可以上网");
            }
            if (age > 100 || age < 0) {
                throw new MyException("您输入的年龄不在范围");
            }
        } catch (InputMismatchException e) {
            System.out.println("您输入的年龄不是数字");
        } catch (MyException e) {
            System.out.println(e.getMessage());
        }
    }
}

如这个判断用户年龄是否到达上网年龄的Demo,如果用户输入100以上或负数,会抛出我们自定义的异常,被catch捕获,因为继承了Exception,所以继承了父类所有的属性和方法

5.练习:

1.思考下面方法的返回值是多少

 public static int test() {
        int i = 0;
        String[] names = new String[3];
        try {
            if (names[0].equals("张三")) {
                names[0] = "李四";
            } else {
                names[3] = "zeh";
                return 2;
            }
        } catch (NullPointerException e) {
            return ++i;
        } catch (ArrayIndexOutOfBoundsException e) {
            return ++i;
        } finally {
            return ++i;
        }
}

2.调用下面方法会输出什么,方法的返回值是多少

 public static int test() {
        int i = 0;
        String[] names = new String[3];
        try {
            if (names[0].equals("张三")) {
                names[0] = "李四";
            } else {
                names[3] = "zeh";
                return 2;
            }
        } catch (NullPointerException e) {
            return ++i;
        } catch (ArrayIndexOutOfBoundsException e) {
            return ++i;
        } finally {
            ++i;
            System.out.println("i="+i);
        }
}

答案:

  1. names[0].equals(“张三”)会抛出NullPointerException,所以后续代码不会执行,异常被捕获,执行第一个catch中的代码,遇到return语句,因为finally中的return会使catch中的return失效,所以只会执行++i操作,而不会返回值,最后执行finally中的return ++i,最后返回2.

  2. names[0].equals(“张三”)会抛出NullPointerException,所以后续代码不会执行,异常被捕获,执行第一个catch中的代码,遇到return语句,但是finally中的代码肯定会被执行,所以会先将catch中的++i用一个temp进行暂存,执行finally中++i输出i=2,然后返回1.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值