Bootstrap

零基础Java第二十一期:异常(一)

目录

一、异常的概念与体系

1.1. 异常的概念

 1.2. 异常的体系结构

1.3.  异常的分类

二、异常的处理 

2.1.  防御式编程

2.2. 异常的抛出

2.3. 异常的捕获


一、异常的概念与体系

1.1. 异常的概念

       在Java中,将程序执行过程中发生的不正常行为称为异常。异常是Java语法中的核心环节,是一种针对程序的错误处理的科学方式。

       在C语言,是没有异常的机制的,因为C语言比较“古老”了;在C++中,引入了异常,但只是一个“半成品”;而Java的异常是比较完善的体系。

       这里我们要注意异常与编译出错的差别。还有几种常见的异常,如数组的越界访问,空指针异常。

System.out.printl("hello");//编译出错
System.out.println(10 / 0);//异常,运行时出错,会抛出异常

int[] array1 = {1,2,3};
System.out.println(array1[10]);//数组的越界访问

int[] array2 = null;
System.out.println(array2.length);//空指针

 1.2. 异常的体系结构

 

       从上图中我们可以看到,Throwable是异常体系的顶层类,其派生出两个重要的子类, Error 和 Exception。Error是在JVM内部使用的, 程序员一般用不到Error。我们需要重点学习的是Exception。这么多类我们不需要背,因为Java标准库提供了很多现成的异常。

1.3.  异常的分类

(1)受查异常,在程序编译期间发生的异常,这样的异常必须要显式处理。

public class Person {
    private String name;
    private String gender;
    int age;

    @Override
    protected Person clone() {
        return(Person) super.clone();
    }
}

public class Main {
    public static void main(String[] args) {
        Person one = new Person();
        System.out.println();
    }
}

      上面的代码会在出现异常的位置下出现红色的波浪线,来提示我们出现了异常。受查异常不处理,代码就无法通过编译。

import java.io.FileNotFoundException;

public class Main {
    //throws后面可以接可能抛出的异常
    private static void openConfig(String filename) throws FileNotFoundException{
        if(!filename.equals("config.txt")){
            throw new FileNotFoundException("文件不存在");
        }
    }

    public static void main(String[] args) throws FileNotFoundException {
        openConfig("xxx");
    }
}

(2) 非受查异常

      在程序执行期间发生的异常,RunTimeException以及其子类对应的异常,都称为非受查异常。

二、异常的处理 

       在Java中,异常处理主要的5个关键字:throw、try、catch、final、throws。 

2.1.  防御式编程

(1) LBYL:Look Before You Leap。在操作之前就做充分的检查。即:事前防御型。缺陷:正常流程和错误处理流程代码混在一起, 代码整体显的比较混乱。

