java中多层Exception异常传递及处理浅谈

 JAVA中异常处理是我们经常接触的,很多时候,我们在排查日志的时候都发现,一个很复杂的函数里面,突然间中断了,从日志打印的堆栈信息中,又很难发现真正的问题所在,因为很多时候异常被掩盖了。

举一个最简单的例子,例如:

	public void testThrowFileEx() throws FileNotFoundException{
		throw new FileNotFoundException();
	}
	
	public void testThrowClassEx() throws ClassNotFoundException{
		try {
			this.equals("");
			testThrowFileEx(); //FileNotFoundException()这个方式是会抛异常的
			this.hashCode();
		} catch (FileNotFoundException e) {
			throw new ClassNotFoundException();
		}
	}

这里得到的结果是:

java.lang.ClassNotFoundException
	at com.test.file.encrypt.SimpleEncryptFile.testThrowClassEx(SimpleEncryptFile.java:90)
	at com.test.file.encrypt.SimpleEncryptFile.main(SimpleEncryptFile.java:106)

打印的堆栈结果没有显示FileNotFoundException,只是显示了ClassNotFoundException。

而testThrowClassEx()函数中的ClassNotFoundException是由于testThrowFileEx()里面抛出的FileNotFoundException引起的,不过由于testThrowClassEx()函数内部处理了FileNotFoundException,反而抛出了新的ClassNotFoundException,因此掩盖了真实的错误信息。我们在排查问题的时候就不清楚ClassNotFoundException是testThrowClassEx()中具体那个函数错误引起的。

 

这里我们有以下思路可以解决:

1、当我们不需要转义异常的,就直接抛出调用函数的异常即可。

2、当我们需要转义异常的,就需要先在抛异常前,先打印堆栈信息。

      示例中e.printStackTrace()只是为了说明问题,在具体项目中使用日志工具打印堆栈信息,这种是初级的处理方式,一般应用中不会这样做。

3、真实项目中,当我们需要转义异常的,例如从AException转为BException,这个时候有两个处理方式:

     3.1、当BException构造函数可以传递Throwable的时候,直接throw new BException(e);即可。

     3.2、当BException构造函数不可以传递Throwable的时候,就需要自己调用initCause()初始化BException异常引起的源异常。

这里就涉及到Exception的cause对象问题了,cause对象是指我们的异常是由于什么异常引起的。

很多时候我们看见的异常,有部分异常会打印:Cause by...,就是抛出异常对象时,传递了cause对象。

Exception in thread "main" java.io.InterruptedIOException: file is interrupted
	at com.test.exception.demo.DealFile.deals(DealFile.java:37)
	at com.test.exception.demo.DealFile.main(DealFile.java:45)
Caused by: java.io.FileNotFoundException: File is broken
	at com.test.exception.demo.DealFile.openFile(DealFile.java:16)
	at com.test.exception.demo.DealFile.readFile(DealFile.java:23)
	at com.test.exception.demo.DealFile.deals(DealFile.java:31)
	... 1 more

 

--------------------------这里是对于上面的进行举例说明-----------------------------

1、这里先讲不需要转义的情况,就直接抛出原来的异常即可,或者连异常都不需要处理,直接在方法中throws异常,例如:

	public void testThrowFileEx() throws FileNotFoundException{
		throw new FileNotFoundException();
	}
	
	public void testThrowClassEx() throws FileNotFoundException{
		try{
			this.equals("");
			testThrowFileEx(); //FileNotFoundException()这个方式是会抛异常的
			this.hashCode();
		}catch(FileNotFoundException e){
			throw e;
		}finally{
			System.out.println("意外中断了...");
		}
	}
	

或者:

	public void testThrowFileEx() throws FileNotFoundException{
		throw new FileNotFoundException();
	}
	
	public void testThrowClassEx() throws FileNotFoundException{
		this.equals("");
		testThrowFileEx(); //FileNotFoundException()这个方式是会抛异常的
		this.hashCode();
	}

打印的日志:

java.io.FileNotFoundException
	at com.test.file.encrypt.SimpleEncryptFile.testThrowFileEx(SimpleEncryptFile.java:98)
	at com.test.file.encrypt.SimpleEncryptFile.testThrowClassEx(SimpleEncryptFile.java:104)
	at com.test.file.encrypt.SimpleEncryptFile.main(SimpleEncryptFile.java:124)

这里就得到了我们想要的结果,准确定位到错误位置是由于testThrowFileEx()引起的。

两种场景都可以,有没有try catch模块,主要是看函数里面有没有finally块要处理。

         如果不需要处理finally块的,就直接抛出异常就可以了。

         如果需要处理finally块的,就需要使用try catch模块。

 

2、再来看看需要转义的情况,例如:

	public void testThrowFileEx() throws FileNotFoundException{
		throw new FileNotFoundException();
	}
	
	public void testThrowClassEx() throws ClassNotFoundException{
		try {
			this.equals("");
			testThrowFileEx(); //FileNotFoundException()这个方式是会抛异常的
			this.hashCode();
		} catch (FileNotFoundException e) {
			e.printStackTrace(); 
			throw new ClassNotFoundException();
		}
	}

打印的结果:

