1.异常概述
- Java的异常机制主要依赖于try, catch, finally, throw, throws五个关键字;
- try块中放置可能引发异常的代码;
- catch块中放置处理异常的代码;
- finally块用于回收在try块中打开的物理资源,异常机制保证finally块总被执行;
- throws用于声明该方法可能抛出的异常;
- throw用于抛出一个实际异常;
2.异常处理机制
1.使用try-catch捕获异常
- 异常处理过程:
- 抛出异常:try块中的代码出现异常,系统生成异常对象;
- 捕获异常:Java环境收到异常对象,在catch中寻找对应对象的解决办法;
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的格式:");
}
}
}
- try块一般放在方法体里面,保证方法执行过程中的容错性;
2.异常类的继承体系
- try-catch块的{}不可省略;
- Java的异常类的总接口为Throwable,Error类和Exception类实现了这一接口;
- error一般是虚拟机错误、系统崩溃等问题,不需要程序捕获;
- 不同exception子类对应的try-catch
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("未知异常");
}
}
}
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("未知异常");
}
}
}
- 一般要把小异常放在前面,exception异常放在最后,这是因为程序要先处理对应的小异常,没有办法了才用exception对象处理;
try
{....}
catch
{IOException e1}
catch
{Exception e2}
3.多异常捕获
- 从Java7开始,一个catch块可以捕获多种类型的异常;
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"); // ②
}
}
}
4.访问异常信息
- 访问catch块中的异常信息的方法:
- getMessage():返回该异常的详细描述字符串;
- printStackTrace():将该异常的跟踪栈信息输出到标准错误输出;
- printStackTrace(PrintStream p):将异常跟踪信息输出到指定流;
- getStackTrace():返回该异常的跟踪栈信息;
import java.io.*;
public class AccessExceptionMsg
{
public static void main(String[] args)
{
try
{
var fis = new FileInputStream("a.txt");
}
catch (IOException ioe)
{
System.out.println(ioe.getMessage());
ioe.printStackTrace();
}
}
}
5.使用finally回收资源
- Java的垃圾回收机制不会回收任何物理资源,垃圾回收机制只能回收堆内存中对象所占用的内存;
- finally块的作用就是回收物理资源的,不论try块中的代码是否出错,finally块总会执行;
- catch块和finally块至少出现其一;
import java.io.*;
public class FinallyTest
{
public static void main(String[] args)
{
FileInputStream fis = null;
try
{
fis = new FileInputStream("a.txt");
}
catch (IOException ioe)
{
System.out.println(ioe.getMessage());
// return语句强制方法返回
return; // ①
// 使用exit来退出虚拟机
// System.exit(1); // ②
}
finally
{
// 关闭磁盘文件,回收资源
if (fis != null)
{
try
{
fis.close();
}
catch (IOException ioe)
{
ioe.printStackTrace();
}
}
System.out.println("执行finally块里的资源回收!");
}
}
}
6.异常处理的嵌套
7.Java9增强的自动关闭资源的try块
- java7增强了try语句的功能——它允许在try关键字后紧跟一对圆括号,圆括号内可以初始化一个或多个资源(物理资源),try语句在该语句结束时自动关闭资源;
- 自动关闭资源的try语句相当于包含了隐式的finally块,因此这个语句可以没有catch和finally;
- Java7把所有的资源类都进行了改写,改写后的资源类都实现了AutoCloseable或者Closeable接口;
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修饰,但只要不对该变量重新赋值,按该变量就是有效的final
var ps = new PrintStream(new
FileOutputStream("a.txt"));
// 只要将两个资源放在try后的圆括号内即可
try (br; ps)
{
// 使用两个资源
System.out.println(br.readLine());
ps.println("庄生晓梦迷蝴蝶");
}
}
}
3.Checked异常和Runtime异常
- java的异常分为checked异常和Runtime异常,其中checked异常必须显式指定解决办法;
- checked异常的解决方法:
- 使用try-catch块解决;
- 如果不知道如何解决,就只能声明抛出异常;
- throws将异常抛出给上一级处理,上一级无法处理的话交给JVM处理,一般出现要抛出的异常,程序就会终止,并在JVM中打印出异常信息;
4.使用throw抛出异常
1.抛出异常
- 如果程序中的数据、执行与既定的业务需求不符,这就是一种异常;由于业务抛出的异常,必须由程序员自行决定是否抛出;
- throw抛出的不是异常类,是异常实例
public class ThrowTest
{
public static void main(String[] args)
{
try
{
// 调用声明抛出Checked异常的方法,要么显式捕获该异常
// 要么在main方法中再次声明抛出
throwChecked(-3);
}
catch (Exception e)
{
System.out.println(e.getMessage());
}
// 调用声明抛出Runtime异常的方法既可以显式捕获该异常,
// 也可不理会该异常
throwRuntime(3);
}
public static void throwChecked(int a) throws Exception
{
if (a > 0)
{
// 自行抛出Exception异常
// 该代码必须处于try块里,或处于带throws声明的方法中
throw new Exception("a的值大于0,不符合要求");
}
}
public static void throwRuntime(int a)
{
if (a > 0)
{
// 自行抛出RuntimeException异常,既可以显式捕获该异常
// 也可完全不理会该异常,把该异常交给该方法调用者处理
throw new RuntimeException("a的值大于0,不符合要求");
}
}
}
2.自定义异常类
- 用户自定义异常都应该继承Exception基类;定义异常类要提供两个构造器:1是无参构造器;2是带一个字符串参数的构造器,该字符串将作为该异常对象的描述信息:
public class AuctionException extends Exception
{
// 无参数的构造器
public AuctionException(){} // ①
// 带一个字符串参数的构造器
public AuctionException(String msg) // ②
{
super(msg);
}
}
3.catch和throw同时使用
- catch和throw同时使用程序:
public class AuctionTest
{
private double initPrice = 30.0;
// 因为该方法中显式抛出了AuctionException异常,
// 所以此处需要声明抛出AuctionException异常
public void bid(String bidPrice)
throws AuctionException
{
var d = 0.0;
try
{
d = Double.parseDouble(bidPrice);
}
catch (Exception e)
{
// 此处完成本方法中可以对异常执行的修复处理,
// 此处仅仅是在控制台打印异常跟踪栈信息。
e.printStackTrace();
// 再次抛出自定义异常
throw new AuctionException("竞拍价必须是数值,"
+ "不能包含其他字符!");
}
if (initPrice > d)
{
throw new AuctionException("竞拍价比起拍价低,"
+ "不允许竞拍!");
}
initPrice = d;
}
public static void main(String[] args)
{
var at = new AuctionTest();
try
{
at.bid("df");
}
catch (AuctionException ae)
{
// 再次捕捉到bid方法中的异常。并对该异常进行处理
System.err.println(ae.getMessage());
}
}
}
4.使用throw语句抛出异常(387)
5.异常链
- 异常转译:程序捕获原始异常,抛出一个业务异常,其中包含了对异常的提示信息;这种行为也被称为异常链:
public static void main(String[] args)
{
try
{
...
}
catch
{
...//原始异常信息
throw new SalException("自定义异常"); //向客户展示的是无关于系统的异常
}
}
public class SalException extends Exception
{
public SalException(){}
public SalException(String msg)
{
super(msg);
}
}
5.Java的异常跟踪栈
- 异常对象的printStackTrace()用于打印异常的跟踪信息
6.异常处理规则(391)
- 不要使用过于庞大的try块;
- 不要忽略捕获的异常;
- 不要过度使用异常;