前语

上一年又从头刷了路遥的《普通的国际》,最近也在朋友引荐下,来看了路遥的另一部成名作《人生》。 故事中的主人公高加林,虽生在乡村,面朝黄土背朝天,却不甘愿像父辈相同或许,一心想着脱节民语的捆绑,追求他的抱负日子。 但是命运多舛,在他所幻想的抱负日子中,一次次跌倒,终究不得不承认自己的普通,日子总得持续。 实践国际如此,代码国际里,咱们也希望一切都是抱负的。 咱们希望用户输入的数据格局永远是正确的,翻开的资源也必定存在,用户的硬件是正常的,用户的操作体系四安稳的,用户的网络也必定是疏通的等等

啪,还敢抛出异常
但是事与愿违,愿望是好的,实践是残酷的。 引进反常机制的目的就在于,当“人生”中呈现反常时,能够将其捕获并处理,确保咱们能更好的走下去,不至于呈现一点插曲,就停滞不前。

一、反常引进

因而呢就呈现了反常处理,咱们把或许呈现反常的事务代码放到try块中界说,把反常处理放到catch块中进行处理,确保程序遇到反常依然能持续运转,确保程序的健壮性。

① 咱们来看看高加林的一生中各种反常的处理

try{
	//事务逻辑,高加林的一生
	System.out.println("1、高中毕业,虽然没考上大学。却不断学习,在报纸上发表诗歌和散文,参加考试,谋得一份暂时教师职位");
	System.out.println("2、高加林的叔叔从外地回到家园作业,在县城担任劳作局局长,副局长为了讨好新上任的局长;");
	System.out.println("便私下给高加林走了后门,就这样高加林成为了公职人员");
	System.out.println("3、与高中同学黄亚萍相遇,再次相遇的两个人,志趣相投,相聊甚欢。高加林以为攀上黄亚萍这个高枝,能到大城市一展宏图");
}catch(ExceptionClass1 e1){
	System.out.println("第一个反常发生,教师职位被他人顶替");
	System.out.println("没有了作业,从头回到乡村,成为最不想当的农人");
	System.out.println("很快参加村里的劳作队伍里,白天尽力劳作,晚上看书学习,等待着东山再起的时机");
}catch(ExceptionClass2 e2){
	System.out.println("第二个反常发生,遭人举报走后门");
	System.out.println("再次丢了作业,一直想脱节农名身份的他,再次成为了农人");
}catch(ExceptionClass3 e3){
	System.out.println("第三个反常发生,纸包不住火,黄亚萍及其家人知道了高加林遭受举报");
	System.out.println("被打回原型,和黄亚萍断绝了联系");
	System.out.println("黄亚萍不想高加林回乡村,希望高加林去找叔父帮助,看是否能够持续留下来");
}catch(Exception e){
	System.out.println("再次跌倒的他,没再去找叔父");
	System.out.println("有了清醒的认知,自己的路还是得靠自己走下去");
}finally{
	System.out.println("承受实践,更好的走下去");
}

② 代码中

//未处理反常,程序遇到反常无法持续履行
public class ExceptionTest {
    public static void main(String[] args) {
        int num1 =7;
        int num2 =0;
        int res = num1/num2;
        System.out.println("程序持续履行......");
    }
}
//输出
Exception in thread "main" java.lang.ArithmeticException: / by zero
	at ExceptionTest.main(ExceptionTest.java:5)

参加反常处理

//参加反常处理,程序遇到反常后持续运转
public class ExceptionTest {
    public static void main(String[] args) {
        int num1 =7;
        int num2 =0;
        try {
            int res = num1/num2;
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
        System.out.println("程序持续履行......");
    }
}
//输出
/ by zero
程序持续履行......

小结: 假如履行try块里的事务逻辑代码呈现反常,体系会主动生成一个反常目标,该反常目标被体骄傲给Java运转时环境,这个进程成为抛出反常 Java运转时环境收到反常目标时,会寻觅能处理反常目标的catch块,假如找到适宜的catch块,则把该反常目标交给该catch块处理,这个进程被成为反常捕获; 假如Java运转环境找不到捕获反常的catch块,则运转时环境停止,Java程序已将退出

二、基本概念

程序履行中发生的不正常状况(语法过错逻辑过错不是反常)称为反常,一切的反常都承继与java.lang.Throwable Throwable 有两个重要子类

  • Error(过错):Java虚拟机无法解决的严重问题,咱们没办法经过 catch 来进行捕获 。如:内存溢出(OutOfMemoryError)、Java 虚拟机运转过错(Virtual MachineError)、类界说过错(NoClassDefFoundError)等。error 是严重过错,Java虚拟时机挑选线程停止
  • Exception(反常):编程过错或偶然的外在因素导致的一般问题,程序自身能够处理的反常,能够经过 catch 来进行捕获。Exception 又能够分 运转时反常(程序运转时发生的反常)和编译时反常(程序编译期间发生的反常,必需求处理的反常,不然代码无法编译经过)。

啪,还敢抛出异常

三、反常承继体系

:虚线为完成,实线为承继

啪,还敢抛出异常

四、常见运转反常

4.1 NullPointerException 空指针反常

啪,还敢抛出异常

public class ExceptionTest {
    public static void main(String[] args) {
       String str = null;
        System.out.println(str.length());
    }
}
//输出
Exception in thread "main" java.lang.NullPointerException
	at ExceptionTest.main(ExceptionTest.java:4)

4.2 ArithmeticException 数学运算反常

啪,还敢抛出异常

public class ExceptionTest {
    public static void main(String[] args) {
      int num1=7;
      int num2=0;
      int res = num1/num2;
    }
}
//输出
Exception in thread "main" java.lang.ArithmeticException: / by zero
	at ExceptionTest.main(ExceptionTest.java:5)

4.3 ArrayIndexOutOfBoundsException 数组下标越界反常

啪,还敢抛出异常

public class ExceptionTest {
    public static void main(String[] args) {
      String strarr[] ={"个人博客","www.xiezhrspace.cn","大众号","XiezhrSpace"};
        //留意数组下标时从0开端的
        for (int i = 0; i <= strarr.length; i++) {
            System.out.println(strarr[i]);
        }
    }
}
//输出
个人博客
www.xiezhrspace.cn
大众号
XiezhrSpace
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 4
	at ExceptionTest.main(ExceptionTest.java:5)

4.4 ClassCastException 类型转化反常

啪,还敢抛出异常

public class ExceptionTest {
    public static void main(String[] args) {
        Class1 class1 = new Class2();  //向上转型
        Class2 class2 = (Class2)class1; //向下转型
        Class3 class3= (Class3)class1;  //两个类没有联系,转型失利
    }
}
class Class1{}
class Class2 extends Class1{};
class Class3 extends Class1{};
//输出
Exception in thread "main" java.lang.ClassCastException: Class2 cannot be cast to Class3
	at ExceptionTest.main(ExceptionTest.java:5)

4.5 NumberFormatException 数字格局不正确反常

啪,还敢抛出异常

public class ExceptionTest {
    public static void main(String[] args) {
      String str1 ="123";
      String str2 = "abc";
        System.out.println(Integer.valueOf(str1));   //能够转成功
        System.out.println(Integer.valueOf(str2));
    }
}
//输出
123
Exception in thread "main" java.lang.NumberFormatException: For input string: "abc"
	at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
	at java.lang.Integer.parseInt(Integer.java:580)
	at java.lang.Integer.valueOf(Integer.java:766)
	at ExceptionTest.main(ExceptionTest.java:6)

五、常见编译时反常

编译期间就有必要处理的反常,不然代码不能经过编译

啪,还敢抛出异常

5.1 FileNotFoundException 操作一个不存在的文件时分发生反常

啪,还敢抛出异常

5.2 ClassNotFoundException 加载类,类不存在时反常

啪,还敢抛出异常

5.3 SQLException 操作数据库,查询表时发生反常

啪,还敢抛出异常

5.4 IllegalArgumentException 参数不匹配时发生反常

啪,还敢抛出异常

六、反常处理

Java 的反常处理经过 5 个要害字来完成:trycatchthrowthrowsfinallytry catch 句子用于捕获并自行处理反常 finally 句子用于在任何状况下(除特殊状况外)都有必要履行的代码 throw 手动生成反常目标 throws 将发生的反常抛出,交给调用者处理,最顶级的处理者是JVM

6.1 反常处理办法

