当前位置: 首页 > news >正文

啪,还敢抛出异常

🙉 作者简介: 全栈领域新星创作者 ;天天被业务折腾得死去活来的同时依然保有对各项技术热忱的追求,把分享变成一种习惯,再小的帆也能远航。

🏡 个人主页:xiezhr的个人主页

前言

去年又重新刷了路遥的《平凡的世界》,最近也在朋友推荐下,来看了路遥的另一部成名作《人生》。
故事中的主人公高加林,虽生在农村,面朝黄土背朝天,却不甘心像父辈一样或者,一心想着摆脱民语的束缚,追求他的理想生活。
然而命运多舛,在他所想象的理想生活中,一次次跌倒,最终不得不承认自己的平凡,生活总得继续。
现实世界如此,代码世界里,我们也希望一切都是理想的。
我们希望用户输入的数据格式永远是正确的,打开的资源也一定存在,用户的硬件是正常的,用户的操作系统四稳定的,用户的网络也一定是畅通的等等
还敢抛异常
然而事与愿违,愿望是好的,现实是残酷的。
引入异常机制的目的就在于,当“人生”中出现异常时,能够将其捕获并处理,保证我们能更好的走下去,不至于出现一点插曲,就停滞不前。

一、异常引入

因此呢就出现了异常处理,我们把可能出现异常的业务代码放到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 zeroat 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.NullPointerExceptionat 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 zeroat 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: 4at 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 Class3at 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 个关键字来实现:trycatchthrowthrowsfinally
try 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 zeroat 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 zeroat 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)@ResponseBodypublic Result bizExceptionHandler(DefinitionException e) {return ResultUtil.defineError(e);}/*** 处理其他异常**/@ExceptionHandler(value = Exception.class)@ResponseBodypublic Result exceptionHandler( Exception e) {return ResultUtil.otherError(ErrorEnum.UNKNOW_ERR);}
}

说明: 方法上面加上一个 @ResponseBody的注解,用于将对象解析成json,方便前后端的交互,也可以使用 @ResponseBody放在异常类上面

9.7 代码测试

9.7.1 定义User实体类
@Data
public class User {//唯一标识idprivate Integer id;//姓名private String name;//性别private String sex;//年龄private Integer age;
}
9.7.2 定义controller类
@RestController
@RequestMapping("/result")
public class ExceptionController {@Autowiredprivate 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{//返回的结果是2return ++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
4) 当远程调用返回对象时,一律要求进行空指针判断,以防止产生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){...}

本期内容到这就结束了,各位小伙伴们,我们下期见 (●’◡’●)

相关文章:

啪,还敢抛出异常

&#x1f649; 作者简介&#xff1a; 全栈领域新星创作者 &#xff1b;天天被业务折腾得死去活来的同时依然保有对各项技术热忱的追求&#xff0c;把分享变成一种习惯&#xff0c;再小的帆也能远航。 &#x1f3e1; 个人主页&#xff1a;xiezhr的个人主页 前言 去年又重新刷了…...

Apache JMeter 5.5 下载安装以及设置中文教程

Apache JMeter 5.5 下载安装以及设置中文教程JMeter下载Apache JMeter 5.5配置环境变量查看配置JDK配置JMeter环境变量运行JMeter配置中文版一次性永久设置正文JMeter 下载Apache JMeter 5.5 官方网站&#xff1a;Apache JMeter 官网 版本介绍&#xff1a; 版本中一个是Bina…...

string类模拟实现

了解过string常用接口后&#xff0c;接下来的任务就是模拟实现string类。 目录 VS下的string结构 默认成员函数和简单接口 string结构 c_str()、size()、capacity()、clear()、swap() 构造函数 拷贝构造函数 赋值重载 析构函数 访问及遍历 容量操作 reserve resize …...

cadence SPB17.4 S032 - allegro - 保存/载入光绘层定义

文章目录cadence SPB17.4 S032 - allegro - 保存/载入光绘层定义概述保存光绘层在新板子中载入已经保存的相同类型老板子定义好的光绘层定义文件碎碎念ENDcadence SPB17.4 S032 - allegro - 保存/载入光绘层定义 概述 以前布线完成, 准备出板厂文件时, 总是要手工重新建立光绘…...

微服务实战--高级篇:分布式缓存 Redis

分布式缓存 – 基于Redis集群解决单机Redis存在的问题 单机的Redis存在四大问题&#xff1a; 1.Redis持久化 Redis有两种持久化方案&#xff1a; RDB持久化AOF持久化 1.1.RDB持久化 RDB全称Redis Database Backup file&#xff08;Redis数据备份文件&#xff09;&#xf…...

【C语言】可变参数列表

本篇博客让我们来认识一下C语言学习过程中往往被忽略的可变参数列表 所谓可变参数&#xff0c;就是一个不限定参数数量的函数&#xff0c;我们可以往里面传入任意个数的参数&#xff0c;以达成某些目的。 关联&#xff1a;C11可变模板参数&#xff1b;本文首发于 慕雪的寒舍 …...