boolean ret = false;
 ret = 登陆游戏();
 if (!ret) {
    处理登陆游戏错误;
    return;
 }
 ret = 开始匹配();
 if (!ret) {
    处理匹配错误;
    return;
 }
 ret = 游戏确认();
 if (!ret) {
    处理游戏确认错误;
    return;
 }
 ret = 选择英雄();
 if (!ret) {
    处理选择英雄错误;
    return;

 (2)EAFP: It's Easier to Ask Forgiveness than Permission。“事后获取原谅比事前获取许可更容易"。也就是先操作,遇到问题再处理。即:事后认错型

try {
    登陆游戏();
    开始匹配();
    游戏确认();
    选择英雄();
    载入游戏画面();
    ...
 } catch (登陆游戏异常) {
    处理登陆游戏异常;
 } catch (开始匹配异常) {
    处理开始匹配异常;
 } catch (游戏确认异常) {
    处理游戏确认异常;
 } catch (选择英雄异常) {
    处理选择英雄异常;
 } catch (载入游戏画面异常) {
    处理载入游戏画面异常;
 }

        优势:正常流程和错误流程是分离开的, 程序员更关注正常流程,代码更清晰,容易理解代码 异常处理的核心思想就是 EAFP。

2.2. 异常的抛出

       在编写程序时,如果程序中出现错误,此时就需要将错误的信息告知给调用者。在Java中,可以借助throw关键字,抛出一个指定的异常对象,将错误信息告知给调用者。 

语法规则:

throw new xxxException(“产生异常的原因”)
//在数组中查找一个元素

public class Main {
    public static int getElment(int[] array,int index){
        if(array == null){
            // 数组为空, 抛出空指针异常
            throw new NullPointerException("传递的数组为null");
        }
        if(index<0 || index>= array.length){
            // 数组下标越界, 抛出另一个异常
            throw new ArrayIndexOutOfBoundsException("数组越界异常");
        }
        return array[index];
    }

    public static void main(String[] args) {
        int[] arrs = {1,2,3};
        getElment(arrs,3);
    }
}

     这样我们就能看到抛出的异常并给了一个提示,正是我们在异常当中所传递的参数。

注意:throw必须写在方法体内部;抛出的对象必须是Exception 或者 Exception 的子类对象; 异常一旦抛出,其后的代码就不会执行

2.3. 异常的捕获

(1)throws关键字 

        处在方法声明时参数列表之后,当方法中抛出编译时异常,用户不想处理该异常,此时就可以借助throws将异常抛 给方法的调用者来处理。即当前方法不处理异常,提醒方法的调用者处理异常。 

语法规则: 

修饰符  返回值类型   方法名(参数列表) throws 异常类型1,异常类型2...{
}
import java.io.FileNotFoundException;

public class Main {
    //throws加在方法后面,声明这个方法可能抛出的异常
    private static void openConfig(String filename) throws FileNotFoundException{
        if(!filename.equals("config.txt")){
            throw new FileNotFoundException("文件不存在");
        }
    }

    public static void main(String[] args) throws FileNotFoundException {
        openConfig("xxx");
    }
}

       注意:声明的异常必须是 Exception 或者 Exception 的子类;方法内部如果抛出了多个异常,throws之后必须跟多个异常类型,之间用逗号隔开,如果抛出多个异常类型 具有父子关系,直接声明父类即可;调用声明抛出异常的方法时,调用者必须对该异常进行处理,或者继续使用throws抛出。

       throw与throws的区别:throw是真得抛出来一个异常对象,而throws只是声明了一下,真正抛没抛,不知道。

(2)try-catch捕获并处理

       throws只是声明,声明告诉方法的调用者来处理异常。真正进行异常处理,要通过try-catch。 

语法规则: 

try{
   // 将可能出现异常的代码放在这里
}catch(要捕获的异常类型  e){
      // 如果try中的代码抛出异常了,此处catch捕获时异常类型与try中抛出的异常类型一致时,或者是try中抛出异常的基类
时,就会被捕获到
      // 对异常就可以正常处理,处理完成后,跳出try-catch结构,继续执行后序代码
import java.io.FileNotFoundException;
import java.io.IOException;

public class Main {
    public static void openConfig(String filename) throws IOException{
        if(!filename.equals("config.txt")){
            throw new FileNotFoundException("文件不存在");
        }
    }

    public static void main(String[] args) {
        try {
            openConfig("xxx");
            System.out.println("调用完成");
        } catch (IOException e) {
            //e是IOException的一个对象,这个对象是由throw抛出的
            System.out.println("进入到异常处理逻辑中");
        }
    }
}

        我们在调用openConfig后面加上一条语句,这是因为openConfig发现了异常,意味着这个代码块不会再执行后面了,直接进入后面的catch语句。

        这里还有一个小技巧,我们把鼠标光标放在被调用的方法上,然后alt加回车,直接出现要抛出的异常,很快就能生成。 

 

       catch 语句中, 一般会打印出异常的详细信息,printStackTrace() 方法可以打印异常的详细信息,主要就是调用栈。

       调用栈就会抛出异常的位置是怎样一层一层调过去的。 

       catch语句可以有多个具体catch哪些异常,取决于try抛出哪些异常。 

import java.io.FileNotFoundException;
import java.io.IOException;

public class Main {
    public static void openConfig(String filename) throws IOException {
        if(!filename.equals("config.txt")){
            throw new FileNotFoundException("文件不存在");
        }
    }
    public static int getElment(int[] array,int index){
        if(array == null){
            // 数组为空, 抛出空指针异常
            throw new NullPointerException("传递的数组为null");
        }
        if(index<0 || index>= array.length){
            // 数组下标越界, 抛出另一个异常
            throw new ArrayIndexOutOfBoundsException("数组越界异常");
        }
        return array[index];
    }

    public static void main(String[] args) {
        try {
            String str = null;
            str.length();
            openConfig("xxx");
            System.out.println("调用完成");
        } catch (IOException e) {
            System.out.println("进入到异常处理逻辑中");
            e.printStackTrace();
        } catch (NullPointerException e){
            System.out.println("空指针异常");
            e.printStackTrace();
        }
    }
}

        catch里面每一个独立的代码块,里面的e都是代码块里面的局部变量,所以不用担心e的命名不同。

;