  • try-catch-finally :在代码中捕获反常自行处理
  • throws :将发生的反常抛出,交给调用者处理,最顶级处理者是JVM

注: try-catch-finally 和throws 任选一种即可

6.2 反常处理

6.2.1 try-catch-finally

①原理图

啪,还敢抛出异常
②语法结构

try {
    逻辑程序块  //或许有反常的代码
} catch(Exception e) {
	/*
	①发生反常时,体系将反常封装成Exception目标e,并传递给catch
	②得到反常Exception e 后,程序员自行处理
	注:只有发生反常时分,catch代码块才履行
    */
    捕获反常
    throw(e);  
} finally {
    释放资源代码块
}

③实例 小提示:选中代码,按下快捷键ctrl+alt+t 能够呼出代码提示

public class TestException {
    public static void main(String[] args) {
        int num1 =10;
        int num2 =0;
        try {
            int num3=num1/num2;
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            System.out.println("finally代码块被履行了");
        }
        System.out.println("程序持续履行...");
    }
}
//输出
java.lang.ArithmeticException: / by zero
	at com.xiezhr.TestException.main(TestException.java:9)
finally代码块被履行了
程序持续履行...

上述代码中,把或许呈现反常的代码num1/num2; 放到了try句子块中,当代码发生反常时,在catch中捕获反常,并打印反常。不论有没有发生反常,finally 句子块里的代码都会履行。发生反常后程序并没有停止,最终输出 “程序持续履行…”

6.2.2 try-with-resources

Java中,关于文件操作IO流、数据库连接等开销非常昂贵的资源,用完之后有必要及时经过close办法将>其封闭,不然资源会一直处于翻开状况,或许会导致内存走漏等问题。 封闭资源的常用办法便是在finally块里调用close办法将资源封闭

所以关于流的操作咱们常常回用到如下代码

//读取文本文件的内容
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;
public class TestException {
    public static void main(String[] args) {
        Scanner scanner = null;
        try {
            scanner = new Scanner(new File("D://xiezhr.txt"));
            while (scanner.hasNext()) {
                System.out.println(scanner.nextLine());
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } finally {
            if (scanner != null) {
                scanner.close();
            }
        }
    }
}
//输出
个人博客:www.xiezhrspace.cn
个人大众号:XiezhrSpace
欢迎你重视

剖析: 上述代码中咱们经过io流读取D盘xiezhr.txt文件中的内容,并将其内容按行打印出来。最终在finally代码块中将scanner封闭

Java 7 之后,新的语法糖 try-with-resources 能够简写上述代码

import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;
public class TestException {
    public static void main(String[] args) {
        try(Scanner scanner = new Scanner(new File("D://xiezhr.txt"))){
            while (scanner.hasNext()) {
                System.out.println(scanner.nextLine());
            }
        }catch (FileNotFoundException e){
            e.printStackTrace();
        }
    }
}
//输出
个人博客:www.xiezhrspace.cn
个人大众号:XiezhrSpace
欢迎你重视

两段代码完成的功能是相同的,但明显try-with-resources 写法简单了很多。咱们也更发起优先运用 try-with-resources 而不是try-finally

6.2.3 抛出反常

当一个办法发生某种反常,但是不确定怎么处理这种反常,那么就需求在该办法的头部显示地声明抛出反常,标明该办法将不对这些反常进行处理,而用该办法调用者负责处理

6.2.3.1 thorows

①语法格局 returnType method_name(paramList) throws Exception 1,Exception2,…{…}

  • returnType 标明回来值类型
  • method_name 标明办法名
  • paramList 标明参数列表;
  • Exception 1,Exception2,… 标明反常类 假如有多个反常类,它们之间用逗号分隔。这些反常类能够是办法中调用了或许拋出反常的办法而发生的反常,也能够是办法体中生成并拋出的反常

②运用场景 当时办法不知道怎么处理这种类型的反常,该反常应该由向上一级的调用者处理; 假如 main 办法也不知道怎么处理这种类型的反常,也能够运用 throws 声明抛出反常,该反常将交给 JVM 处理。 JVM 对反常的处理办法是,打印反常的跟踪栈信息,并间断程序运转,这便是前面程序在遇到反常后主动完毕的原因

③实践操作


import java.io.File;
import java.io.IOException;
import java.util.Scanner;
public class TestException {
    //界说办法时声明抛出反常,办法中呈现的反常自己不处理,交由调用者处理
    public void readfile() throws IOException {
        // 读取文件
        Scanner scanner = new Scanner(new File("D://xiezhr.txt"));
        while (scanner.hasNext()) {
            System.out.println(scanner.nextLine());
        }
        scanner.close();
    }
    public static void main(String[] args)  {
        TestException tt = new TestException();
        try {
            //调用readfile办法
            tt.readfile();
        } catch (IOException e) {
            //打印反常
            e.printStackTrace();
        }
    }
}
//输出
个人博客:www.xiezhrspace.cn
个人大众号:XiezhrSpace
欢迎你重视

剖析: 以上代码,首先在界说 readFile() 办法时用 throws 要害字声明在该办法中或许发生的反常,然后在 main() 办法中调用readFile() 办法,并运用 catch 句子捕获发生的反常

④ 反常处理流程图

啪,还敢抛出异常

留意:子类办法声明抛出的反常类型应该是父类办法声明抛出的反常类型的子类或相同,子类办法声明抛出的反常不允许比父类办法声明抛出的反常多。

//下面程序编译就报错,原因时子类抛出比父类还大的反常

 public class OverrideThrows {
        public void test() throws IOException {
            FileInputStream fis = new FileInputStream("a.txt");
        }
    }
  class Sub extends OverrideThrows {
      // 子类办法声明抛出了比父类办法更大的反常
      public void test() throws Exception {
      }
  }
6.2.3.1 throw

throw用来直接抛出一个反常

①语法 throw ExceptionObject;

  • ExceptionObject 有必要是 Throwable 类或其子类的目标
  • 假如是自界说反常类,也有必要是 Throwable 的直接或直接子类

②履行原理 throw 句子履行时,它后边的句子将不履行; 此时程序转向调用者程序,寻觅与之相匹配的 catch 句子,履行相应的反常处理程序。 假如没有找到相匹配的 catch 句子,则再转向上一层的调用程序。这样逐层向上,直到最外层的反常处理程序停止程序并打印出调用栈状况

③实践操作


import java.util.Scanner;
public class TestException {
    public boolean validateUserName(String username) {
        boolean con = false;
        if (username.length() >4) {
            // 判别用户名长度是否大于8位
            if ("admin".equals(username)) {
                con = true;
            }else{
                throw new IllegalArgumentException("你输入的用户名不对");
            }
        } else {
            throw new IllegalArgumentException("用户名长度有必要大于 4 位!");
        }
        return con;
    }
    public static void main(String[] args) {
        TestException te = new TestException();
        Scanner input = new Scanner(System.in);
        System.out.println("请输入用户名:");
        String username = input.next();
        try {
            boolean con = te.validateUserName(username);
            if (con) {
                System.out.println("用户名输入正确!");
            }
        } catch (IllegalArgumentException e) {
            System.out.println(e);
        }
    }
}
//输出
①
请输入用户名:
abc
java.lang.IllegalArgumentException: 用户名长度有必要大于 4 位!
②
请输入用户名:
abcdef
java.lang.IllegalArgumentException: 你输入的用户名不对
③
请输入用户名:
admin
用户名输入正确!
6.2.4 自界说反常

当Java供给的内置反常类型不能满意咱们的需求时,咱们能够规划自己的反常类型。

①语法格局 class XXXException extends Exception|RuntimeException

  • 一般将自界说反常类的类名命名为 XXXException,其间 XXX 用来代表该反常的作用
  • 自界说反常类需求承继 Exception 类或其子类,假如自界说运转时反常类需承继 RuntimeException 类或其子类
  • 自界说反常类一般包含两个结构办法:一个是无参的默许结构办法,另一个结构办法以字符串的办法接收一个定制的反常音讯,并将该音讯传递给超类的结构办法。

②实践操作

import java.util.Scanner;
public class TestException {
    public static void main(String[] args) {
       int age;
       Scanner scanner = new Scanner(System.in);
        System.out.println("请输入你的年纪");
        age=scanner.nextInt();
        try {
            if(age < 0) {
                throw new AgeException("您输入的年纪为负数!输入有误!");
            } else if(age > 100) {
                throw new AgeException("您输入的年纪大于100!输入有误!");
            } else {
                System.out.println("您的年纪为:"+age);
            }
        } catch (AgeException e) {
            e.printStackTrace();
        }
    }
}
//输出
①
请输入你的年纪
120
com.xiezhr.AgeException: 您输入的年纪大于100!输入有误!
	at com.xiezhr.TestException.main(TestException.java:15)
②
请输入你的年纪
-34
com.xiezhr.AgeException: 您输入的年纪为负数!输入有误!
	at com.xiezhr.TestException.main(TestException.java:13)
③
请输入你的年纪
30
您的年纪为:30

6.2.5 多反常捕获

Java7今后,catch 句子能够有多个,用来匹配多个反常

①语法格局

try{
    // 或许会发生反常的句子
} catch (IOException | ParseException e) {
    // 反常处理
}
  • 多种反常类型之间用竖线|离隔
  • 反常变量有隐式的 final 修饰,因而程序不能对反常变量从头赋值

② 反常书写办法改变

try{
    // 或许会发生反常的句子
} catch (FileNotFoundException e) {
    // 调用办法methodA处理
} catch (IOException e) {
    // 调用办法methodA处理
} catch (ParseException e) {
    // 调用办法methodA处理
}

变成

try{
    // 或许会发生反常的句子
} catch (FileNotFoundException | IOException | ParseException e) {
    // 调用办法处理
} 

③实践操作

import java.util.Scanner;
public class TestException {
    public static void main(String[] args) {
       int num1;
       int num2;
        try {
            Scanner scanner = new Scanner(System.in);
            System.out.println("请输入num1的值");
            num1=scanner.nextInt();
            System.out.println("请输入num2的值");
            num2=scanner.nextInt();
            int num= num1/num2;
            System.out.println("你输入的两个数相除,成果是" + num);
        } catch (IndexOutOfBoundsException | NumberFormatException | ArithmeticException e){
            System.out.println("程序发生了数组越界、数字格局反常、算术反常之一");
            e.printStackTrace();
        }
        catch (Exception e) {
            System.out.println("不知道反常");
            e.printStackTrace();
        }
    }
}
//输出
①
请输入num1的值
12
请输入num2的值
0
程序发生了数组越界、数字格局反常、算术反常之一
java.lang.ArithmeticException: / by zero
	at com.xiezhr.TestException.main(TestException.java:15)
②
请输入num1的值
6888888888
不知道反常
java.util.InputMismatchException: For input string: "6888888888"
	at java.util.Scanner.nextInt(Scanner.java:2123)
	at java.util.Scanner.nextInt(Scanner.java:2076)
	at com.xiezhr.TestException.main(TestException.java:12)
③
请输入num1的值
12
请输入num2的值
4
你输入的两个数相除,成果是3

剖析: 上面程序中IndexOutOfBoundsException|NumberFormatException|ArithmeticException来界说反常类型,这就标明该 catch 块能够一起捕获这 3 种类型的反常

七、Throwable 类常用办法

  • String getMessage(): 回来反常发生时的扼要描绘
  • String toString(): 回来反常发生时的详细信息
  • String getLocalizedMessage(): 回来反常目标的本地化信息。运用 Throwable 的子类掩盖这个办法,能够生成本地化信息。假如子类没有掩盖该办法,则该办法回来的信息与 getMessage()回来的成果相同
  • void printStackTrace(): 在操控台上打印 Throwable 目标封装的反常信息

八、易混概念

8.1 Error和Exception的异同

  • Error Exception 都有共同的先人Throwable,即ErrorException 都是Throwable的子类
  • Exception :程序自身能够处理的反常,能够经过 try-catch 来进行捕获。Exception又能够分为 Checked Exception (受查看反常,有必要处理) 和 Unchecked Exception(不受查看反常,能够不处理)。
  • ErrorError 属于程序无法处理的过错 ,咱们没办法经过 try-catch 来进行捕获 。例如 Java 虚拟机运转过错(Virtual MachineError)、虚拟机内存不够过错(OutOfMemoryError)、类界说过错(NoClassDefFoundError)等 。这些反常发生时,Java 虚拟机(JVM)一般会挑选线程停止

8.2 throw和throws的区别

  • throws 用来声明一个办法或许抛出的一切反常信息,标明呈现反常的一种或许性,但并不必定会发生这些反常;throw 则是指拋出的一个详细的反常类型,履行 throw 则必定抛出了某种反常目标。
  • 通常在一个办法(类)的声明处经过 throws 声明办法(类)或许拋出的反常信息,而在办法(类)内部经过 throw声明一个详细的反常信息。
  • throws 通常不必显示地捕获反常,可由体系主动将一切捕获的反常信息抛给上级办法; throw 则需求用户自己捕获相关的反常,而后再对其进行相关包装,最终将包装后的反常信息抛出

8.3 Checked Exception 和 Unchecked Exception

  • Checked Exception 受查看反常 Java 代码在编译进程中,假如受查看反常没有被 catch或许throws 要害字处理的话,就没办法经过编译
  • 常见的受查看反常有: IO 相关的反常、ClassNotFoundExceptionSQLException
  • 例如,下面便是 –
    啪,还敢抛出异常
  • Unchecked Exception 即 不受查看反常 ,Java 代码在编译进程中 ,咱们即便不处理不受查看反常也能够正常经过编译。
  • 咱们常常看到的有以下几种 NullPointerException :空指针过错 IllegalArgumentException :参数过错比方办法入参类型过错 NumberFormatException:字符串转化为数字格局过错 ArrayIndexOutOfBoundsException:数组越界过错 ClassCastException:类型转化过错 ArithmeticException:算术过错 SecurityException :安全过错比方权限不够 UnsupportedOperationException:不支持的操作过错比方重复创立同一用户

8.4 try-with-resources 与 try-catch-finally

  • try-with-resources 是Java 1.7增加的新语法糖,在try 代码块完毕之前会主动封闭资源。
  • try-with-resources 适用于任何完成 java.lang.AutoCloseable或许 java.io.Closeable 的目标, 字节输入流(InputStream),字节输出流(OutputStream),字符输入流(Reader),字符输出流(Writer)均完成了这接口
  • try-catch-finally 没有约束条件,finally不仅能够封闭资源,还能够用于履行其他代码块;
  • try-with-resources 代码更加简练,有约束条件,资源会立即被封闭
  • finally封闭资源不会立即封闭,取决与网络和体系,或许会很快,也或许会等一两天,所以,最好不要运用finally作为事务流程的操控,在《Effective java》一书 的第9条:try-with-resources优先于try-finally 中有相关详细的介绍,其间提到了许多由于finally推迟导致的网络事情

九、SpringBoot 中高雅的处理一致反常回来

日常开发中,咱们处理反常一般都会用到try-catchthrowthrows 的办法抛出反常。 这种办法不经程序员处理麻烦,对用户来说也不太友好 咱们都希望不必写过多的重复代码处理反常,又能提升用户体验。这时分大局反常处理就显得很快捷很重要了

9.1 大局反常捕获与处理

