开启掘金生长之旅!这是我参加「掘金日新计划 12 月更文挑战」的第7天,点击查看活动详情


前言

本文为Java反常相关常识,包括关于反常的概述,反常的分类,以及关于反常的处理机制。

Java全栈学习道路可参阅:【Java全栈学习道路】最全的Java学习道路及常识清单,Java自学方向指引,内含最全Java全栈学习技能清单~

一、Java反常引入

public static void main(String[] args) {
    int num1 = 10;
    Scanner scanner = new Scanner(System.in);
    int num2 = scanner.nextInt();
    int res = num1 / num2;
    System.out.println(res);
    System.out.println("后边还有代码呦!");
}

首要,问咱们一个问题:

请问:假如我输入了 “abc” 或许 0 程序应该怎样运转呢?

abc
Exception in thread "main" java.util.InputMismatchException
	at java.util.Scanner.throwFor(Scanner.java:864)
	at java.util.Scanner.next(Scanner.java:1485)
	at java.util.Scanner.nextInt(Scanner.java:2117)
	at java.util.Scanner.nextInt(Scanner.java:2076)
	at cn.itnanls.Demo.main(Demo.java:13)
0
Exception in thread "main" java.lang.ArithmeticException: / by zero
	at cn.itnanls.Demo.main(Demo.java:14)

程序它在想办法告诉你,你错了!程序是永久找不到女朋友的,因为程序只要真的是自己错了,它才会承认自己错了。

而且有个很重要的问题,程序碰到反常就不再持续履行后边的代码了,真是悲惨剧,很明显这不是你想要的。

世界上存在永久不会出错的程序吗?也许这只会呈现在程序员的梦中。随着编程言语和软件的诞生,反常状况就如影随形地纠缠着咱们,只要正确处理好意外状况,才干确保程序的可靠性。

其实这个比方的处理方案至少有两种:

(1)运转前对参数进行校验

public static void main(String[] args) {
    int num1 = 10;
    Scanner scanner = new Scanner(System.in);
    int num2 = scanner.nextInt();
    if (num2 == 0){
        System.out.println("除数不能为零");
        return;
    }
    int res = num1 / num2;
    System.out.println(res);
    System.out.println("后边还有代码呦!");
}

这样是不是就处理了除数不能为零的问题。

(2)出了问题想办法处理

public static void main(String[] args) {
    // 尝试履行
    try {
        int num1 = 10;
        Scanner scanner = new Scanner(System.in);
        int num2 = scanner.nextInt();
        int res = num1 / num2;
        System.out.println(res);
    } catch (Exception e){    // 遇到问题,找出问题
        System.out.println("除数不能为零!");      // 处理问题
        e.printStackTrace();                    // 把问题的内容打印出来   
    }
    System.out.println("后边还有代码呦!");
}

这样也可以让程序持续履行,咱们发现咱们找出的问题,他叫Exception,也是的java类。

二、反常概述与反常体系结构

首要咱们来了解一下什么是反常,以及反常的体系结构。

1.反常概述

反常:在Java言语中,将程序履行中发生的不正常状况称为“反常”。(开发过程中的语法过错和逻辑过错不是反常) Java程序在履行过程中所发生的反常事情可分为两类:

  • Error:Java虚拟机无法处理的严峻问题。 如:JVM体系内部过错、资源耗尽等严峻状况。比方:StackOverflowError和OOM。一般不编写针对性的代码进行处理。
/*
 - Error:
 - Java虚拟机无法处理的严峻问题。如:JVM体系内部过错、资源耗尽等严峻状况。
 - 比方:StackOverflowError和OOM。
 - 一般不编写针对性的代码进行处理。
 */
public class ErrorTest {
    public static void main(String[] args) {
        //1.栈溢出:java.lang.StackOverflowError
        //main(args);  //递归
        //2.堆溢出:java.lang.OutOfMemoryError,简称OOM 
        Integer[] arr = new Integer[1024*1024*1024];
    }
}
  • Exception:其它因编程过错或偶然的外在要素导致的一般性问题,可以运用针对性的代码进行处理。例如:空指针拜访企图读取不存在的文件;网络连接中断;数组角标越界。

