异常

初识异常

例如:除以0

public class Test {

    public static void main(String[] args) {
        System.out.println(10 / 0);
    }
}
image.png

数组下标越界

public class Test {
    public static void main(String[] args) {
        int[] arr = {1, 2, 3};
        System.out.println(arr[100]);
    }
}
image.png

访问null对象

public class Test {
    public int num = 10;
    
    public static void main(String[] args) {
        Test t = null;
        System.out.println(t.num);
    }
}
image.png

异常就是指程序运行时,出现错误时通知程序调用者的一种机制

  • 注意异常时运行时的一种机制,不是编译期
  • 我们可以举个例子来理解一下什么是编译期的错误,比如我们写代码时出现关键字的拼写错误,此时出现的错误就是编译期的错误

防御式编程

  • 防御式编程时提高软件质量技术的有益辅助手段
  • 防御式编程的主要思想是:子程序应该不因传入错误数据而被破坏,哪怕是由其他子程序产生的错误数据
  • 这种思想将可能出现的错误造成的影响控制在有限的范围内

错误在代码中是客观存在的。因此我们要让程序出现问题的时候及时通知程序猿。主要有两种方式:

  • LBYL:Look Before You Leap,在操作之前就做充分的检查
  • EAFP:It’s Easier to Ask Forgiveness than Permission,先操作,遇到问题再处理
    异常的核心思想就是EAFP(先操作,遇到问题再处理)

异常的优点

  • LBYL
 boolean ret = 创建套接字();
        if(!ret) {
            创建套接字失败;
            return;
        }
        ret = 绑定地址信息();
        if(!ret) {
            绑定地址信息失败;
            return;
        }
        ...
  • EAFP:
try {
           创建套接字();
           绑定地址信息();
           ...
       } catch (创建套接字异常) {
            处理创建套接字异常;
        } catch (绑定地址信息异常) {
            处理绑定地址信息异常;
       }
        ...

对比两种风格的代码,我们可以发现:

  • LBLY处理方式会将正常流程和错误处理流程代码混在一起,代码整体显得比较混乱
  • EAFP处理方式的正常流程和错误处理流程是分开的,更容易理解代码

异常的基本用法

异常捕捉

  • 基本语法
try {
      //可能出现的异常
} [catch (异常类型 异常对象) {
      //异常处理
} ...]
[finally {
      //异常出口
}]
  • try代码块中放的是可能出现异常的代码
  • catch代码块中放的是出现异常后的处理行为
  • finally代码块中的代码用于处理善后工作,会在最后执行
  • 其中catch和finally都可以根据情况选择加或者不加

下面我们看几种常见的异常处理方式

  • 不处理异常
public class Test {
    public static void main(String[] args) {
       int[] arr = {1, 2, 3};
        System.out.println(arr[100]);
        System.out.println("hello");
    }
}
image.png

可以看到,如果对于异常不进行处理,程序就会在出现异常处终止,后序的代码将不再执行。其实这里异常并不是没有被处理,而是被JVM处理,JVM处理异常的方式就是打印出现异常的调用栈信息并终止程序

  • 使用try catch后的程序执行过程
public class Test {
    public static void main(String[] args) {
        int[] arr = {1, 3, 5, 6};
        try {
            System.out.println(arr[100]);
        } catch(ArrayIndexOutOfBoundsException e) {
            e.printStackTrace();
        }
        System.out.println("hello");
    }
}
image.png

从运行结果可以看出,try中一旦有了异常,就会跳到对应的catch中,不再执行try中剩余的逻辑

  • catch只能处理对应种类的异常
public class Test {
    public static void main(String[] args) {
        int[] arr = {1, 3, 5, 6};
        try {
            System.out.println(arr[100]);
        } catch(NullPointerException e) {
            e.printStackTrace();
        }
        System.out.println("hello");
    }
}
image.png

可以看到,这里的catch语句并没有捕获到数组访问越界的异常,该异常最终被JVM处理。

catch可以有多个

public class Test {
    public static void main(String[] args) {
        int[] arr = {1, 3, 5, 6};
        try {
            System.out.println(arr[100]);
        } catch(NullPointerException e) {
            e.printStackTrace();
        } catch (ArrayIndexOutOfBoundsException e) {
            e.printStackTrace();
        }
        System.out.println("hello");
    }
}
image.png
  • 也可以使用一个catch捕获所有的异常。Exception类是所有异常类的父类。因此可以用这个类型表示捕获所有异常。catch进行匹配的时候,不仅可以捕捉到相同类型的异常,还可以捕捉到目标类型异常的子类对象。不推荐使用这种方式。
public class Test {
    public static void main(String[] args) {
        int[] arr = {1, 3, 5, 6};
        try {
            System.out.println(arr[100]);
        } catch(Exception e) {
            e.printStackTrace();
        }
        System.out.println("hello");
    }
}
image.png
  • finally表示最后的善后工作,如释放资源
public class Test {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        try {
            System.out.println("Before scanner");
            int num = scanner.nextInt();
            System.out.println("After scanner");
        } catch(InputMismatchException e) {
            e.printStackTrace();
        } finally {
            System.out.println("scanner.close()");
            scanner.close();
        }
        System.out.println("hello");
    }
}
image.png

无论try中是否发生异常,finally中的代码一定会执行

  • 可以使用try回收资源
public class Test {
    public static void main(String[] args) {

        try (Scanner scanner = new Scanner(System.in)) {
            System.out.println("Before scanner");
            int num = scanner.nextInt();
            System.out.println("After scanner");
        } catch(InputMismatchException e) {
            e.printStackTrace();
        }

        System.out.println("hello");
    }
}
image.png

和前一个代码的写法等价,将Scanner对象在try的()中创建,能够保证在try执行完毕后自动调用Scanner的close方法

  • 如果当前方法中没有合适的异常处理方式,异常就会沿着调用栈向上传递,直到最后交给JVM处理
public class Test {
    private static void func() {
        int[] arr = {1, 2, 3, 5};
        System.out.println(arr[100]);
    }
    public static void main(String[] args) {
        try {
            func();
        } catch (ArrayIndexOutOfBoundsException e) {
            e.printStackTrace();
        }
        System.out.println("hhh");
    }
}
image.png

异常处理流程

  • 程序先执行try中的代码
  • 如果try中的代码出现异常,就会结束try中的代码,看和catch中的异常类型是否匹配
  • 如果找到匹配的异常类型,就会执行catch中的代码
  • 如果没有找到匹配的异常类型,就会将异常向上传递到上层调用者
  • 无论是否找到匹配的异常类型,finally中的代码都会被执行到(在该方法结束之前执行)
  • 如果上层调用者也无法处理异常,异常就会继续向上传递
  • 一直到main方法也没有合适的代码处理异常,就会交给JVM来进行处理,此时程序就会异常终止

异常抛出

除了Java内置的类会抛出一些异常之外,程序猿也可以手动抛出某个异常。使用throw关键字来完成这个操作。
下面来看一个具体的例子:

public class Test {
    private static int div(int a, int b) {
       if(b == 0) {
           throw new ArithmeticException("这是我自己抛出的异常");
       }
       return a / b;
    }
    public static void main(String[] args) {
        int ret = div(10, 0);
        System.out.println(ret);
    }
}
image.png

异常说明

我们在处理异常时,通常希望知道这段代码中究竟会出现哪些可能的异常。我们可以使用throws关键字,把可能抛出的异常显式的标注在方法定义的位置。从而提醒调用者要注意捕获

public class Test {
    private static int div(int a, int b) throws ArithmeticException {
       if(b == 0) {
           throw new ArithmeticException("这是我自己抛出的异常");
       }
       return a / b;
    }
    public static void main(String[] args) {
        int ret = div(10, 0);
        System.out.println(ret);
    }
}
image.png

finally的注意事项

public class Test {
    private static int func() {
        try {
            return 10;
        } finally {
            return 20;
        }
    }
    public static void main(String[] args) {
        System.out.println(func());
    }
}
image.png

注意

  • finally执行的时机是在方法返回之前(try或者catch中如果有return会在这个return之前执行finally)
  • 但是如果finally中也存在return语句,那么就会执行finally中的return,从而不会执行到try中原有的return
  • 不建议在finally中写return语句,编译器会有警告

java异常体系

  • 顶层类Throwable派生出两个重要的子类,Error和Exception
  • 其中Error指的是Java运行时内部错误和资源耗尽错误。应用程序不抛出此类异常。这种内部错误一旦出现,除了告知用户并使程序终止之外,没有别的办法,这种情况很少出现
  • Exception是程序猿使用的异常类的父类
  • 其中Exception有一个子类称为RuntimeException,这里面又派生出很多我们常见的异常类NullPointerException等

Java语言规范将派生于Error类或RuntimeException类的所有异常称为非受查异常,所有的其他异常称为受查异常
如果一段代码可能抛出受查异常,那么必须显式进行处理

import java.io.File;
import java.util.InputMismatchException;
import java.util.Scanner;
import java.util.Stack;

public class Test {
    public static void main(String[] args) {
        File file = new File("d:/test.txt");
        Scanner sc = new Scanner(file);

        String str = sc.next();
    }
}
image.png

从报错信息可以看出,我们必须对受查异常进行处理。
这里有两种处理方式

  • 方法一:使用try catch包裹起来进来
import java.io.File;
import java.io.FileNotFoundException;
import java.nio.file.FileAlreadyExistsException;
import java.util.InputMismatchException;
import java.util.Scanner;
import java.util.Stack;

public class Test {
    public static void main(String[] args) {
        File file = new File("e:/test.txt");
        Scanner sc = null;
        try {
           sc = new Scanner(file);
           String str = sc.nextLine();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        System.out.println("After try catch");
    }
}
image.png
  • 方法二:在方法上加上异常说明,相当于将处理动作交给上级调用者
public class Test {
    public static void main(String[] args) throws FileNotFoundException {
        File file = new File("e:/test.txt");
        Scanner sc = new Scanner(file);
        String str = sc.nextLine();

    }
}
image.png

自定义异常类

中虽然已经内置了丰富的异常类,但是我们实际场景中可能还有一些情况需要我们对异常类进行扩展,创建符合我们实际情况的异常
这里,我们模拟一个用户登录的场景

import java.io.File;
import java.io.FileNotFoundException;
import java.nio.file.FileAlreadyExistsException;
import java.util.InputMismatchException;
import java.util.Scanner;
import java.util.Stack;

public class Test {
    public static void main(String[] args) throws FileNotFoundException {
        try (Scanner sc = new Scanner(System.in)) {
            System.out.println("请输入用户名:");
            String name = sc.next();
            System.out.println("请输入登录密码:");
            String password = sc.next();
            login(name, password);
        } catch (PasswordException e) {
            e.printStackTrace();
        } catch (UserException e) {
            e.printStackTrace();
        }
    }

    private static void login(String name, String password) throws UserException, PasswordException {
        if(!"admin".equals(name)) {
            throw new UserException("用户名错误");
        }
        if(!"123456".equals(password)) {
            throw new PasswordException("密码错误");
        }
        System.out.println("登录成功");
    }
}
class UserException extends Exception {
    public UserException(String msg) {
        super(msg);
    }
}
class PasswordException extends Exception {
    public PasswordException(String msg) {
        super(msg);
    }
}
image.png
image.png
image.png

注意:

  • 自定义异常通常会继承自Exception或者RuntimeException
  • 继承自Exception的异常默认是受查异常
  • 继承自RuntimeException的异常默认是非受查异常
  • 自定义异常类往往不是创建一个类,而是创建一个系列
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容