Java中的异常处理

目录

前言:

异常简介: 

Error类:

Exception类:

Exception异常:

运行异常: 

编译异常:

throw和throws关键字: 

throw:

throws:

try-catch关键字:

finally:

为啥叫受查异常?

throw和throws的区别:

总结:


前言:

        应该都听说过Java中的异常处理,其实不止Java中有异常处理,我们学过的其他语言中的报错是不是也是异常呢?对,是,但是Java中的异常为什么要学习?因为它可以当程序真的报错而崩溃时,我们进行异常处理可以使其跳过,程序还能正常的往下执行。

        像C语言,如果程序报错就会崩溃,而Java可以得知这个异常,可以继续正常运行。

异常简介: 

        异常就是异于常态,和正常情况不一样,有错误。在Java中,阻止当前方法或作用域的情况,称之为异常。

        Java中所有异常类都继承于Throwable类,对,你没听错,这些异常都是类。

        Java中,所有异常都有一个公共祖先Throwable(可抛出)。Throwable指定代码中可用传播机制通过Java应用程序传输的任何问题的共性。

        Throwable主要包括两个大类,一个是Error类,另一个是Exception类。

        注意,异常和错误是两种东西。不是同一概念。

Java中的异常处理_第1张图片

Error类:

        其中Error类中包括虚拟机错误和线程死锁,一旦Error出现了,程序就彻底的挂了,被称为程序终结者。

Exception类:

        也就是通常所说的“异常”。主要指编码、环境、用户操作输入出现问题,Exception主要包括两大类,非检查异常(RuntimeException)和检查异常(其他的一些异常)。Java中的异常处理_第2张图片 

        因为Error类出现,程序就会挂掉,所以讲的意义不大。这一篇我们就来重点了解Exception类这个异常。 Java中的异常处理_第3张图片

Exception异常:

        Java中异常是分种类的,异常分为两大类:运行异常和编译异常(下图只是简单分类)。Java中的异常处理_第4张图片

        比如算数异常: Java中的异常处理_第5张图片

        当发生异常时,不会再执行异常后面的代码。 

        比如空指针异常和数组越界异常:Java中的异常处理_第6张图片

Java中的异常处理_第7张图片         此时我们来观察运行异常和编译异常的区别,比如我们利用递归来观察:

public static void func() {
    func();
}
public static void main(String[] args) {
    func();
}

         执行上面的代码会执行,之后报错。Java中的异常处理_第8张图片

         Error指的是Java虚拟机无法解决的严重问题,比如JVM的内部错误、资源耗尽等,比较典型的是:StackOverflowError 和 OutOfMemoryError(上面的死递归就是栈溢出)。

        Exception:异常产生后程序员可以通过代码进行处理,是程序继续执行。

运行异常: 

        异常分为运行时异常和编译时异常,我们刚才说的空指针异常,算术异常,数组下标越界访问异常都是运行异常。运行异常又称非受查异常。这个名字我们一会了解。

        注意此时程序至少运行了。

编译异常:

        那么接下来我们就来看看编译异常是什么。还记不记得我们之前讲的克隆方法,声明克隆接口表示能被克隆,要通过向下转型,还有要扔出异常才能被克隆。

class Person implements Cloneable{
    public String name;

    public Person(String name) {
        this.name = name;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                '}';
    }
}

public class Test {
    public static void main(String[] args) throws CloneNotSupportedException {
        Person person1 = new Person("zhansan");
        Person person2 = (Person) person1.clone();
        System.out.println(person2);
    }
}

Java中的异常处理_第9张图片

        是否还记得我们当时一直出现红色标志,我们进行了向下转型,重写了该方法并且使用了Cloneable接口,但是还是不能运行,必须抛出异常才可以运行。其实这里就发生了编译异常。

         编译异常在编译期间一定要处理,否则代码不能编译通过。

        那么我们有时候写出的会标红,这叫做语法错误。Java中的异常处理_第10张图片

throw和throws关键字: 

throw:

         如何让程序抛出异常?Java中抛出异常通常是使用throw和throws关键字来实现的。

        throw是将产生的异常抛出,是一个抛出异常的动作(一般是指定的异常)。

int a = 10;
if (a == 10) {
    throw new NullPointerException("hahaha");
}

        通过throw来手动抛出异常。 我们之前的算数等等异常是JVM抛出的,因为我们没有处理。Java中的异常处理_第11张图片

        throw关键字一般抛出我们自定义的异常。 

