Java 学习之路 之 Checked异常和Runtime异常体系(四十六)

Java 的异常被分为两大类:Checked 异常和 Runtime 异常(运行时异常)。所有的 RuntimeException 类及其子类的实例被称为 Runtime 异常:不是 RuntimeException 类及其子类的异常实例则被称为 Checked 异常。

只有 Java 语言提供了 Checked 异常,其他语言都没有提供 Checked 异常。Java 认为 Checked 异常都是可以被处理(修复)的异常,所以 Java 程序必须显式处理 Checked 异常。如果程序没有处理 Checked 异常,该程序在编译时就会发生错误,无法通过编译。

Checked 异常体现了 Java 的设计哲学——没有完善错误处理的代码根本就不会被执行!

对于 Checked 异常的处理方式有如下两种。

当前方法明确知道如何处理该异常,程序应该使用 try...catch 块来捕获该异常,然后在对应的 catch 块中修复该异常。例如,前面介绍的五子棋游戏中处理用户输入不合法的异常,程序在 catch 块中打印对用户的提示信息,重新开始下一次循环。

当前方法不知道如何处理这种异常,应该在定义该方法时声明抛出该异常。

Runtime 异常则更加灵活,Runtime 异常无须显式声明抛出,如果程序需要捕获 Runtime 异常,也可以使用 try...catch 块来实现。

只有 Java 语言提供了 Checked 异常,Checked 异常体现了 Java 的严谨性,它要求程序员必须注意该异常——要么显式声明抛出,要么显式捕获并处理它,总之不允许对 Checked 异常不闻不问。这是一种非常严谨的设计哲学,可以增加程序的健壮性。问题是:大部分的方法总是不能明确地知道如何处理异常,因此只能声明抛出该异常,而这种情况又是如此普遍,所以 Checked 异常降低了程序开发的生产率和代码的执行效率。关于 Checked 异常的优劣,在 Java 领域是一个备受争论的问题。

1,使用 throws 声明抛出异常

使用 throws 声明抛出异常的思路是,当前方法不知道如何处理这种类型的异常,该异常应该由上一级调用者处理:如果 main 方法也不知道如何处理这种类型的异常,也可以使用 throws 声明抛出异常,该异常将交给 JVM 处理。JVM 对异常的处理方法是,打印异常的跟踪栈信息,并中止裎序运行,这就是前面程序在遇到异常后自动结束的原因。

前面内容里有些程序已经用到了 throws 声明抛出,throws 声明抛出只能在方法签名中使用,throws 可以声明抛出多个异常类,多个异常类之间以逗号隔开。throws 声明抛出的语法格式如下:

throws ExceptionClass1 , ExceptionClass2...

上面 throws 声明抛出的语法格式仅跟在方法签名之后,如下例子程序使用了 throws 来声明抛出 IOException 异常,一旦使用 throws 语句声明抛出该异常,程序就无须使用 try...catch 块来捕获该异常了。

package com.sym.demo5;

import java.io.FileInputStream;
import java.io.IOException;

public class ThrowsTest {
	public static void main(String[] args) throws IOException {
		FileInputStream fis = new FileInputStream("a.txt");
	}
}

上面程序声明不处理 IOException 异常,将该异常交给 JVM 处理,所以程序一旦遇到该异常.JVM 就会打印该异常的跟踪栈信息,并结束程序。运行上而程序,会得到如下结果。

Exception in thread "main" java.io.FileNotFoundException: a.txt (The system cannot find the file specified)
	at java.io.FileInputStream.open(Native Method)
	at java.io.FileInputStream.<init>(Unknown Source)
	at java.io.FileInputStream.<init>(Unknown Source)
	at com.sym.demo5.ThrowsTest.main(ThrowsTest.java:8)

如果某段代码中调用了一个带 throws 声明的方法,该方法声明抛出了 Checked 异常,则表明该方法希望它的调用者来处理该异常。也就是说,调用该方法时要么放在 try 块中显式捕获该异常,要么放在另一个带 throws 声明抛出的方法中。如下例子程序示范了这种用法。

package com.sym.demo5;

import java.io.FileInputStream;
import java.io.IOException;

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 声明抛出的方法中
		FileInputStream fis = new FileInputStream("a.txt");
	}
}

使用“throws”声明抛出异常时有一个限制,就是方法重写时“两小”中的一条规则:子类方法声明抛出的异常类型应该是父类方法声明抛出的异常类型的子类或相同,子类方法声明抛出的异常不允许比父类方法声明抛出的异常多。看如下程序。  

package com.sym.demo5;

import java.io.FileInputStream;
import java.io.IOException;

public class OverrideThrows {
	public void test() throws IOException{
		FileInputStream fis = new FileInputStream("a.txt");
	}
	
	class Sub extends OverrideThrows{
		//子类方法声明抛出了比父类更大的异常
		// 所以下面方法出错
		public void test() throws Exception{
			
		}
	}
}

上面程序中 Sub 子类中的 test() 方法声明抛出 Exception,该 Exception 是其父类声明抛出异常 IOException 类的父类,这将导致程序无法通过编译。

由此可见,使用 Checked 异常至少存在如下两大不便之处。

对于程序中的 Checked 异常,Java 要求必须显式捕获并处理该异常,或者显式声明抛出该异常。这样就增加了编程复杂度。

如果在方法中显式声明抛出 Checked 异常,将会导致方法签名与异常耦合,如果该方法是重写父类的方法.则该方法抛出的异常还会受到被重写方法所抛出异常的限制。

在大部分情况下笔者推荐使用 Runtime 异常,而不使用 Checked 异常。尤其当程序需要自行抛出异常时,使用 Runtime 异常将更加简洁。

当使用 Runtime 异常时,程序无须在方法中声明抛出 Checked 异常,一旦发生了自定义错误,程序只管抛出 Runtime 异常即可。

如果程序需要在合适的地方捕获异常并对异常进行处理,则一样可以使用 try...catch 块来捕获 Runtime 异常。

使用 Runtime 异常是比较省事的方式,使用这种方式既可以享受“正常代码和错误处理代码分离”,“保证程序具有较好的健壮性一的优势,又可以避免因为使用 Checked 异常带来的编程烦琐性。因此,C#、Ruby、Python 等语言没有所谓的 Checked 异常,所有的异常都是 Runtime 异常。

但 Checked 异常也有其优势---- Checked 异常能在编译时提醒程序员代码可能存在的问题,提醒程序员必须注意处理该异常,或者声明该异常由该方法调用者来处理,从而可以避免程序员因为粗心而忘记处理该异常的错误。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值