关于这些过错,一般有两种处理办法:一是遇到过错就终止程序的运转。另一种办法是由程序员在编写程序时,就考虑到过错的检测、过错消息的提示,以及过错的处理。 捕获过错最理想的是在编译期间;但有的过错只要在运转时才会发生,比方:除数为0,数组下标越界等。

因此针对反常发生的场景可将反常分为:编译时反常和运转时反常。编译时反常便是在编译期间发生且可以捕获的反常,运转时反常是只要在运转时才会发生的反常。

2.反常的分类

下面咱们就来具体看看什么是运转时反常以及什么是编译时反常。 运转时反常

  • 是指编译器不要求强制处置的反常。一般是指编程时的逻辑过错,是程序员应该活跃防止其呈现的反常。
  • java.lang.RuntimeException类及它的子类都是运转时反常。
  • 关于这类反常,可以不作处理,因为这类反常很遍及,若全处理或许会对程序的可读性和运转功率发生影响。

编译时反常

  • 是指编译器要求有必要处置的反常。即程序在运转时因为外界要素造成的一 般性反常。编译器要求Java程序有必要捕获或声明一切编译时反常。
  • 关于这类反常,假如程序不处理,或许会带来意想不到的结果。

3.反常体系结构

以下为咱们总结了Java中的反常的体系结构。一切的Java反常类都归属于java.lang.Throwable。

java.lang.Throwable
 *         |-----java.lang.Error:一般不编写针对性的代码进行处理。
 *         |-----java.lang.Exception:可以进行反常的处理
 *             |------编译时反常(checked)
 *                     |-----IOException:IO反常
 *                         |-----FileNotFoundException:体系找不到指定途径的反常
 *                     |-----ClassNotFoundException:类找不到反常
 *             |------运转时反常(unchecked,RuntimeException)
 *                     |-----NullPointerException:空指针反常
 *                     |-----ArrayIndexOutOfBoundsException:数组角标越界反常
 *                     |-----ClassCastException:类型转化反常
 *                     |-----NumberFormatException:数字格局化反常
 *                     |-----InputMismatchException:输入的类型不匹配的反常
 *                     |-----ArithmeticException:算术反常
  • Throwable类是 Java 言语中一切过错或反常的超类。
  • Throwable类是一切过错和Java中反常的超类。 只要当目标是此类(或其子类之一)的实例时,才干经过 Java 虚拟机或许 Java throw 句子抛出。
  • Exception和Error都是承继了Throwable类,它是反常处理机制的基本组成类型。
  • Exception和Error表现了Java平台设计者对不同反常状况的分类。Exception是程序正常运转中,可以预料的意外状况,或许并且应该被捕获,进行相应处理。
  • Error是指在正常状况下,不大或许呈现的状况,绝大部分的Error都会导致程序(比方JVM自身)处于非正常的、不行恢复状况。既然是非正常状况,所以不便于也不需求捕获,常见的比方OutOfMemoryError之类,都是Error的子类。

三、常见反常

下边呢将经过代码为咱们展示一切常见的反常,包括:编译时反常以及运转时反常(包括:算术反常,输入的类型不匹配的反常,数字格局化反常,类型转化反常,角标越界反常,空指针反常等)。下边咱们就来看看吧~