throws:

        throws一般使用在方法之后。Java中的异常处理_第12张图片

         告诉方法调用者,调用这个方法可能会抛出这个异常。也就是说,如果在方法中有这个编译时异常(受查异常),那么这个编译时异常一定要进行处理。目前我们的处理方式是在方法定义的时候,通过throws关键字声明异常。最后这个异常是交给JVM处理的。Java中的异常处理_第13张图片

        throws必须跟在方法的参数列表之后,声明异常必须是Exception 或者Exception 的子类。

        如果方法内存抛出了多个异常,throws之后必须跟多个异常类型,之间用逗号隔开;如果抛出的异常类型有父子关系,直接声明父类即可。

//public void OpenConfig(String filename) throws IOException, FileNotFoundException{
//FileNotFoundException 继承于 IOException
public void OpenConfig(String filename) throws IOException{
    
}//这两种方式是一样的

        此时我们再来看一个代码: 

public static void func() throws CloneNotSupportedException{
    int a = 10;
    if (a == 10) {
        throw new CloneNotSupportedException("hahaha");
    }
}

public static void main(String[] args) throws CloneNotSupportedException {
    func();//因为 func 方法的声明里面有编译异常处理,所以主方法也要写
}

        但此时我们确实抛出异常了,但是程序还是崩溃了,那么异常实际上没有被程序员处理,实际上还是交给了JVM处理。

try-catch关键字:

        程序员要解决这些异常,就需要使用这些语句:

public static void func() throws CloneNotSupportedException{
    int a = 10;
    if (a == 10) {
        throw new CloneNotSupportedException("hahaha");
    }
}

public static void main(String[] args) throws CloneNotSupportedException {
    try {
        func();
    } catch (CloneNotSupportedException e) {
        throw new RuntimeException(e);
    }
}

        try语句中,可能会有异常发生,我们写这些语句是,是可以预测会发生那些异常的(身为程序员,我不信你看不出来),那么catch语句中,就是捕获这些可能发生的异常。 如果捕获到了就执行catch当中的内容。Java中的异常处理_第14张图片

        如果我们预测失败,就是catch中没有捕获到发生的异常,还是会交给JVM处理,程序崩溃。 Java中的异常处理_第15张图片

        我们可以通过catch捕获多个异常(就是多写几个catch语句): 

try {
    System.out.println(10/0);
} catch (ArithmeticException e) {
    System.out.println("我来处理 ArithmeticException 了");
} catch (NullPointerException e) {
    System.out.println("我来处理 NullPointerException 了");
}

        这里处理的异常都是在try语句中的。我们也可以通过 | 连接多个异常,充当“或”。 

/*try {
    System.out.println(10/0);
} catch (ArithmeticException e) {
    System.out.println("我来处理 ArithmeticException 了");
} catch (NullPointerException e) {
    System.out.println("我来处理 NullPointerException 了");
}*/

//以下方式等同上面,但是少用
try {
    System.out.println(10/0);
} catch (ArithmeticException | NullPointerException e) {
    System.out.println("我来处理 ArithmeticException 了");
}

        也就是说,在try里面发生的异常,就不会执行里面的代码了。 Java中的异常处理_第16张图片

        虽然可以捕获多种类型异常,但是同一时刻只能抛出一个异常。

        我们不能先捕获所有父类异常之后捕获子类异常,否则出错。

        但是可以先捕获子类异常,之后捕获父类异常,此时就充当了垫后的角色。Java中的异常处理_第17张图片

        还有就是,那几个运行异常的类型最好记住,如果我们使用了try-catch语句,就一定要知道可能会出现哪些异常。当然,我们知道所有异常都继承Exception类,刚才说可以使用父类来指代。但是最好不要使用父类,否则这样就失去了意义。

finally:

        你猜它为啥叫finally?我们先看代码:

public static int func() {
    try {
        int[] array = null;
        System.out.println(array.length);
    } catch (NullPointerException e) {
        System.out.println("捕获到了一个空指针异常");
    } finally {
        System.out.println("这里执行了finally");
    }
    return 10;
}
public static void main(String[] args) {
    System.out.println(func());
}

Java中的异常处理_第18张图片

        可以看到, finally最后无论如何都会被执行。

        这里面会有很多抗,也就是说我们必须知道执行顺序,比如以下代码结果:Java中的异常处理_第19张图片

        因为try里面直接返回了,但是finally还是被执行了。那么既然如此,我们就不能乱返回了:Java中的异常处理_第20张图片         因为可能不止一条语句执行,所以返回值不能有多个。

        执行顺序:try中的语句正常,就先执行finally,之后执行try;try中的语句有异常,就先执行catch里面捕获到的异常语句,之后执行finally。

        所以try里面没有异常,要先执行finally:

public static int func1() {
    try {
        return 10;
    } finally {
        return 100;
    }
}
public static void main(String[] args) {
    System.out.println(func1());
}

        这个结果为100,此时直接执行finally,并不在执行try里面的代码。 

        我们先来执行一个异常代码:Java中的异常处理_第21张图片

        此时func2里面没有处理这个异常,main方法中处理了这个异常,那么这个func2方法就最好声明一下,让使用者知道会有异常并处理。 

public static void func2() throws ArrayIndexOutOfBoundsException{
    //声明会有这个异常让使用者处理
    int[] arr = {1,2,3};
    System.out.println(arr[100]);
}
public static void main(String[] args) {
    //此时因为是 main 方法调用,所以要在 main 方法中处理
    //使用 try-catch
    try {
        func2();
    } catch (ArrayIndexOutOfBoundsException e) {

    } finally {
        
    }

}

         所以调用的方法中也没有处理,最终就会交给JVM来处理。

为啥叫受查异常?

        为了更好的理解,我们来举一个例子,我们来自定义异常,因为异常本来就是类,所以我们利用继承来自己定义异常。

        我们可以看到,像ArrayIndexOutOfBoundsException这个异常是继承于IndexOutOfBoundsException的,所以我们自己写异常时,就需要用到继承于Exception中的类来写。

        此时我们定义一个LogIn类,并在里面对比用户名和密码,如果对照错误,则抛出自己定义的异常。 

public class LogIn {
    private String userName = "admin";
    private String password = "123455";
    private static void loginInfo (String userName, String password) {
        if (!userName.equals(userName)) {
            //此时用户名输入错误,就需要报异常
            //此时抛出自定义异常
            throw new UserNameException("用户名有问题");
        }
        if (!password.equals((password))) {
            //此时密码错误
            throw new PasswordException("密码有问题");
        }
        System.out.println("登陆成功");
    }

    public static void main(String[] args) {
        loginInfo("admin","123456");
    }
}

//extends Exception          编译时异常
//extends RuntinmeException  运行时异常

class UserNameException extends Exception {
    //自定义异常一定要继承于异常
    //此时为编译时异常
    public UserNameException (String message) {
        super(message);
    }
}

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

Java中的异常处理_第22张图片         此时报红是因为这是一个受查异常,所以需要通过try-catch包起来。但是我们之前讲过,可以通过调用它的函数try-catch,有异常的函数只需要通过声明可能有异常即可。Java中的异常处理_第23张图片

public static void main(String[] args) {
    //此时通过throws已经声明了异常
    //调用方法里面需要通过 try-catch 来解决
    try {
        loginInfo("admin","123456");
    } catch (UserNameException e) {
        
    } catch (PasswordException e) {
        
    } finally {
        
    }
    
}
private static void loginInfo (String userName, String password) throws UserNameException,PasswordException{
    if (!userName.equals(userName)) {
        //此时用户名输入错误,就需要报异常
        //此时抛出自定义异常
        throw new UserNameException("用户名有问题");
    }
    if (!password.equals((password))) {
        //此时密码错误
        throw new PasswordException("密码有问题");
    }
    System.out.println("登陆成功");
}

        因为这是受查异常,因为我们继承了编译异常的类,所以必须解决。所以运行异常(非受查异常)就不用解决了。

        注:因为空指针异常,算数异常不是编译异常,所以你在主方法后面不加上throws也可以通过,由JVM来帮你处理。

        我们见过一个最典型的受查异常(编译异常)就是克隆方法了,我们当时主方法调用克隆方法,这个主方法就必须使用throws关键字,否则无法完成编译。

throw和throws的区别:

        1、throws出现在方法函数头;而throw出现在函数体。
        2、throws表示出现异常的一种可能性,并不一定会发生这些异常;throw则是抛出了异常,执行throw则一定抛出了某种异常对象。
        3、两者都是消极处理异常的方式(这里的消极并不是说这种方式不好),只是抛出或者可能抛出异常,但是不会由函数去处理异常,真正的处理异常由函数的上层调用处理。

总结:

        多去刷题,多去使用,就会领悟。

你可能感兴趣的:(Java,java,开发语言)