目标检测的旋框框文献学习

这是最近打算看完的文献&#xff0c;一天一篇 接下来将记录一下文献阅读笔记&#xff0c;避免过两天就忘了 RRPN 论文题目&#xff1a;Arbitrary-Oriented Scene Text Detection via Rotation Proposals 论文题目&#xff1a;通过旋转方案进行任意方向的场景文本检测&#x…...

Hive 在工作中的调优总结

总结了一下在以往工作中&#xff0c;对于Hive SQL调优的一些实际应用&#xff0c;是日常积累的一些优化技巧&#xff0c;如有出入&#xff0c;欢迎在评论区留言探讨~ EXPLAIN 查看执行计划 建表优化 分区 分区表基本操作&#xff0c;partitioned二级分区动态分区 分桶 分…...

每天一道大厂SQL题【Day09】充值日志SQL实战

每天一道大厂SQL题【Day09】充值日志SQL实战 大家好&#xff0c;我是Maynor。相信大家和我一样&#xff0c;都有一个大厂梦&#xff0c;作为一名资深大数据选手&#xff0c;深知SQL重要性&#xff0c;接下来我准备用100天时间&#xff0c;基于大数据岗面试中的经典SQL题&#…...

MATLAB 遗传算法

✅作者简介&#xff1a;人工智能专业本科在读&#xff0c;喜欢计算机与编程&#xff0c;写博客记录自己的学习历程。 &#x1f34e;个人主页&#xff1a;小嗷犬的个人主页 &#x1f34a;个人网站&#xff1a;小嗷犬的技术小站 &#x1f96d;个人信条&#xff1a;为天地立心&…...

探讨 Java 中 valueOf 和 parseInt 的区别

前言 在编程中&#xff0c;遇到类型转换&#xff0c;好像会经常用到 parseInt 和 valueOf&#xff0c;当然这里只拿 Integer 类型进行陈述&#xff0c;其他类型也是雷同的&#xff1b; 想必有读者也跟我一样&#xff0c;经常交叉使用这两个方法&#xff0c;但却不知道这两者到…...

JSON学习笔记

♥课程链接&#xff1a;【狂神说Java】一小时掌握JSON_哔哩哔哩_bilibili配套的当然还要学习ajax不管是前端后端&#xff0c;感觉这部分内容是必须的&#xff0c;不然真的做项目的时候云里雾里。总体json的内容不多&#xff0c;具体就&#xff1a;1. 列表、对象等语法格式2. js…...

家政服务小程序实战教程07-轮播图组件

小程序中首页一般显示轮播图的功能&#xff0c;点击轮播图会跳转到具体的一篇文章或者是产品&#xff0c;本篇我们就介绍一下轮播图功能的开发 01 设计数据源 我们轮播图组件需要两个字段&#xff0c;一个是展示的图片&#xff0c;一个是跳转页面传入的参数。打开数据源&…...

MySQL之索引创建、删除、唯一索引、普通索引、及命名规则、注意事项

一、MySQL 索引 定义 索引是一个数据结构&#xff0c;用于加速数据库表中数据的查询。索引存储了一些数据表中的列值&#xff0c;以及这些列值在数据表中的位置&#xff0c;这样就可以通过索引来快速查找到数据表中的某一行数据。 MySQL 支持多种索引类型&#xff0c;包括普通…...

【C++设计模式】学习笔记(3):策略模式 Strategy

目录 简介动机(Motivation)模式定义结构(Structure)要点总结笔记结语简介 Hello! 非常感谢您阅读海轰的文章,倘若文中有错误的地方,欢迎您指出~ ଘ(੭ˊᵕˋ)੭ 昵称:海轰 标签:程序猿|C++选手|学生 简介:因C语言结识编程,随后转入计算机专业,获得过国家奖学金…...

Java——聊聊JUC中的ThreadLocal

文章目录&#xff1a; 1.什么是ThreadLocal&#xff1f; 1.1 api介绍 1.2 最简单的案例认识ThreadLocal 1.3 线程池结合ThreadLocal案例 2.Thread &ThreadLocal & ThreadLocalMap 3.ThreadLocal内存泄漏问题 3.1 四大引用之强引用 3.2 四大引用之软引用 3.3 四…...

软件工程(4)--螺旋模型

前言 这是基于我所学习的软件工程课程总结的第四篇文章。 在软件开发过程中必须及时识别和分析风险&#xff0c;并且采取适当措施以消除或减少风险的危害。构建原型是一种能使某些类型的风险降至最低的方法。为了降低交付给用户的产品不能满足用户需要的风险&#xff0c;一种行…...

图解LeetCode——剑指 Offer 50. 第一个只出现一次的字符

