异常:Core Java 7.1

异常:程序运行过程中,由于程序本身的错误或者外部环境的影响,而出现的异常情况

断言:为确保程序能正常运行或者排查程序出现异常的原因,需要编写一些测试代码进行测试。但程序正式运行时,是不允许测试代码工作的。如果直接删掉这些测试代码,再次遇到异常时,可能又需要重复编写相同的测试代码。可以通过断言来避免这些问题。

日志:当程序遇到异常时,并不能总是能同用户或者终端沟通。此时可以通过日志记录下问题,以备日后进行分析。

异常处理

当程序出现异常时,比如网络中断、文件格式错误、数组下标越界、null对象,如果程序正在进行某些操作而因为出现异常无法完成,程序应该能:

  1. 返回到安全状态,并让用户能执行其他操作
  2. 允许用户保存所有操作的结果,并以妥善的方式终止程序

异常处理的任务就是 将控制权 从异常产生的代码处 转移到 能够处理这种异常的处理器(ExceptionHandler)处

如果方法不能够以正常的途径完成它的任务,那就通过另外一个路径退出方法。这种情况下,方法不返回任何值,而是抛出一个封装了错误信息的对象,并立即退出。这种情况下,调用这个方法的代码也无法继续执行,而是异常处理机制开始搜索能够处理这种异常状况的异常处理器(ExceptionHandler)。

几种类型的错误

用户输入错误、硬件错误(打印机关闭的、打印机打印过程中没有纸了)、物理限制(内存、硬盘满了,空间不足了)
代码错误

Throwable -> [ Error, Exception -> [ IOException, RuntimeException ] ]

Error是JVM自身错误,即JVM内部错误或者资源耗尽错误,应用程序不应该抛出这种类型的对象。

应用程序的错误(BUG)会抛出RuntimeException,常见的有:

  • ClassCastException
  • ArrayIndexOutOfBoundsException
  • nullPointer

应用程序本身没有问题,而是IO等其它问题,会抛出IOException,常见的有:

  • 文件指针已经到文件尾部:EOFException
  • 试图打开一个不存在的文件:FileNotExistsException,尽管可以在打开之前先检查一下,但是有可能下一瞬间,它又被其它程序删除了。
  • 根据给定的字符串找不到类:ClassNotFoundException

声明受检异常

在Exception中,RuntimeException是非受检异常(unchecked),其它是可受检异常(checked Exception)。

所有可能的受检异常都必须在方法中声明。

不需要声明JVM的内部错误,即Error,因为根本不可控。
也不需要声明RuntimeException,因为它是程序错误(BUG)引起的(而不是环境错误),是应该主动避免的。

声明、抛出、传递、捕获、处理

传递、抛出、声明

当代码可能出现受检异常时,最好的选择是什么也不做,而是将异常传递给调用者。传递的方式就是抛出异常,需要在
如果 read 方法出现了错误, 就 让 read 方法的调用者去操心!即声明read方法可能会抛出一个 IOException

捕获、处理

应该捕获那些知道如何处理的异常, 而将那些不知道怎样处理的异常继续进行传递。

仔细阅读一下 Java API 文档, 以便知道每个方法可能会抛出哪种异常, 然后再决定是自己处理,还是添加到 throws 列表中。

再次抛出异常与异常链

当超类的方法没有抛出异常,而子类重写后的方法代码可能会抛出受检异常时,需要在子类方法中自行捕获并处理。因为规则是子类抛出的异常不能大于父类。

这时就可以在catch语句中,将异常再次包装成一个非受检异常,并直接抛出它。

try{
}
catch(SQLException e){
	Throwable throwable = new ServletException("database error");
	throwable.initCause(e);
	throw e
}

当上层调用方捕获到以上再次包装后的异常时,就可以用如下方式获取原始异常SQLException e:

throwable.getCause();

强烈建议使用这种包装技术,如果开发了一个供其他程序员使用的子系统,这样就可以用户抛出子系统中的高级异常,而不会丢失原始异常的细节。

强烈建议耦合try/catch 和 try/finally 语句块,这样可以提高代码的清晰度。方式如下:

try{
	try{
		// 工作代码
	}
	finally{
		//  释放资源
	}
}
catch(){
	// 处理、或者抛出异常
}

这种方式不仅清楚,而且还有一个功能,就是会报告finally子句中出现的错误

带资源的try语句(try-with-resource)

如果在try块中打开了资源,并且这些资源实现了AutoCloseable接口,如InputStream\OutputStream\Reader\Writer等及其子类,那么就可以简写如下(try-with-resource):

public static void main(String[] args){

		try(
				Scanner in = new Scanner(new FileInputStream("IntegerProxy2.java"));
				PrintWriter out = new PrintWriter("out.txt")
			) // 在try关键字后加上一队小括号(),所有的要打开的实现了AutoCloseable的资源,都必须定义在这对小括号内,
			 // 这样,javac就知道finally要释放哪些资源了。
			{
				while(in.hasNextLine()){
					out.println(in.nextLine());
				}
		} catch(FileNotFoundException e){
			e.printStackTrace();
		}
    }

注意,只有实现了AutoCloseable的类型的变量才可以定义在try()的小括号内,如果在try()中定义一个String name="lily",编译无法通过。

异常处理注意

  • 不要用异常处理代替测试,因为处理异常花费的时间远超正常的测试花费的时间
  • 不要每条语句一个try-catch,而是把所有catch语句连在一起写,比较清晰
  • 尽量将异常层次化,而不是所有的异常全都catch封装到一个Throwable或者Exception对象中去
  • 将一种异常转换成另一种更加合适的异常时不要犹豫。例如在解析某个文件的一个整数时,捕获到了NumberFormatException异常,然后将它转换成IOException或者MySubsystemException的子类。
  • **如果要调用的方法抛出的异常发生的可能性概率极低,可以用捕获后不做任何处理的方式直接关闭它。**但是不应该什么也不做,不应该既不捕获处理、也不抛出。
  • 当当前方法的参数或者返回值为null时,应该根据实际业务选择是否抛出异常,在当前方法中抛出一个有具体明确的说明信息的Exception,比调用当前方法的方法抛出一个无明确信息的NullPointerException这样一个运行时异常RuntimeException更有用(有时可能需要根据业务的实际情况处理,有时业务允许结果为空)
  • 底层的方法更应该传递异常,而不是捕获和处理异常。这样就让高层次的方法通知用户发生了错误、或者放弃不合适的命令
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章