public class ExceptionTest {
    //******************以下是编译时反常***************************
    @Test
    public void test7(){
        File file = new File("hello.txt");
        FileInputStream fis = new FileInputStream(file);
        int data = fis.read();
        while(data != -1){
            System.out.print((char)data);
            data = fis.read();
        }   
        fis.close();
    }
    //******************以下是运转时反常***************************
    //ArithmeticException 算术反常
    @Test
    public void test6(){
        int a = 10;
        int b = 0;
        System.out.println(a / b);
    }
    //InputMismatchException 输入的类型不匹配的反常
    @Test
    public void test5(){
        Scanner scanner = new Scanner(System.in);
        int score = scanner.nextInt();
        System.out.println(score);
        scanner.close();
    }
    //NumberFormatException 数字格局化反常
    @Test
    public void test4(){
        String str = "123";
        str = "abc";
        int num = Integer.parseInt(str);
    }
    //ClassCastException 类型转化反常
    @Test
    public void test3(){
        Object obj = new Date();
        String str = (String)obj;
    }
    //IndexOutOfBoundsException 角标越界反常
    @Test
    public void test2(){
        //ArrayIndexOutOfBoundsException  数组角标越界反常
        int[] arr = new int[10];
        System.out.println(arr[10]);
        //StringIndexOutOfBoundsException  字符串角标越界反常
        String str = "abc";
        System.out.println(str.charAt(3));
    }
    //NullPointerException 空指针反常
    @Test
    public void test1(){
//        int[] arr = null;
//        System.out.println(arr[3]);
        String str = "abc";
        str = null;
        System.out.println(str.charAt(0));
    }
}

Error 是 Throwable 的子类,用于指示合理的应用程序不应该企图捕获的严峻问题。Java 程序通常不捕获过错。过错一般发生在严峻故障时,它们在Java程序处理的领域之外。反常:java言语中,将程序履行中发生的不正常状况称之为反常,便是程序履行的时分呈现了你不想看到的状况。

四、反常处理机制一:try-catch-finally

Java中的反常处理机制包括try-catch-finally捕获反常以及throws抛出反常两种处理机制。

1.Java反常处理

咱们先来了解一下什么是Java反常处理,以及第一种反常处理机制——try-catch-finally。

在编写程序时,经常要在或许呈现过错的当地加上检测的代码,如进行x/y运算时,要检测分母为0,数据为空,输入的不是数据而是字符等。过多的if-else分支会导致程序的代码加长、臃肿,可读性差。因此选用反常处理机制可以对这类反常进行捕获。

Java反常处理:

  • Java选用的反常处理机制,是将反常处理的程序代码集中在一起,与正常的程序代码分隔,使得程序简练、高雅,并易于护。

Java反常处理的办法:

Java反常处理的办法有以下两种:

  • 办法一:try-catch-finally
  • 办法二:throws + 反常类型

2.try-catch-finally的运用

  • 运用 try 和 catch 关键字可以捕获反常。
  • try/catch 代码块放在反常或许发生的当地。 try-catch-finally的语法格局如下:
try {
   // 程序代码
} catch(ExceptionName e1) {
   //Catch 块
}
  • catch 句子包括要捕获反常类型的声明。当维护代码块中发生一个反常时,try 后边的 catch 块就会被查看。假如发生的反常包括在 catch 块中,反常会被传递到该 catch 块,这和传递一个参数到办法是相同。
  • 一个 try 代码块后边跟随多个 catch 代码块的状况就叫多重捕获
  • 多重捕获块的语法如下所示:
try{
          //或许呈现反常的代码
}catch(反常类型1 变量名1){
          //处理反常的办法1
}catch(反常类型2 变量名2){
          //处理反常的办法2
}catch(反常类型3 变量名3){
          //处理反常的办法3
}
....
finally{
         //必定会履行的代码
}

需求说明的是:

  • finally代码块是可选的,也便是说可以没有finally的处理。
  • 运用try将或许呈现反常的代码包装起来,在履行过程中,一旦呈现反常,就会生成一个对应反常类的目标,根据此目标的类型,去catch中进行匹配
  • 一旦try中的反常目标匹配到某一个catch时,就进入catch中进行反常的处理。一旦处理完结,就跳出当时的try-catch结构(在没有写finally的状况)然后持续履行其后边的代码。
  • catch中的反常类型假如没有子父类联系,则谁声明在上,谁声明鄙人无所谓。catch中的反常类型假如满意子父类联系,则要求子类必定声明在父类的上面。不然的话会报错。
  • 常用的关于反常目标的处理办法: ① String getMessage() ② printStackTrace()
  • 在try结构中声明的变量,再出了try结构今后,就不能再被调用
  • try-catch-finally的结构可以嵌套