 Springboot对供给了一个 @ControllerAdvice注解以及 @ExceptionHandler注解,分别用于开启大局的反常捕获和阐明捕获哪些反常,对那些反常进行处理。

@ControllerAdvice
public class MyExceptionHandler {
    @ExceptionHandler(value =Exception.class)
	public String exceptionHandler(Exception e){
		System.out.println("呈现了一个反常"+e);
       	return e.getMessage();
    }
}

剖析 上面这段代码便是说,只要是代码运转进程中有反常就会进行捕获,并输出出这个反常。然后咱们随意编写一个会发生反常的代码,测验出来的反常是这样的。

啪,还敢抛出异常
这关于前后端别离来说这样的报错对用户并不好,前后端别离之后仅有的交互便是json了,咱们也希望将后端的反常变成json回来给前端处理。

9.2 用枚举类型记载已知过错信息与成功信息

ErrorEnum枚举类中界说了常见的过错码以及过错的提示信息。 SuccEnum 枚举类中界说了成功码及成功提示信息

至于这里为什么用枚举就不详细说了,网上文章说说的也比较多了 详细能够参照:Java 枚举(enum) 详解7种常见的用法

① 已知过错信息

public enum ErrorEnum {
    // 数据操作过错界说
    NO_PERMISSION(403,"没有权限拜访"),
    NO_AUTH(401,"请先登录体系"),
    NOT_FOUND(404, "未找到该资源!"),
    USER_NOT_FIND(402, "未找到用户信息"),
    INTERNAL_SERVER_ERROR(500, "服务器出问题了"),
    UNKNOW_ERR(-1,"不知道过错")
    ;
    /** 过错码 */
    private Integer errorCode;
    /** 过错信息 */
    private String errorMsg;
    ErrorEnum(Integer errorCode, String errorMsg) {
        this.errorCode = errorCode;
        this.errorMsg = errorMsg;
    }
    public Integer getErrorCode() {
        return errorCode;
    }
    public String getErrorMsg() {
        return errorMsg;
    }
}

② 成功信息

public enum SuccEnum  {
    SUCCESS(200, "success");
    /** 成功码 **/
    private Integer succCode;
    /* 成功信息*/
    private String succMsg;
    SuccEnum(Integer succCode, String succMsg) {
        this.succCode = succCode;
        this.succMsg = succMsg;
    }
    public Integer getSuccCode() {
        return succCode;
    }
    public String getSuccMsg() {
        return succMsg;
    }
}

9.3 界说一致成果回来与反常回来