一、题目 在字符串 s 中找出第一个只出现一次的字符。如果没有&#xff0c;返回一个单空格。 s 只包含小写字母。 二、示例 2.1> 示例 1: 【输入】s "abaccdeff" 【输出】b 2.2> 示例 2: 【输入】s "" 【输出】 限制&#xff1a; 0 < s 的…...

《HTML 5与CSS 3核心技法》读书笔记

目录前言第1章 写在前面第2章 HTML 语法基础第3章 布局类元素 &#xff0c;房子的楼板、柱子和大梁第4章 功能类元素&#xff0c;房子的门、窗、水管和电气第5章 CSS基础第6章 选择器&#xff0c;确定样式的作用范围选择器类型选择器的组合使用第7章 权重&#xff0c;样式发送冲…...

【沐风老师】3DMAX几何投影插件Geometry Projection使用详解

【几何投影插件】 描述 3DMAX几何投影插件Geometry Projection&#xff0c;将一个或多个对象或它的顶点选择沿全局或局部 x、y 或 z 轴投影到另一个对象上。 适用版本 3dMax2013或更高版本 安装设置 插件的安装非常简单&#xff0c;解压后把插件脚本 “geometry_projectio…...

浏览器访问 AWS ECS 上部署的 Docker 容器(监听 80 端口)

✅ 一、ECS 服务配置 Dockerfile 确保监听 80 端口 EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]或 EXPOSE 80 CMD ["python3", "-m", "http.server", "80"]任务定义&#xff08;Task Definition&…...

SpringBoot+uniapp 的 Champion 俱乐部微信小程序设计与实现,论文初版实现

摘要 本论文旨在设计并实现基于 SpringBoot 和 uniapp 的 Champion 俱乐部微信小程序&#xff0c;以满足俱乐部线上活动推广、会员管理、社交互动等需求。通过 SpringBoot 搭建后端服务&#xff0c;提供稳定高效的数据处理与业务逻辑支持&#xff1b;利用 uniapp 实现跨平台前…...

LLM基础1_语言模型如何处理文本

基于GitHub项目&#xff1a;https://github.com/datawhalechina/llms-from-scratch-cn 工具介绍 tiktoken&#xff1a;OpenAI开发的专业"分词器" torch&#xff1a;Facebook开发的强力计算引擎&#xff0c;相当于超级计算器 理解词嵌入&#xff1a;给词语画"…...

什么是Ansible Jinja2

理解 Ansible Jinja2 模板 Ansible 是一款功能强大的开源自动化工具&#xff0c;可让您无缝地管理和配置系统。Ansible 的一大亮点是它使用 Jinja2 模板&#xff0c;允许您根据变量数据动态生成文件、配置设置和脚本。本文将向您介绍 Ansible 中的 Jinja2 模板&#xff0c;并通…...

鸿蒙DevEco Studio HarmonyOS 5跑酷小游戏实现指南

1. 项目概述 本跑酷小游戏基于鸿蒙HarmonyOS 5开发&#xff0c;使用DevEco Studio作为开发工具&#xff0c;采用Java语言实现&#xff0c;包含角色控制、障碍物生成和分数计算系统。 2. 项目结构 /src/main/java/com/example/runner/├── MainAbilitySlice.java // 主界…...

Pinocchio 库详解及其在足式机器人上的应用

Pinocchio 库详解及其在足式机器人上的应用 Pinocchio (Pinocchio is not only a nose) 是一个开源的 C 库&#xff0c;专门用于快速计算机器人模型的正向运动学、逆向运动学、雅可比矩阵、动力学和动力学导数。它主要关注效率和准确性&#xff0c;并提供了一个通用的框架&…...

HashMap中的put方法执行流程(流程图)

1 put操作整体流程 HashMap 的 put 操作是其最核心的功能之一。在 JDK 1.8 及以后版本中&#xff0c;其主要逻辑封装在 putVal 这个内部方法中。整个过程大致如下&#xff1a; 初始判断与哈希计算&#xff1a; 首先&#xff0c;putVal 方法会检查当前的 table&#xff08;也就…...

Java毕业设计:WML信息查询与后端信息发布系统开发

JAVAWML信息查询与后端信息发布系统实现 一、系统概述 本系统基于Java和WML(无线标记语言)技术开发&#xff0c;实现了移动设备上的信息查询与后端信息发布功能。系统采用B/S架构&#xff0c;服务器端使用Java Servlet处理请求&#xff0c;数据库采用MySQL存储信息&#xff0…...

【Android】Android 开发 ADB 常用指令

查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...

Python Einops库:深度学习中的张量操作革命

Einops&#xff08;爱因斯坦操作库&#xff09;就像给张量操作戴上了一副"语义眼镜"——让你用人类能理解的方式告诉计算机如何操作多维数组。这个基于爱因斯坦求和约定的库&#xff0c;用类似自然语言的表达式替代了晦涩的API调用&#xff0c;彻底改变了深度学习工程…...