java异常的声明、捕获、抛出、处理(throws、throw、try-catch详解) 桃扇骨 2024-03-22 15:52 25阅读 0赞 ### **异常的处理** ### 1. `LBYL`: Look Before You Leap. 在操作之前就做充分的检查. 即:事前防御型 boolean ret = false; ret = 登陆游戏(); if (!ret) { 处理登陆游戏错误; return; } ret = 开始匹配(); if (!ret) { 处理匹配错误; return; } ret = 游戏确认(); if (!ret) { 处理游戏确认错误; return; } ret = 选择英雄(); if (!ret) { 处理选择英雄错误; return; } ret = 载入游戏画面(); if (!ret) { 处理载入游戏错误; return; } 缺陷:正常流程和错误处理流程代码混在一起, 代码整体显的比较混乱。 1. `EAFP`: It’s Easier to Ask Forgiveness than Permission. “事后获取原谅比事前获取许可更容易”. 也就是先操作, 遇到问题再处理. 即:事后认错型 try { 登陆游戏(); 开始匹配(); 游戏确认(); 选择英雄(); 载入游戏画面(); ... } catch (登陆游戏异常) { 处理登陆游戏异常; } catch (开始匹配异常) { 处理开始匹配异常; } catch (游戏确认异常) { 处理游戏确认异常; } catch (选择英雄异常) { 处理选择英雄异常; } catch (载入游戏画面异常) { 处理载入游戏画面异常; } ...... 优势:正常流程和错误流程是分离开的, 程序员更关注正常流程,代码更清晰,容易理解代码,异常处理的核心思想就是 EAFP。 在Java中,异常处理主要的5个关键字:throw、try、catch、final、throws。 ### **异常的抛出** ### 在Java中,可以借助throw关键字,抛出一个指定的异常对象,将错误信息告知给调用者。具体语法如下:`throw new XXXException("异常产生的原因");` 比如我们自己定义一个空指针异常 public class Test { public static void exception(int[] arr){ if(arr == null){ throw new NullPointerException("指向数组的引用为空"); } } public static void main(String[] args) { exception(null); } } ![在这里插入图片描述][eb1aaf5f36944106889034ff2b586f8f.png]刚刚的异常是系统自带的空指针异常 我们也可以自己自定义一个异常然后抛出 public class Test { public static int getElement(int[] array, int index) { if (null == array) { throw new NullPointerException("传递的数组为null"); } if (index < 0 || index >= array.length) { throw new ArrayIndexOutOfBoundsException("传递的数组下标越界"); } return array[index]; } public static void main(String[] args) { int[] array = { 1, 2, 3}; getElement(array, 3); } } ![在这里插入图片描述][03a209bdbac147a5a6b8e236ebee468e.png] 注意: 1. throw必须写在方法体内部 2. 抛出的对象必须是Exception 或者 Exception 的子类对象 3. 如果抛出的是 RunTimeException(运行时异常) 或者 RunTimeException 的子类,则可以不用处理,直接交给JVM来处理(注意这里并不是说运行时异常可以编运行,依旧是不能运行的)。 4. 如果抛出的是编译时异常,用户必须处理,否则无法通过编译 5. 异常一旦抛出,其后的代码就不会执行 ### **异常的捕获** ### 异常的捕获,也就是异常的具体处理方式,主要有两种:异常声明throws 以及 try-catch捕获处理。 #### 1.异常声明`throws` #### 处在方法声明时参数列表之后,当方法中抛出编译时异常,用户不想处理该异常,此时就可以借助throws将异常抛给方法的调用者来处理。即当前方法不处理异常,提醒方法的调用者处理异常。 语法格式: 修饰符 返回值类型 方法名(参数列表) throws 异常类型1,异常类型2...{ } 比如 public class Test { public static int getElement(int[] array, int index)throws NullPointerException,ArrayIndexOutOfBoundsException { if (null == array) { throw new NullPointerException("传递的数组为null"); } if (index < 0 || index >= array.length) { throw new ArrayIndexOutOfBoundsException("传递的数组下标越界"); } return array[index]; } public static void main(String[] args)throws NullPointerException,ArrayIndexOutOfBoundsException { int[] array = { 1, 2, 3}; getElement(array, 3); System.out.println("这行代码并没有被执行"); } } 注意,这个例子中异常我们是处理了的,但是没有解决,程序执行到这里发现异常,抛出异常之后,会在这里直接中断,后续代码是不会执行的,所有本例子里面没有打印“这行代码并没有被执行”这行字。 在本例子中,这个越剧异常`getElement`方法没有解决,抛出异常,交给调用者`main`方法,`main`方法也没有解决,依旧抛出最后交给了JVM当我们没有解决这个异常的时候,这个异常就交给JVM,一旦交给JVM,程序就崩溃了。 #### **try-catch捕获并处理** #### throws对异常并没有真正处理,而是将异常报告给抛出异常方法的调用者,由调用者处理。如果真正要对异常进行 处理,就需要try-catch。 语法格式: try{ // 将可能出现异常的代码放在这里 }catch(要捕获的异常类型 e){ // 如果try中的代码抛出异常了,此处catch捕获时异常类型与try中抛出的异常类型一致时,或者是try中抛出异常的基类 时,就会被捕获到 // 对异常就可以正常处理,处理完成后,跳出try-catch结构,继续执行后序代码 }[catch(异常类型 e){ // 对异常进行处理 }finally{ // 此处代码一定会被执行到 }] // 后序代码 // 当异常被捕获到时,异常就被处理了,这里的后序代码一定会执行 // 如果捕获了,由于捕获时类型不对,那就没有捕获到,这里的代码就不会被执行 注意: 1. []中表示可选项,可以添加,也可以不用添加 2. try中的代码可能会抛出异常,也可能不会 举个例子说明 public class Test { public static int getElement(int[] array, int index) { return array[index]; } public static void main(String[] args) { int[] array = { 1, 2, 3}; try { getElement(array, 3);//try中写可能抛出异常的代码 } catch (NullPointerException e) { //捕捉异常 //catch里面捕获异常之后需要的处理 System.out.println("捕获到 NullPointerException 异常,进行处理异常逻辑"); e.printStackTrace();//打印栈上的信息,也就是异常出现的一些信息,就会出现红字。 } catch (ArrayIndexOutOfBoundsException e) { System.out.println("捕获到ArrayIndexOutOfBoundsException 异常,进行处理异常逻辑"); e.printStackTrace(); } System.out.println("异常后面的代码正常执行");//之前的throws抛出异常后,后面的代码是直接中断不执行的,所以并不能称之为处理异常,而这里后面的代码继续执行,说明这个异常被处理了。 } } ![在这里插入图片描述][8692ecad770e4250a5eed4d65abe06d7.png] 异常的种类有很多, 我们要根据不同的业务场景来决定. 对于比较严重的问题(例如和算钱相关的场景), 应该让程序直接崩溃, 防止造成更严重的后果 对于不太严重的问题(大多数场景), 可以记录错误日志, 并通过监控报警程序及时通知程序猿 对于可能会恢复的问题(和网络相关的场景), 可以尝试进行重试. 在我们当前的代码中采取的是经过简化的第二种方式. 我们记录的错误日志是出现异常的方法调用信息, 能很快速的让我们找到出现异常的位置. 以后在实际工作中我们会采取更完备的方式来记录异常信息. 注意: 1. 抛出异常位置之后的代码将不会被执行(注意这里面的之后的代码指异常之后的所有代码,不单单指try块内的代码) 2. 如果抛出异常类型与catch时异常类型不匹配,即异常不会被成功捕获,也就不会被处理,继续往外抛,直到JVM收到后中断程序----异常是按照类型来捕获的 3. try中可能会抛出多个不同的异常对象,则必须用多个catch来捕获----即多种异常,多次捕获 public class Test { public static int getElement(int[] array, int index) { return array[index]; System.out.println("这段代码也不会执行"); } public static void main(String[] args) { int[] array = { 1, 2, 3}; try { System.out.println("这行代码会执行"); getElement(array, 3); System.out.println("这行代码不会执行");//try里面可能发生异常代码之后的代码不执行 } catch (NullPointerException e) { //正在的异常并没有被捕获,此时会直接抛出异常,异常最终交给JVM,程序中断,后续代码也不会执行 System.out.println("捕获到 NullPointerException 异常,进行处理异常逻辑"); e.printStackTrace(); } /*catch (ArrayIndexOutOfBoundsException e) { System.out.println("捕获到ArrayIndexOutOfBoundsException 异常,进行处理异常逻辑"); e.printStackTrace(); }*/ System.out.println("这行代码也不会执行"); } } 但是注意发生异常的中断,异常的中断并不是以一行代码为单位的,换句话说并不是说一行代码里面有两个异常就会抛出两个异常,而是只会抛出一个异常,因为另外一个还没有执行就被中断了,所以“try中可能会抛出多个不同的异常对象”并不是指会同时抛出,而是在不同条件下(比如if else下)分别抛出。如下面这个例子 public class Test { public static int getElement(int[] array, int index) { int[] arr = { 1,2,3}; //一行代码里有越界有空指针 arr[4] = array[index];//arr[4]也不会执行,前面就发生异常中断了 System.out.println("这行代码不执行");//不会执行 return arr[4];//不会执行 } public static void main(String[] args) { int[] array = { 1, 2, 3}; try { int arr = getElement(null, 3); } catch (ArrayIndexOutOfBoundsException e) { System.out.println("捕获到ArrayIndexOutOfBoundsException 异常,进行处理异常逻辑"); e.printStackTrace(); } catch (NullPointerException e) { //率先被捕获 System.out.println("捕获到 NullPointerException 异常,进行处理异常逻辑"); e.printStackTrace(); } System.out.println("这行代码会执行"); } } ![在这里插入图片描述][e45cad0b073f40208ae631aed288cecd.png] 1. 对于首查异常而言,如果try中没有抛出异常,那么catch中检测不到就在编译时就会报错(显示出红色波浪下划线) ![在这里插入图片描述][0fa0f6ac2e7a44a18b0fff373b3601fc.png] 1. 如果多个异常的处理方式是完全相同, 也可以写成这样: catch (ArrayIndexOutOfBoundsException | NullPointerException e) { ... } 1. 如果异常之间具有父子关系,一定是子类异常在前,父类异常在后 ,否则语法错误。catch 进行类型匹配的时候, 不光会匹配相同类型的异常对象, 也会捕捉目标异常类型的子类对象.如刚才的代码, NullPointerException ArrayIndexOutOfBoundsException 都是 Exception 的子类,因此都能被捕获到. ![在这里插入图片描述][361b1a8303c84dc89082b620b36ebd58.png]如下代码就不会报错(没有下划线 ) 当try中存在多个异常时,谁先抛出异常就先捕获并处理哪一个异常,catch不影响异常捕获的顺序,同时,子类异常抛出后,父类异常不会再抛出。 public class Test { public static int getElement(int[] array, int index) { int ret = array[index]; System.out.println("这行代码不执行"); return ret; } public static void main(String[] args) { int[] array = { 1, 2, 3}; try { int arr = getElement(null, 3); } catch (ArrayIndexOutOfBoundsException e) { System.out.println("捕获到ArrayIndexOutOfBoundsException 异常,进行处理异常逻辑"); e.printStackTrace(); } catch (NullPointerException e) { //率先被捕获 System.out.println("捕获到 NullPointerException 异常,进行处理异常逻辑"); e.printStackTrace(); }catch (Exception e) { //父类异常不会被抛出 System.out.println("捕获全部异常"); e.printStackTrace(); } System.out.println("这行代码会执行"); } } ![在这里插入图片描述][b3076a3d0c8143c6a7d8d99649fc3d9d.png] ### finally ### 在写程序时,有些特定的代码,不论程序是否发生异常,都需要执行,比如程序中打开的资源:网络连接、数据库 连接、IO流等,在程序正常或者异常退出时,必须要对资源进进行回收。另外,因为异常会引发程序的跳转,可能 导致有些语句执行不到,finally就是用来解决这个问题的。 不管try是否会抛出异常,finally中代码一定会被执行。 语法格式: try{ // 可能会发生异常的代码 }catch(异常类型 e){ // 对捕获到的异常进行处理 }finally{ // 此处的语句无论是否发生异常,都会被执行到 } // 如果没有抛出异常,或者异常被捕获处理了,这里的代码也会执行 如下面这个例子 import java.util.Scanner; public class Test { public static int getElement(int[] array, int index) { return array[index]; } public static void main(String[] args) { Scanner scanner = new Scanner(System.in);//scanner本身就是一个资源,使用完需要调用close方法关闭 int[] array = { 1, 2, 3}; try { int a = scanner.nextInt(); array[2] = a; int arr = getElement(null, 3); } catch (NullPointerException e) { System.out.println("捕获到 NullPointerException 异常,进行处理异常逻辑"); e.printStackTrace(); } finally{ scanner.close(); System.out.println("finally执行了"); } System.out.println("这行代码会执行"); } } ![在这里插入图片描述][06f8ac1e509e46d2aceaf3e3ddb6c185.png]来看下面一道面试题 public class Test { public static int func() { try{ int a = 10; return a; }catch(NullPointerException e){ return 1; }finally { return 9; } } public static void main(String[] args) { System.out.println(func()); } } try里面有return,finally里面有return,这样到返回哪个值呢? ![在这里插入图片描述][0aa21bf89dfd44d8a043ab2905f0c7eb.png] finally 执行的时机是在方法返回之前(try 或者 catch 中如果有 return 会在这个 return 之前执行 finally). 但是如果 finally 中也存在 return 语句, 那么就会执行 finally 中的 return, 从而不会执行到 try 中原有的 return. 一般我们不建议在 finally 中写 return (被编译器当做一个警告) ### **异常处理流程** ### * 程序先执行 try 中的代码 如果 try 中的代码出现异常, 就会结束 try 中的代码, 看和 catch 中的异常类型是否匹配. * 如果找到匹配的异常类型, 就会执行 catch 中的代码 如果没有找到匹配的异常类型, 就会将异常向上传递到上层调用者. * 无论是否找到匹配的异常类型, finally 中的代码都会被执行到(在该方法结束之前执行). * 如果上层调用者也没有处理的了异常,就继续向上传递. 一直到 main 方法也没有合适的代码处理异常, 就会交给 JVM 来进行处理, 此时程序就会异常终止. public static void main(String[] args) { //0 try { //1 func();//2 } catch (ArrayIndexOutOfBoundsException e) { //3 e.printStackTrace();//4 } System.out.println("after try catch");//5 } public static void func() { //6 int[] arr = { 1, 2, 3};//7 System.out.println(arr[100]);//8 } } 执行顺序是012678345 ### 自定义异常类 ### 此时我们在处理用户名密码错误的时候可能就需要抛出两种异常. 我们可以基于已有的异常类进行扩展(继承), 创建 和我们业务相关的异常类. 具体方式: 1. 自定义异常类,然后继承自Exception 或者 RunTimeException 2. 实现一个带有String类型参数的构造方法,参数含义:出现异常的原因 如下面这个例子 我们定义一个`NameExcepetion`类继承`Exception`并且调用参数的构造方法用于提示造成异常的原因 public class NameExcepetion extends Exception{ //自定义的错误必须继承Exceptiion或者RuntimeException, // 前者是所以异常的父类,可以用以表示编译时异常,后者可以专门用以表示允许时异常 public NameExcepetion(String message) { super(message); } } 再定义一个`PasswordException`类继承`Exception`,并且调用参数的构造方法用于提示造成异常的原因 public class PasswordException extends Exception{ public PasswordException(String message) { super(message); } } 然后定义一个登录类用于登录实现 import java.util.Scanner; public class Login { private static String name = "baixian"; private static String passworld = "123456"; public static void login(String inputName, String inputPassword) throws NameExcepetion, PasswordException { //用throws将异常抛出,交给调用方法main处理 if (!name.equals(inputName)) { throw new NameExcepetion("用户名输入错误");//调用带参数的构造方法实例化一个NameExcepetion,也就是提出一个ameExcepetion异常 } if (!passworld.equals(inputName)) { throw new PasswordException("密码输入错误"); } } public static void main(String[] args) { Scanner scanner = new Scanner(System.in); try { System.out.println("请输入用户名:"); String inputName = scanner.nextLine(); System.out.println("请输入密码:"); String inputPassword = scanner.next(); login(inputName, inputPassword); } catch (NameExcepetion nameExcepetion) { //捕获NameExcepetion异常 nameExcepetion.printStackTrace();//打印异常调用栈 System.out.println("处理用户名异常");//main函数处理异常 } catch (PasswordException passwordException) { passwordException.printStackTrace(); System.out.println("处理密码异常"); } finally { scanner.close(); } } } ![在这里插入图片描述][6acaf27d5cc948f2af7d15ae7889c2e4.png]![在这里插入图片描述][2be69f4ae73e439d8d620ea0ceef2194.png] [eb1aaf5f36944106889034ff2b586f8f.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/03/18/6c3132515105424c906fb03fdf8262e4.png [03a209bdbac147a5a6b8e236ebee468e.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/03/18/687bd646390b433aa95649b1441dd85d.png [8692ecad770e4250a5eed4d65abe06d7.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/03/18/12aafe0dc54c4090b8052716bd2b3ada.png [e45cad0b073f40208ae631aed288cecd.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/03/18/bf9eab8a2e6443c5b76064584ffd2061.png [0fa0f6ac2e7a44a18b0fff373b3601fc.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/03/18/8a71717667b24749bb120ce920e7acc7.png [361b1a8303c84dc89082b620b36ebd58.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/03/18/8646ccfe941f446daa42daf4794b2a8a.png [b3076a3d0c8143c6a7d8d99649fc3d9d.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/03/18/611e68fa9de2410cae92af6d1c2300f5.png [06f8ac1e509e46d2aceaf3e3ddb6c185.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/03/18/9e74368db5114632b0e84689dbaa5b6c.png [0aa21bf89dfd44d8a043ab2905f0c7eb.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/03/18/d1367642bfc84c6ea8c30329189e0f81.png [6acaf27d5cc948f2af7d15ae7889c2e4.png]: https://img-blog.csdnimg.cn/6acaf27d5cc948f2af7d15ae7889c2e4.png [2be69f4ae73e439d8d620ea0ceef2194.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/03/18/7bac376c41c34b20935e7cee16410939.png
相关 Java异常处理机制详解:捕获和重新抛出 在Java编程中,异常处理是一个重要的部分。它帮助开发者在程序运行过程中遇到问题时,能够优雅地中断程序并进行适当的处理。 1. **捕获异常**: - `try`块:这 悠悠/ 2024年09月17日 23:06/ 0 赞/ 14 阅读
相关 Java中的异常处理机制:捕获、声明与抛出 在Java中,异常处理机制是通过try-catch-finally语句来实现的。 1. 捕获(Catching):在try块中编写可能会出现异常的代码。如果这段代码引发了匹配 古城微笑少年丶/ 2024年09月17日 19:15/ 0 赞/ 15 阅读
相关 详解Java异常处理:捕获、抛出与处理 在Java编程中,异常处理是一种机制,用于在程序执行过程中遇到错误或异常情况时进行处理。以下是Java中异常处理的几个重要方面: 1. 捕获异常(try-catch) - 比眉伴天荒/ 2024年09月15日 08:33/ 0 赞/ 16 阅读
相关 Java异常处理:捕获、声明与抛出异常 在Java编程中,异常处理是一种机制,用于在程序运行过程中遇到错误或意外情况时,能优雅地进行处理,而不是让程序直接崩溃。 1. **捕获异常**: 在方法的开始,使用` 妖狐艹你老母/ 2024年09月13日 14:36/ 0 赞/ 89 阅读
相关 Java中的异常处理:捕获、抛出和处理 在Java编程中,异常处理是一种机制,用于处理程序运行时可能出现的错误情况。主要包括以下三个步骤: 1. **捕获(Throwing)**: 当一个方法或者代码块无法按 桃扇骨/ 2024年09月13日 03:18/ 0 赞/ 22 阅读
相关 Java异常处理详解:捕获和声明性抛出异常案例分析 在Java编程中,异常处理是一个重要的部分。它允许我们在程序运行过程中遇到错误时,能有控制地进行处理,而不是让程序直接崩溃。 以下是Java异常处理的几个主要方面: 1. 朴灿烈づ我的快乐病毒、/ 2024年09月12日 02:36/ 0 赞/ 20 阅读
相关 Java异常处理:捕获和声明性抛出示例 在Java中,异常处理用于捕获和管理程序运行时可能出现的问题。有两种主要的异常处理方式:try-catch块以及声明性抛出异常。 1. 捕获异常的例子: ```java pu 悠悠/ 2024年09月10日 01:54/ 0 赞/ 24 阅读
相关 java异常的声明、捕获、抛出、处理(throws、throw、try-catch详解) 异常的处理 1. `LBYL`: Look Before You Leap. 在操作之前就做充分的检查. 即:事前防御型 boolean ret = fals 桃扇骨/ 2024年03月22日 15:52/ 0 赞/ 26 阅读
还没有评论,来说两句吧...