  • success:用boolean 类型标识,标识是否成功
  • code: 状况码,区分各种报错信息与成功回来
  • msg : 成功或过错提示信息
  • data : 回来的数据
@Data
public class Result<T> {
    //是否成功
    private Boolean success;
    //状况码
    private Integer code;
    //提示信息
    private String msg;
    //数据
    private T data;
    public Result() {
    }
    //自界说回来成果的结构办法
    public  Result(Boolean success,Integer code, String msg,T data) {
        this.success = success;
        this.code = code;
        this.msg = msg;
        this.data = data;
    }
}

9.4 封装工具类回来成果

这里咱们界说好了一致的成果回来,其间里面的静态办法是用来当程序反常的时分转化成反常回来规定的格局。

public class ResultUtil {
    //成功,并回来详细数据
    public static Result success(SuccEnum succEnum,Object obj){
        Result result = new Result();
        result.setSuccess(true);
        result.setMsg(succEnum.getSuccMsg());
        result.setCode(succEnum.getSuccCode());
        result.setData(obj);
        return result;
    }
    //成功,无数据回来
    public static Result succes(SuccEnum succEnum){
        Result result = new Result();
        result.setSuccess(true);
        result.setMsg(succEnum.getSuccMsg());
        result.setCode(succEnum.getSuccCode());
        result.setData(null);
        return result;
    }
    //自界说反常回来的成果
    public static Result defineError(DefinitionException de){
        Result result = new Result();
        result.setSuccess(false);
        result.setCode(de.getErrorCode());
        result.setMsg(de.getErrorMsg());
        result.setData(null);
        return result;
    }
    //其他反常处理办法回来的成果
    public static Result otherError(ErrorEnum errorEnum){
        Result result = new Result();
        result.setSuccess(false);
        result.setMsg(errorEnum.getErrorMsg());
        result.setCode(errorEnum.getErrorCode());
        result.setData(null);
        return result;
    }
}

9.5 自界说反常

内置反常不能满意咱们事务需求的时分,咱们就需求自界说反常

public class DefinitionException extends RuntimeException {
    protected Integer errorCode;
    protected String errorMsg;
    public DefinitionException(){
    }
    public DefinitionException(Integer errorCode, String errorMsg) {
        this.errorCode = errorCode;
        this.errorMsg = errorMsg;
    }
    public Integer getErrorCode() {
        return errorCode;
    }
    public void setErrorCode(Integer errorCode) {
        this.errorCode = errorCode;
    }
    public String getErrorMsg() {
        return errorMsg;
    }
    public void setErrorMsg(String errorMsg) {
        this.errorMsg = errorMsg;
    }
}

9.6 界说大局反常处理类

咱们自界说一个大局反常处理类,来处理各种反常,包括自己界说的反常内部反常。这样能够简化不少代码,不必自己对每个反常都运用try,catch的办法来完成

@ControllerAdvice
public class GlobalExceptionHandler {
    /**
     * 处理自界说反常
     *
     */
    @ExceptionHandler(value = DefinitionException.class)
    @ResponseBody
    public Result bizExceptionHandler(DefinitionException e) {
        return ResultUtil.defineError(e);
    }
    /**
     * 处理其他反常
     *
     */
    @ExceptionHandler(value = Exception.class)
    @ResponseBody
    public Result exceptionHandler( Exception e) {
        return ResultUtil.otherError(ErrorEnum.UNKNOW_ERR);
    }
}

阐明: 办法上面加上一个 @ResponseBody的注解,用于将目标解析成json,便利前后端的交互,也能够运用 @ResponseBody放在反常类上面

9.7 代码测验

9.7.1 界说User实体类
@Data
public class User {
    //仅有标识id
    private Integer id;
    //名字
    private String name;
    //性别
    private String sex;
    //年纪
    private Integer age;
}
9.7.2 界说controller类
@RestController
@RequestMapping("/result")
public class ExceptionController {
    @Autowired
    private GlobalExceptionHandler globalExceptionHandler;
    @GetMapping("/getUser")
    public Result getStudent(){
        User user = new User();
        user.setId(100);
        user.setName("xiezhr");
        user.setAge(21);
        user.setSex("男");
        Result result = ResultUtil.success(SuccEnum.SUCCESS, user);
        return result;
    }
    @GetMapping("/getDefException")
    public Result DeException(){
        throw new DefinitionException(400,"我犯错了");
    }
    @GetMapping("/getException")
    public Result Exception(@RequestParam("name") String name, @RequestParam("pwd") String pwd){
        Result result = ResultUtil.success(SuccEnum.SUCCESS);
        try {
            if ("admin".equals(name)){
                User user = new User();
                user.setId(101);
                user.setName("xiezhr");
                user.setAge(18);
                user.setSex("男");
                result =  ResultUtil.success(SuccEnum.SUCCESS,user);
            }else if (name.equals("xiezhr")){
                result =  ResultUtil.otherError(ErrorEnum.USER_NOT_FIND);
            }else{
                int i = 1/0;
            }
        }catch (Exception e){
            result =  globalExceptionHandler.exceptionHandler(e);
        }
        return result;
    }
}

9.8 接口测验

9.8.1 获取没有反常的数据回来

http://localhost:8090/result/getUser

啪,还敢抛出异常

9.8.2 自界说反常回来

http://localhost:8090/result/getDefException

啪,还敢抛出异常
http://localhost:8090/result/getException?name=xiezhr&pwd=123
啪,还敢抛出异常

9.8.3 其他的反常 回来

http://localhost:8090/result/getException?name=ff&pwd=abc

啪,还敢抛出异常

十、反常处理及规约

反常的处理⽅式有两种。 1、 ⾃⼰处理。 2、 向上抛, 交给调⽤者处理。

反常, 千万不能捕获了之后什么也不做。 或许只是使⽤e.printStacktrace。

详细的处理⽅式的挑选其实准则⽐较简明: ⾃⼰清晰的知道怎么处理的, 就要处理掉。 不知道怎么处理的, 就交给调⽤者处理。

下面时阿里巴巴Java开发手册关于反常处理规则

①【强制】 Java类库中界说的能够经过预查看办法规避的RuntimeException不应该经过catch的办法处理,如NullPointerExceptionIndexOutOfBoundsException

阐明:无法经过预查看的反常不在此列,比方当解析字符串办法的数字时,或许存在数字格局过错,经过catch NumberFormatException完成

正例:

if(obj!=null){....}

反例:

try{
	obj.method();
}catch(NullPointerException e){
	...
}

②【强制】 反常捕获后不要用来做流程操控和条件操控

阐明:反常规划的初衷是解决程序运转中各种意外,且反常的处理效率比条件判别办法要第很多。

③【强制】 catch 时请辨明安稳代码和非安稳代码。安稳代码一般指本机运转且履行成果确定性高的代码。关于非安稳代码的catch 尽或许在进行反常类型的分区后,再做对应的反常处理

阐明:对大段代码进行try-catch,将使程序无法依据不同的反常做出正确的“应激”反应,也不利于定位问题,这是一种不负责的表现

正例:在用户注册场景中,假如用户输入不合法字符串,或用户名称已存在,或用户输入的密码过于简单,那么程序会作出分门别类的判别并提示用户。

④【强制】 捕获反常使为了处理反常,不要捕获了却阐明都不处理而抛弃之,假如不想处理它,请将反常抛给它的调用者。最外层的事务运用者有必要处理反常,将其转化为用户能够了解的内容。

⑤【强制】 在事务场景中,抛出反常被catch 后,假如需求回滚,那么必定要留意手动回滚事务。

⑥【强制】 finally 块有必要对资源目标、流目标进行封闭操作,假如有一次要做try-catch操作。

阐明:关于JDK7即以上版别,能够运用try-catch-resource 办法

⑦【强制】 不要在finally块中运用return

阐明try 块中return 句子履行成功后,并不马上回来,而是持续履行finally 块中的句子,假如此处存在return句子,则在此直接回来,无情地丢弃try块中的回来点。

正例:

private int x =0;
public int checkReturn(){
	try{
		//x=1,此处不回来
		return ++x;
	}finally{
		//回来的成果是2
		return ++x;
	}
}

⑧【强制】 捕获反常与抛出反常有必要彻底匹配,或许捕获反常时抛出反常的父类。

阐明:假如以及对方抛出的时绣球,实践接收到的时铅球,就会发生意外

⑨【强制】 在调用RPC、二方包或动态生成类的相关办法时,捕获反常有必要运用Throwable拦截。

阐明:经过反射机制调用办法,假如找不到办法,则抛出NoSuchMethodException。 在阐明状况下抛出NoSuchMethodException呢?二方包在类冲突时,裁定机制或许导致引进非预期的版别使类的办法签名不匹配,或许在字节码修正结构(比方:ASM)动态创立或修正类时,修正了相应的办法签名。关于这些状况,即便在代码编译期是正确的,在代码运转期也会抛出NoSuchMethodException

⑩【引荐】 办法的回来值能够为null,不强制回来空调集或许空目标等,有必要增加注释充分阐明在阐明状况下会回来null值。此时数据库id不支持存入负数二抛出反常。

阐明:本手册清晰,防止发生NPE是调用者的职责。即便被调用办法回来空调集或许空目标,对调用者来说,也并非高枕无忧,有必要考虑长途调用失利、序列化失利、运转时反常等场景回来null值的状况

⑪【引荐】 防止发生NPE时程序员的基本修养,留意NPE发生的场景。 1)当回来类型为基本数据类型,return 包装数据类型的目标时,主动拆箱有或许发生NPE 反例:

public int f(){
	//假如为null,则主动拆箱,抛NPE。
	return Integer 目标;
}

2)数据库的查询成果或许为null 3) 调集里的元素即便isNotEmpty , 取出的数据元素也有或许为null

  1. 当长途调用回来目标时,一概要求进行空指针判别,以防止发生NPE。 5)关于Session 中获取的数据,建议进行NPE查看,以防止空指针 6)级联调用obj.getA().getB().getC();的一连串调用简单发生NPE。

