一.异常处理机制
1.异常类的继承体系
try{
}
catch(Exception e)
{
//使用continue之后会继续运行try中的语句,
//从而实现循环
continue;
}
如下图所示,Java把所有的非正常情况分成两种:异常(Exception)和错误(Error),它们都继承Throwable父类。
public class DivTest
{
public static void main(String[] args)
{
try
{
int a = Integer.parseInt(args[0]);
int b = Integer.parseInt(args[1]);
int 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("未知异常");
}
}
}
异常捕获时,一定要记住先捕获小异常,再捕获大异常。否则出现编译错误。
2.Java7提供的多异常捕获
在Java7以前,每个catch块只能捕获一种类型的异常,但从Java7开始,一个catch块可以捕获多种类型的异常。
使用一个catch块捕获多种类型的异常时需要注意如下两个地方:
1>.捕获多种类型的异常时,多种异常类型之间用竖线隔开。
2>.捕获多种类型的异常时,异常变量有隐式的final修饰,因此程序不能对异常变量重新赋值。
public class MultiExceptionTest
{
public static void main(String[] args)
{
try
{
int a = Integer.parseInt(args[0]);
int b = Integer.parseInt(args[1]);
int 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"); // ②
}
}
}
3.访问异常信息
所有的异常对象都包含了如下几个常用方法:
getMessage():返回该异常的详细描述字符串
printStackTrace():将该异常的跟踪栈信息输出到标准错误输出
printStackTrace(PrintStream s):将该异常的跟踪栈信息输出到指定输出流
getStackTrace():返回该异常的跟踪栈信息。
4.使用finally回收资源
程序在try块里打开的一些物理资源(如数据库连接,网络连接和磁盘文件),这些物理资源都必须显式回收。
Java的垃圾回收机制不会回收任何物理资源,垃圾回收机制只能回收堆内存中对象所占用的内存。
其实不管try块中的代码是否异常,也不管哪一个catch块被执行,甚至在try块或catch块中执行了return语句,finally块总会被执行的。
import java.io.FileInputStream;
import java.io.IOException;
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块里的资源回收!");
}
}
}
结果:
a.txt (系统找不到指定的文件。)
执行finally块里的资源回收!
如果在catch中使用System.exit(1)语句来退出虚拟机,则会出现下面的结果:
a.txt (系统找不到指定的文件。)
因为如果在异常处理代码中使用System.exit(1)语句来退出虚拟机,则finally块将失去执行的机会。
不要在finally块中使用如return或throw等导致方法终止的语句,一旦在finally块中使用了上述语句,将会导致try块和catch块中的return和throw语句失效。
二.Java7的自动关闭资源的try语句
为保证try语句可以正常关闭资源,这些资源实现类必须实现AutoCloseable或Closeable接口,实现这两个接口就必须实现close方法。
import java.io.*;
public class AutoCloseTest
{
public static void main(String[] args)
throws IOException
{
//允许在try关键字后紧跟一对圆括号,圆括号可以声明
//初始化一个或多个资源,此处的资源指的是哪些必须在程序
//结束时显式关闭的资源,try语句结束时自动关闭这些资源
try (
// 声明、初始化两个可关闭的资源
// try语句会自动关闭这两个资源。
BufferedReader br = new BufferedReader(
new FileReader("AutoCloseTest.java"));
PrintStream ps = new PrintStream(new
FileOutputStream("a.txt")))
{
// 使用两个资源
System.out.println(br.readLine());
ps.println("庄生晓梦迷蝴蝶");
}
}
}
上面的程序,由于BufferedReader,PrintStream都实现了Closeable接口,而且他们放在try语句中声明,初始化,所以try语句会自动关闭他们。
Closeable是AutoCloseable的子接口,可以被自动关闭的资源类要么实现AutoCloseable接口,要么实现Closeable接口。Closeable接口里的close方法声明抛出了IOException,因此他的实现类在实现close方法时只能声明抛出IOException或其子类;AutoCloseable接口里的close方法声明跑出来Exception,因此他的实现类在实现close方法时可以声明抛出任何异常。
三.Checked异常和Runtime异常体系
Java的异常被分为两大类,checked异常和Runtime异常(运行时异常)。所有的RuntimeException类及其子类的实例被称为Runtime异常;不是RuntimeException类及其子类的异常实例则被称为Checked异常。
1…使用throws声明抛出异常
此时的异常信息就会交给JVM处理,jvm会打印该异常的跟踪栈信息,并结束程序。
import java.io.FileInputStream;
import java.io.IOException;
public class ThrowsTest
{
public static void main(String[] args)
throws Exception
{
// 因为test()方法声明抛出IOException异常,
// 所以调用该方法的代码要么处于try...catch块中,
// 要么处于另一个带throws声明抛出的方法中。
test();
}
public static void test()throws IOException
{
// 因为FileInputStream的构造器声明抛出IOException异常,
// 所以调用FileInputStream的代码要么处于try...catch块中,
// 要么处于另一个带throws声明抛出的方法中。
FileInputStream fis = new FileInputStream("a.txt");
}
}
结果:
Exception in thread "main" java.io.FileNotFoundException: a.txt (系统找不到指定的文件。)
at java.base/java.io.FileInputStream.open0(Native Method)
at java.base/java.io.FileInputStream.open(FileInputStream.java:213)
at java.base/java.io.FileInputStream.<init>(FileInputStream.java:155)
at java.base/java.io.FileInputStream.<init>(FileInputStream.java:110)
at ThrowsTest.test(ThrowsTest.java:19)
at ThrowsTest.main(ThrowsTest.java:12)
使用throws声明抛出异常时有一个限制,就是方法重写时“两小”中的一条规则:子类方法声明抛出的异常类型应该是父类方法声明抛出的异常类型的子类或相同,子类方法声明抛出的异常不允许比父类方法声明抛出的异常多。
如下:
public class OverrideThrows
{
public void test()throws IOException
{
FileInputStream fis = new FileInputStream("a.txt");
}
}
class Sub extends OverrideThrows
{
// 子类方法声明抛出了比父类方法更大的异常
// 所以下面方法出错
public void test()throws Exception
{
}
}
四.使用throw抛出异常
1.抛出异常
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,不符合要求");
}
}
}
如果throw语句抛出的异常是Checked异常,则该throw语句要么处于try块里,显示捕获该异常,要么放在一个带throws声明抛出的方法中,即把该异常交给方法的调用者处理;如果throw语句抛出的异常是Runtime异常,则该语句无须放在try块里,也无须放在带throws声明抛出的方法中;程序既可以显示使用try…catch来捕获并处理该异常,也可以完全不理会该异常,把该异常交给方法调用者处理。
2.自定义异常类
用户自定义异常都应该继承Exception基类,如果希望自定义Runtime异常,则应该继承RuntimeException基类,定义异常类时通常需要提供两个构造器:一个无参数的构造器,另一个是带一个字符串参数的构造器,这个字符串将作为该异常对象的描述信息(也就是异常对象的getMessage())。
public class AuctionException extends Exception
{
// 无参数的构造器
public AuctionException(){} //①
// 带一个字符串参数的构造器
public AuctionException(String msg) //②
{
super(msg);
}
}
3.catch和throw同时使用
前面介绍的异常处理方式如下两种:
1> 在出现异常的方法内捕获并处理异常,该方法的调用者将不能再次捕获该异常;
2> 该方法签名中声明抛出异常,将该异常完全交给方法调用者处理;
实际开发中,有时需要由几个方法协作才可完全处理该异常。
public class AuctionTest
{
private double initPrice = 30.0;
// 因为该方法中显式抛出了AuctionException异常,
// 所以此处需要声明抛出AuctionException异常
public void bid(String bidPrice)
throws AuctionException
{
double 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)
{
AuctionTest at = new AuctionTest();
try
{
at.bid("df");
}
catch (AuctionException ae)
{
// 再次捕捉到bid方法中的异常。并对该异常进行处理
System.err.println(ae.getMessage());
}
}
}
结果:
java.lang.NumberFormatException: For input string: "df"
at java.base/jdk.internal.math.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:2054)
at java.base/jdk.internal.math.FloatingDecimal.parseDouble(FloatingDecimal.java:110)
at java.base/java.lang.Double.parseDouble(Double.java:549)
at AuctionTest.bid(AuctionTest.java:12)
at AuctionTest.main(AuctionTest.java:35)
竞拍价必须是数值,不能包含其他字符!
4.Java7增强的throw语句
Java7开始,Java编译器会执行更细致的检查,Java编译器会检查throw语句抛出异常的实际类型。
所以下面的语句只能声明抛出FileNotFoundException异常即可。
public class ThrowTest2
{
public static void main(String[] args)
// Java 6认为①号代码可能抛出Exception,
// 所以此处声明抛出Exception
// throws Exception
// Java 7会检查①号代码可能抛出异常的实际类型,
// 因此此处只需声明抛出FileNotFoundException即可。
throws FileNotFoundException
{
try
{
new FileOutputStream("a.txt");
}
catch (Exception ex)
{
ex.printStackTrace();
throw ex; // ①
}
}
}
六.异常处理规则
1.不要过度使用异常;
2.不要使用过于庞大的try块;
3.避免使用Catch All语句;
4.不要忽略捕获到的异常;