需求留意的是:

运用try-catch-finally处理编译时反常,使得程序在编译时就不再报错,可是运转时仍或许报错。相当于咱们运用try-catch-finally将一个编译时或许呈现的反常,延迟到运转时呈现。在咱们实际的开发中,因为运转时反常比较常见,所以咱们通常就不针对运转时反常编写try-catch-finally了。针关于编译时反常,咱们就必定要考虑关于反常的处理。

以下是关于try catch这种反常处理办法运用的示例代码:

代码示例:

public class ExceptionTest1 {
    @Test
    public void test2(){
        try{
            File file = new File("hello.txt");
            FileInputStream fis = new FileInputStream(file);
            int data = fis.read();
            while(data != -1){
                System.out.print((char)data);
                data = fis.read();
            }
            fis.close();
        }catch(FileNotFoundException e){
            e.printStackTrace();
        }catch(IOException e){
            e.printStackTrace();
        }
    }
    @Test
    public void test1(){
        String str = "123";
        str = "abc";
        int num = 0;
        try{
            num = Integer.parseInt(str);
            System.out.println("hello-----1");
        }catch(NumberFormatException e){
//            System.out.println("呈现数值转化反常了,不要着急....");
            //String getMessage():
//            System.out.println(e.getMessage());
            //printStackTrace():
            e.printStackTrace();
        }catch(NullPointerException e){
            System.out.println("呈现空指针反常了,不要着急....");
        }catch(Exception e){
            System.out.println("呈现反常了,不要着急....");
        }
        System.out.println(num);
        System.out.println("hello-----2");
    }
}

3.finally的运用

咱们再来深入了解一下关于finally的运用。

try-catch-finally中finally的运用:

  • finally 关键字用来创建在 try 代码块后边履行的代码块。
  • 不管是否发生反常,finally 代码块中的代码总会被履行。在 finally 代码块中,可以运转清理类型等收尾善后性质的句子。
  • finally 代码块呈现在 catch 代码块最终
  • finally是可选的
  • finally中声明的是必定会被履行的代码。即便catch中又呈现反常了,try中有return句子,catch中有return句子等状况。
  • 数据库连接、输入输出流、网络编程Socket等资源,JVM是不能主动的回收的,咱们需求自己手动的进行资源的释放。此时的资源释放,就需求声明在finally中。

五、反常处理机制二: throws

关于反常处理的第二种机制是throws抛出反常,下面咱们就来具体了解一下关于throws的运用。

1.声明抛出反常办法概述

反常处理的办法二:throws + 反常类型

  • “throws + 反常类型”写在办法的声明处。指明此办法履行时,或许会抛出的反常类型。

  • 一旦当办法体履行时,呈现反常,仍会在反常代码处生成一个反常类的目标,此目标满意throws后反常类型时,就会被抛出。反常代码后续的代码,就不再履行!

  • 假如一个办法没有捕获一个查看性反常,那么该办法有必要运用 throws 关键字来声明。throws 关键字放在办法签名的尾部。也可以运用 throw 关键字抛出一个反常,不管它是新实例化的仍是刚捕获到的。

  • 下面办法的声明抛出一个 RemoteException 反常:

 public static void main(String[] args) throws InterruptedException {
     Thread.sleep(123);
 }

一个办法可以声明抛出多个反常,多个反常之间用逗号隔开。

体会:

  • try-catch-finally:真正的将反常给处理掉了。
  • throws的办法仅仅将反常抛给了办法的调用者。 并没有真正将反常处理掉。