⑫【引荐】 界说时区分 unchecked/checked 反常,防止直接抛出new RuntimeException(),更不允许抛出Exception 或许Throwable,应该运用事务意义的自界说反常。引荐业界已界说过的自界说反常,如:DAOException / ServiceException

⑬ 【参阅】 关于公司外的HTTP/API 开放接口,有必要运用“errorCode”:运用内部引荐反常抛出; 跨运用间RPC调用优先考虑运用Result 办法,封装isSuccess() 办法、errorCodeerrorMessage

阐明:关于RPC办法回来办法运用Result办法的理由 1)运用抛出反常回来办法,调用办法假如没有捕获到,就会发生运转时过错 2)假如不加栈信息,常识new自界说反常,参加自己了解的errorMesage,关于调用解决问题的帮助不会太多。假如加了栈信息,在频繁调用犯错的状况下,数据序列化和传输的性能损耗也是问题。

⑭【参阅】 防止呈现重复的代码(Don't Repeat Yourself),即DRY准则

阐明: 随意复制和粘贴代码,必然导致代码的重复,当今后需求修正时,需求修正一切的副本,简单遗失。必要时抽取共性办法或公共类,乃至将代码组件化。

正例: 一个类中由多个public 办法,都需求进行数行相同的参数校验操作,这个时分请抽取:

private boolean checkParam(DTO dto){...}

本期内容到这就完毕了,各位小伙伴们,咱们下期见 (●’◡’●)