java.io.FileNotFoundException
	at com.test.file.encrypt.SimpleEncryptFile.testThrowFileEx(SimpleEncryptFile.java:98)
	at com.test.file.encrypt.SimpleEncryptFile.testThrowClassEx(SimpleEncryptFile.java:104)
	at com.test.file.encrypt.SimpleEncryptFile.main(SimpleEncryptFile.java:124)
java.lang.ClassNotFoundException
	at com.test.file.encrypt.SimpleEncryptFile.testThrowClassEx(SimpleEncryptFile.java:108)
	at com.test.file.encrypt.SimpleEncryptFile.main(SimpleEncryptFile.java:124)

这里打印的结果,打印了两段错误信息,其中第一段是FIleNotFoundException,这个是e.printStackTrace()打印出来的,而ClassNotFoundException才是抛出的异常。

通过这种方式,就可以在需要转义时候,先打印堆栈信息,这样方便排查问题,但这种只是平常Demo可以这样些,真实项目中不会这样处理。

 

3、真实项目中,一般建议使用异常传递机制,把底层异常一直往外抛。

package com.test.exception.demo;

import java.io.FileNotFoundException;
import java.io.InterruptedIOException;
import java.util.Random;

public class DealFile {
	public void openFile() throws FileNotFoundException{
		if(new Random().nextBoolean()){
			System.out.println("随机True");
			throw new FileNotFoundException("File not existed");
		}else{
			System.out.println("随机False");
			throw new FileNotFoundException("File is broken");
		}
	}
	
	public void readFile() throws FileNotFoundException{
		System.out.println("file reading");
		try{
			openFile();
		}catch(FileNotFoundException e){
			throw e;
		}
	}
	
	public void deals() throws InterruptedIOException{
		try{
			readFile();
		}catch(FileNotFoundException e){
			/**
			 * 使用initCause函数,把readFile函数中的FileNotFoundException转为InterruptedIOException
			 * 并把FileNotFoundException传递给外层。
			 */
			InterruptedIOException ie = new InterruptedIOException("file is interrupted");
			ie.initCause(e);
			throw ie;
		}
	}
	
	public static void main(String[] args) throws Exception {
		DealFile df = new DealFile();
		df.deals();
	}
}

执行结果:

file reading
随机False
Exception in thread "main" java.io.InterruptedIOException: file is interrupted
	at com.test.exception.demo.DealFile.deals(DealFile.java:31)
	at com.test.exception.demo.DealFile.main(DealFile.java:39)
Caused by: java.io.FileNotFoundException: File is broken
	at com.test.exception.demo.DealFile.openFile(DealFile.java:14)
	at com.test.exception.demo.DealFile.readFile(DealFile.java:21)
	at com.test.exception.demo.DealFile.deals(DealFile.java:29)
	... 1 more

打印结果可以看见是deals()函数抛出了InterruptedIOException异常,而这个异常又是因为openFile()函数中的FileNotFoundException异常引起的。

这样就可以轻松的异常信息逐层传递给外层,开发者从日志中就可以清晰看见错误引起的原因,方便排查问题。

 

另外,还有一个小技巧,我们在调开发的时候,由于程序继承和调用层级很深,我们不清楚当前函数是从给哪个对象调用,而函数又没有异常需要捕获。这时可以使用以下函数来打印当前的堆栈信息,通过打印的堆栈信息,就很清楚整个程序调用这个方法时的逻辑了,非常好用。

new Exception().printStackTrace();

 

这里说的问题都是非常基础的内容,但很多人都没有特别在意这些细节问题。如果大家还有更好的办法,欢迎可以一起探讨。

 

 

  • 4
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java异常处理机制可以说非常重要,它能够帮助我们优雅地处理程序异常情况。以下是一些关于如何在 Java 实现优雅异常处理的建议: 1. 使用适当的异常类型:Java 提供了许多内置的异常类型,你应该根据具体情况选择合适的异常类型。这样可以让代码更具可读性,并且方便其他开发人员理解你的代码。 2. 不要捕获所有异常:捕获异常意味着你要对其进行处理,但并不是所有的异常都需要你来处理。只捕获那些你能够处理或者你有必要处理异常,对于其他异常,可以通过在方法签名声明 throws 关键字来传递给上层调用者处理。 3. 使用 try-with-resources:对于实现了 AutoCloseable 接口的资源对象,可以使用 try-with-resources 语句来自动关闭资源。这样可以避免资源泄漏,并且能够更好地管理资源。 4. 使用自定义异常:除了使用内置的异常类型,你还可以根据具体需求创建自定义异常。自定义异常可以提供更加具体的异常信息,帮助你更好地定位问题。 5. 记录和处理异常信息:在捕获到异常时,不仅仅是简单地打印异常堆栈信息,你还应该考虑记录异常信息,并根据具体情况进行适当的处理。可以将异常信息写入日志文件,或者返回给用户友好的错误提示。 6. 避免空指针异常:空指针异常Java 开发最常见的异常之一。为了避免空指针异常,你可以使用 null 检查、空对象模式、Optional 类型等方式来处理可能为 null 的对象。 7. 分层处理异常:在程序可以使用多层异常处理机制,将底层的异常转换为更高级别的异常,从而能够更好地管理和处理异常。 总之,优雅的异常处理需要根据具体情况进行合理选择和实践。通过合适的异常类型、适当的捕获和处理、记录和传递异常信息,以及避免常见的错误,你能够使你的代码更加健壮和可维护。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值