Checked异常和Runtime异常体系
Java的异常被分为两大类:Checked异常和Runtime异常(运行时异常)。
所有的RuntimeException类及其子类的实例被称为Runtime异常;不是RuntimeException类及其子类的异常实例则被称为Checked异常。
Java认为Checked异常都是可以被处理(修复)的异常,所以Java程序必须显式处理Checked异常。
对于Checked异常的处理方式有如下两种。
➢ 当前方法明确知道如何处理该异常,程序应该使用try...catch块来捕获该异常,然后在对应的catch块中修复该异常
➢ 当前方法不知道如何处理这种异常,应该在定义该方法时声明抛出该异常。
Runtime异常则更加灵活,Runtime异常无须显式声明抛出,如果程序需要捕获Runtime异常,也可以使用try...catch块来实现。
使用throws声明抛出异常
使用 tinows 声明抛出异常的思路是,当前方法不知道如何处理这种类型的异常,该异常应该由上一级调用者处理;
该异常将交给 JVM 处理。JVM 对异常的处理方法是,打印异常的跟踪栈信息,并中止程序运行,这就是前面程序在遇到异常后自动结束的原因。
throws声明抛出只能在方法签名中使用,throws可以声明抛出多个异常类,多个异常类之间以逗号隔开。throws声明抛出的语法格式如下:
import java.io.*; public class ThrowsTest { public static void main(String[] args) throws IOException { var fis = new FileInputStream("a.txt"); } }
上面程序声明不处理IOException异常,将该异常交给JVM处理,所以程序一旦遇到该异常,JVM就会打印该异常的跟踪栈信息,并结束该程序。
如果某段代码中调用了一个带 throws 声明的方法,该方法声明抛出了 Checked 异常,则表明该为该方法希望它的调用者来处理该异常。
也就是说调用该方法时,要么放在try块中显式捕获该异常,要么放在另外一个带throws声明抛出的方法中。
import java.io.*; public class ThrowsTest2 { 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声明抛出的方法中。 var fis = new FileInputStream("a.txt"); } }
方法重写时声明抛出异常的限制
使用throws声明抛出异常时有一个限制:
就是方法重写时“两小”中的一条规则:子类方法声明抛出的异常类型应该是父类方法声明抛出的异常类型的子类或相同,
子类方法声明抛出的异常不允许比父类方法声明抛出的异常多。
import java.io.*; public class OverrideThrows { public void test() throws IOException { var fis = new FileInputStream("a.txt"); } } class Sub extends OverrideThrows { // 子类方法声明抛出了比父类方法更大的异常 // 所以下面方法出错 public void test() throws Exception { } }
使用throw抛出异常
当程序出现错误时,系统会自动抛出异常;除此之外,Java也允许程序自行抛出异常,自行抛出异常使用throw语句来完成。
抛出异常
如果需要在程序中自行抛出异常,则应使用throw语句,throw语句可以单独使用,
throw语句抛出的不是异常类,而是一个异常实例,而且每次只能抛出一个异常实例。
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,不符合要求"); } } }
自定义异常类
用户自定义异常都应该继承Exception基类,如果希望自定义Runtime异常,则应该继承RuntimeException基类。
定义异常类时通常需要提供两个构造器:
一个是无参数的构造器;
另一个是带一个字符串参数的构造器,这个字符串将作为该异常对象的描述信息(也就是异常对象的getMessage()方法的返回值)。
public class AuctionException extends Exception { // 无参数的构造器 public AuctionException(){} // ① // 带一个字符串参数的构造器 public AuctionException(String msg) // ② { super(msg); } }
上面程序创建了 AuctionException 异常类,并为该异常类提供了两个构造器。
尤其是②号粗体字代码部分创建的带一个字符串参数的构造器,其执行体也非常简单,仅通过 super 来调用父类的构造器,
正是这行 super 调用可以将此字符串参数传给异常对象的 message 属性,该 message 属性就是该异常对象的详细描述信息。
如果需要自定义 Runtime 异常,只需将 AuctionExceptionjava 程序中的 Exception 基类改为RuntimeException 基类,其他地方无须修改。
(提示:在大部分情况下,创建自定义异常都可采用与AuctionExceptionjava相似的代码完成,在天部分情况下,创建自定又并吊有只需改变 AuctionException 异常的类名即可,让该异常类
的类名可以准确描述该异常。
catch和throw同时使用
在出现异常的方法内捕获并处理异常,该方法的调用者将不能再次捕获该异常。
前面介绍的异常处理方式有如下两种。
该方法签名中声明抛出该异常,将该异常完全交给方法调用者处理。
在实际应用中往往需要更复杂的处理方式——当一个异常出现时,单靠某个方法无法完全处理该异
常,必须由几个方法协作才可完全处理该异常。
也就是说,在异常出现的当前方法中,程序只对异常进行部分处理,还有些处理需要在该方法的调用者中才能完成,所以应该再次抛出异常,让该方法的调用者也能捕获到异常。
为了实现这种通过多个方法协作处理同一个异常的情形,可以在 catch 块中结合 throw 语句来完成。
如下例子程序示范了这种 catch 和 throw 同时使用的方法。
public class AuctionException extends Exception { public AuctionException(){} public AuctionException(String msg) { super(msg); } }
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()); } } }
public class AuctionException extends Exception { public AuctionException(){} public AuctionException(String msg) { super(msg); } }
public class AuctionTest { private double initPrice = 30.0; public void bid(String bidPrice) throws AuctionException { double d = 0.0; try { d = Double.parseDouble(bidPrice); } catch (Exception e) { e.printStackTrace(); throw new AuctionException("123"); } if (initPrice > d) { throw new AuctionException("abc"); } initPrice = d; } public static void main(String[] args) { AuctionTest at = new AuctionTest(); try { at.bid("df"); } catch (AuctionException ae) { System.err.println(ae.getMessage()); } } }