开发中怎么选择运用try-catch-finally 仍是运用throws?

  • 假如父类中被重写的办法没有以throws办法处理反常,则子类重写的办法也不能运用throws,意味着假如子类重写的办法中有反常,有必要运用try-catch-finally办法处理。
  • 履行的办法a中,先后又调用了另外的几个办法,这几个办法是递进联系履行的。咱们建议这几个办法运用throws的办法进行处理。而履行的办法a可以考虑运用try-catch-finally办法进行处理。

代码示例:

public class ExceptionTest2 {
    public static void main(String[] args){
        try{
            method2();
        }catch(IOException e){
            e.printStackTrace();
        }
//        method3();
    }
    public static void method3(){
        try {
            method2();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public static void method2() throws IOException{
        method1();
    }
    public static void method1() throws FileNotFoundException,IOException{
        File file = new File("hello1.txt");
        FileInputStream fis = new FileInputStream(file);
        int data = fis.read();
        while(data != -1){
            System.out.print((char)data);
            data = fis.read();
        }
        fis.close();
        System.out.println("hahaha!");
    }
}

图解:

Java异常概述与异常处理

2.重写办法声明抛出反常的原则

/*
 * 办法重写的规矩之一:
 * 子类重写的办法抛出的反常类型不大于父类被重写的办法抛出的反常类型
 */
public class OverrideTest {
    public static void main(String[] args) {
        OverrideTest test = new OverrideTest();
        test.display(new SubClass());
    }
    public void display(SuperClass s){
        try {
            s.method();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
class SuperClass{
    public void method() throws IOException{
    }
}
class SubClass extends SuperClass{
    public void method() throws FileNotFoundException{
    }
}

六、手动抛出反常

关于Java的处理除了以上咱们介绍的两种处理机制外,其实咱们也可以对其进行手动抛出。Java反常类目标除在程序履行过程中呈现反常时由体系主动生成并抛出,也可根据需求运用人工创建并抛出。手动抛出反常需求留意:

  • ①首要要生成反常类目标,然后经过throw句子完成抛出操作(提交给Java运转环境) : IOException e = new IOException(); throw e;
  • ②可以抛出的反常有必要是Throwable或其子类的实例。下面的句子在编译时将会发生语法过错:throw new String(“want to throw”);
public class StudentTest {
    public static void main(String[] args) {
        try {
            Student s = new Student();
            s.regist(-1001);
            System.out.println(s);
        } catch (Exception e) {
//            e.printStackTrace();
            System.out.println(e.getMessage());
        }
    }
}
class Student{
    private int id;
    public void regist(int id) throws Exception {
        if(id > 0){
            this.id = id;
        }else{
//            System.out.println("您输入的数据不合法!");
            //手动抛出反常目标
//            throw new RuntimeException("您输入的数据不合法!");
//            throw new Exception("您输入的数据不合法!");
            throw new MyException("不能输入负数");
            //过错的
//            throw new String("不能输入负数");
        }
    }
    @Override
    public String toString() {
        return "Student [id=" + id + "]";
    }
}

七、反常总结

下图是对反常的总结,反常处理五个关键字:try,catch,finally以及throw与throws,对应的便是两种反常处理机制。

Java异常概述与异常处理
世界上最遥远的距离,是我在if里你在else里,好像一向相伴又永久别离; 世界上最痴心的等候,是我当case你是switch,或许永久都选不上自己; 世界上最真情的相依,是你在try我在catch。不管你发神马脾气,我都默默承受,静静处理。到那时,再来期待咱们的finally。

关于Java反常的介绍就到这里了~

后记

本文呢为咱们介绍了Java反常相关常识,包括关于反常的概述,协助咱们理解了 什么是反常,反常的分类,反常分为编译时反常与运转时反常,以及关于反常的处理机制,Java反常处理机制有两种:try catch反常捕获与throws抛出反常。期望本文等共享可以对咱们有用。假如你想持续深入的学习Java相关的常识与技能,可以参阅:

Java全栈学习道路可参阅:【Java全栈学习道路】最全的Java学习道路及常识清单,Java自学方向指引,内含最全Java全栈学习技能清单~

Java异常概述与异常处理