异常和测试岗位的理念紧密相关,所以关于异常应该好好说一说,实际上异常机制是判断一门编程语言是否成熟的标准,主流的编程语言中都提供了健全的异常处理机制,请看清楚这里说的是处理机制,它可以使程序中的异常处理代码和正常业务代码分离,保证程序代码更加优雅的展现,而其本质是大大提高了程序的健壮性,反映到客户层面更多的感知就是稳定性
对于计算机语言来说情况相当复杂,没人能保证得了程序永远不会出错,就算程序没有错误,谁又能保证客户是按你的预期来输入的?就算客户是非常聪明而且配合的,谁能保证运行该程序会永久稳定?谁能保证运行程序的物理资源永久配合?谁能保证网络条件永远合适?。。。太多无法保证的东西,人是否要覆盖住?
程序员喜欢的永远是解决问题以及开发带来的创造快感,都不愿意当个堵漏洞的工人,而这才是漏洞的真正概念,而这些也是一个程序员是否成熟的标准
以Java为例,它的异常处理主要依赖于try、catch、finally、throw和throws五个关键字:
Java的异常分为两种,Checked异常和Runtime异常,Checked异常和Runtime异常,Checked都是可以在编译阶段被处理的异常,因此它强制程序处理所有的Checked异常Runtime异常则无需处理,程序员处理异常是一个繁琐的事情,因此程序的健壮性在人而非try代码块
try
{// 业务实现代码...
}
catch (Exception e)
{// 处理异常的代码块
}
如果程序可以顺利完成,那就一切正常,如果try块里的业务逻辑代码出现异常,系统会自动生成一个异常对象,该异常对象会被提交给Java runtime环境,而这个过程就被称为抛出异常,当抛出异常发生的时候,Java会寻找能够处理该异常对象的catch块,如果找到合适的catch块,则把该异常对象交给该catch块处理,这个过程被称为捕获异常,如果找不到捕获异常的catch块,则Java runtime环境终止,Java程序也会退出
无论程序是否出现在try代码块中,只要执行该代码块出现了异常,系统总会自动生成异常对象,如果不处理则程序直接退出
import java.io.*;public class Gobang
{// 定义一个二维数组来充当棋盘private String[][] board;// 定义棋盘的大小private static int BOARD_SIZE = 15;public void initBoard(){// 初始化棋盘数组board = new String[BOARD_SIZE][BOARD_SIZE];// 把每个元素赋为"╋",用于在控制台画出棋盘for (var i = 0; i < BOARD_SIZE; i++){for (var j = 0; j < BOARD_SIZE; j++){board[i][j] = "╋";}}}// 在控制台输出棋盘的方法public void printBoard(){// 打印每个数组元素for (var i = 0; i < BOARD_SIZE; i++){for (var j = 0; j < BOARD_SIZE; j++){// 打印数组元素后不换行System.out.print(board[i][j]);}// 每打印完一行数组元素后输出一个换行符System.out.print("n");}}public static void main(String[] args) throws Exception{var gb = new Gobang();gb.initBoard();gb.printBoard();// 这是用于获取键盘输入的方法var br = new BufferedReader(new InputStreamReader(System.in));String inputStr = null;// br.readLine():每当在键盘上输入一行内容按回车,// 用户刚刚输入的内容将被br读取到。while ((inputStr = br.readLine()) != null){try{// 将用户输入的字符串以逗号作为分隔符,分解成2个字符串String[] posStrArr = inputStr.split(",");// 将2个字符串转换成用户下棋的坐标var xPos = Integer.parseInt(posStrArr[0]);var yPos = Integer.parseInt(posStrArr[1]);// 把对应的数组元素赋为"●"。if (!gb.board[xPos - 1][yPos - 1].equals("╋")){System.out.println("您输入的坐标点已有棋子了,"+ "请重新输入");continue;}gb.board[xPos - 1][yPos - 1] = "●";}catch (Exception e){System.out.println("您输入的坐标不合法,请重新输入,"+ "下棋坐标应以x,y的格式");continue;}gb.printBoard();System.out.println("请输入您下棋的坐标,应以x,y的格式:");}}
}
程序中catch代码块处理了异常后,使用continue忽略本次循环剩下的代码,开始执行下一次循环,这就保证了足够的兼容性,用户可以随意输入,程序不会因为用户输入不合法而突然退出
当Java runtime环境接收到异常对象的时候,会依次判断该异常对象是否是catch块里的异常类或者其子类的实例,如果是Java runtime将调用该catch块来处理该异常,否则再次拿该异常对象与下一个catch块里的异常类进行比较,如下图所示
如图所示,Java把所有的非正常情况非为两种即:异常(Exception)和错误(Error),他们都继承Throwable父类
Error:一般是指与虚拟机相关的问题,如系统崩溃、虚拟机错误、动态链接失败等,这种错误无法恢复或不可能捕获,将导致程序中断,一般情况下应用程序无法处理这些错误,因此应用程序不应该试图使用catch块来捕获Error对象,在定义该方法时,无须在其throws子句中声明该方法可能抛出Error及其任何子类
public class DivTest
{public static void main(String[] args){try{var a = Integer.parseInt(args[0]);var b = Integer.parseInt(args[1]);var c = a / b;System.out.println("您输入的两个数相除的结果是:" + c );}catch (IndexOutOfBoundsException ie){System.out.println("数组越界:运行程序时输入的参数个数不够");}catch (NumberFormatException ne){System.out.println("数字格式异常:程序只能接受整数参数");}catch (ArithmeticException ae){System.out.println("算术异常");}catch (Exception e){System.out.println("未知异常");}}
}
上面程序针对IndexOutOfBoundsException、NumberFormatException、ArithmeticException类型的异常,提供了专门的异常处理逻辑。
Java运行时的异常处理逻辑可能有如下几种情形:
import java.util.*;public class NullTest
{public static void main(String[] args){Date d = null;try{System.out.println(d.after(new Date()));}catch (NullPointerException ne){System.out.println("空指针异常");}catch (Exception e){System.out.println("未知异常");}}
}
当试图调用一个null对象的实例方法或实例变量的时,就会引发NullPointerException异常,Java运行时会调用NullPointerException对应的catch块来处理该异常,如果遇到其他异常则调用最后的catch块来处理异常
注意:Exception类的catch块必须方法最后,因为所有的异常对象都是Exception或其子类的实例,如果Exception类的catch块在前边,那么它后边的catch块将永远不会获得执行
实际上进行异常捕获的时候不仅应该把Exception类对应的catch块放在最后,而且所有父类异常的catch块都应该排在子类异常catch块后面,也就是先处理小异常再处理大异常,否则将出现编译错误
try
{
}
catch (RuntimeException e)
{System.out.println("运行时异常");
}
catch (NullPointerException ne)
{System.out.println("空指针异常");
}
因为RuntimeException已经包括了NullPointerException异常,所以catch (NullPointerException ne)
处的catch块永远不会获得执行的机会
在Java7之后,一个catch块可以捕获多种类型的异常,只需要在多种异常类型之间使用竖线(|)隔开,并且异常变量有隐式的final修饰,因此程序不能对异常变量重新赋值
public class MultiExceptionTest
{public static void main(String[] args){try{var a = Integer.parseInt(args[0]);var b = Integer.parseInt(args[1]);var c = a / b;System.out.println("您输入的两个数相除的结果是:" + c );}catch (IndexOutOfBoundsException|NumberFormatException|ArithmeticException ie){System.out.println("程序发生了数组越界、数字格式异常、算术异常之一");// 捕捉多异常时,异常变量默认有final修饰,// 所以下面代码有错:ie = new ArithmeticException("test"); }catch (Exception e){System.out.println("未知异常");// 捕捉一个类型的异常时,异常变量没有final修饰// 所以下面代码完全正确。e = new RuntimeException("test"); }}
}
如果程序需要在catch块中访问异常对象的相关信息,可以通过访问catch块的后异常形参来获得,当Java运行时决定调用某个catch块来处理该异常对象时,会将异常对象赋给catch块后的异常参数,程序即可通过该参数来获得异常的相关信息
所有的异常对象都包含了如下几个常用方法:
import java.io.*;public class AccessExceptionMsg
{public static void main(String[] args){try{var fis = new FileInputStream(");}catch (IOException ioe){System.out.Message());ioe.printStackTrace();}}
}
Java的垃圾回收机制不会回收任何物理资源,垃圾回收机制只能回收堆内存中对象所占用的内存,而程序在try代码块里打开的物理资源如数据库连接、网络资源、磁盘文件等,这些资源都必须显示的回收,而这些显示的回收应该在finally块中做,因为无论try和catch执行的什么,finally代码块总会被执行
try
{// 业务代码...
}
catch (SubException e)
{// 异常处理代码块
}
catch (SubException e)
{// 异常处理代码块
}
...
finally
{// 资源回收代码块...
}
import java.io.*;public class FinallyTest
{public static void main(String[] args){FileInputStream fis = null;try{fis = new FileInputStream(");}catch (IOException ioe){System.out.Message());// return语句强制方法返回return; // 使用exit来退出虚拟机it(1); }finally{// 关闭磁盘文件,回收资源if (fis != null){try{fis.close();}catch (IOException ioe){ioe.printStackTrace();}}System.out.println("执行finally块里的资源回收!");}}
}
public class FinallyFlowTest
{public static void main(String[] args)throws Exception{boolean a = test();System.out.println(a);}public static boolean test(){try{// 因为finally块中包含了return语句// 所以下面的return语句失去作用return true;}finally{return false;}}
}
当系统遇到try和catch里的return或者throw语句的时候,都会立即终止执行当前方法,当方法执行并未结束,且return和throw语句也未执行,然后程序去寻找finally代码块,如果没有finally代码块程序立即执行return或throw语句方法终止,如果有finally代码块,系统立即执行finally代码块,当finally代码块执行完毕后系统才会跳回去执行try代码块、catch代码块里的return或throw语句,如果finally里也使用了return或throw等导致方法终止的语句,finally代码块就终止了系统也不会跳回去执行try代码块、catch代码块里的任何代码了
当程序使用finally代码块关闭资源时,显得非常臃肿,Java7之后允许try关键字后跟一对圆括号用于声明、初始化一个或多个资源,然后try语句在该语句结束时自动关闭这些资源,从而降低了代码的臃肿
需要说明的是,要保证try语句可以正常关闭资源,这些资源实现类必须实现AutoCloseable或Closeable接口,实现这两个接口就必须实现close()方法
- Closeable是AutoCloseable的子接口,可以被自动关闭的资源类要么实现AutoCloseable接口,要么实现Closeable接口
- Closeable接口里的close()方法声明抛出了IOException,因此它的实现类在实现close()方法时只能声明抛出IOException或其子类
- AutoCloseable接口里的close()方法声明抛出了Exception,因此它的实现类在实现close()方法时可以声明抛出任何异常
import java.io.*;public class AutoCloseTest
{public static void main(String[] args)throws IOException{try (// 声明、初始化两个可关闭的资源// try语句会自动关闭这两个资源。var br = new BufferedReader(new FileReader("AutoCloseTest.java"));var ps = new PrintStream(newFileOutputStream("))){// 使用两个资源System.out.adLine());ps.println("庄生晓梦迷蝴蝶");}}
}
自动关闭资源的try语句相当于包含了隐式的finally代码块,因此这个代码既没有catch也没有finally,Java7之后几乎所有的资源类,包括文件IO的各种类、JDBC的Connection、Statement等接口进行了改写,改写后资源类都实现了AutoCloseable或Closeable接口
Java9之后不要求在try后的圆括号内声明并创建资源,只需要自动关闭的资源有final修饰或者是有效的final,在Java9之后改写上面的代码
import java.io.*;public class AutoCloseTest2
{public static void main(String[] args)throws IOException{// 有final修饰的资源final var br = new BufferedReader(new FileReader("AutoCloseTest.java"));// 没有显式使用final修饰,但只要不对该变量重新赋值,按该变量就是有效的finalvar ps = new PrintStream(newFileOutputStream("));// 只要将两个资源放在try后的圆括号内即可try (br; ps){// 使用两个资源System.out.adLine());ps.println("庄生晓梦迷蝴蝶");}}
}
本文发布于:2024-02-01 23:54:47,感谢您对本站的认可!
本文链接:https://www.4u4v.net/it/170680842639984.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |