Java异常正确使用方式

一. throws指定异常

请看如下代码:

// 错误的方式
public void doNotSpecifyException() throws Exception {
	// do Something();
}
// 正确的方式 指定到具体的异常
public void doSomething() throws NumberFormatException, IllegalArgumentException {
	// do something
}

有时候我们由于抛出了多个异常,由于偷懒我们就会直接throws Exception,这是不正确的。原因如下:

  • 方法的调用者得到的唯一信息是可能出现错误,并不知道哪些错误,为什么错误,因此不好处理这个异常。
  • 当应用程序增加一个异常时, 由于throws Exception隐藏了具体的错误,您需要通过测试用例而不是编译器错误来查找这些错误。

因此,最好指定到具体的异常。 它告诉方法的调用者需要处理哪些异常事件。同时如果增加了一个异常,你的客户端会意识到这个变化,甚至会得到一个错误。 这比只在运行特定测试用例时出现的异常更容易找到和处理。


二. Catch 指定的异常

请看下面代码:

try {
	doSomething();
} catch (NumberFormatException e) {
	// handle the NumberFormatException
	log.error(e);
} catch (IllegalArgumentException e) {
	// handle the IllegalArgumentException
	log.error(e);
}

在main方法捕获Java .lang. exception可能没有问题 ,但是,如果您正在实现一个库,就应该捕获指定的异常。它允许您以不同的方式处理每个异常类,并防止您捕获到意想不到的异常。 注意: 多层catch时,异常的范围是越来越大。


三. 只在处理异常的地方打异常日志

在抛出异常的地方打日志,然后将其重新抛出给调用者,这可能是经常做的,但这是不正确的,如:

catch (SQLException e) {
  ...
  LOGGER.log(Level.ERROR,  contextInfo, e);
  throw new MySQLException(contextInfo, e);
}

这样记录日志,由于多个实例也是这样打印,区分不了哪个实例。在多线程应用程序中,调试这种类型的日志尤其困难,因为来自其他线程的消息将与重复的记录并抛出的异常交织在一起。

还有可能会多次打印相同的异常。 这会扰乱监视工具中的统计信息,使操作和开发团队难以阅读日志文件。


四. 异常不能作为控制流

异常不能当成if else来使用,原因就是 异常没有if else高效。而且代码非常难以阅读。

五. 保留异常的原始原因

有时,您可能希望将异常包装在另一个异常中。单千万不要搞丢原始的异常堆栈。Exception类及其所有子类提供了几个构造函数方法,这些方法接受原始异常作为参数并将其设置为原因。 如下面代码:

try {
	doSomething();
} catch (NumberFormatException e) {
	throw new MyBusinessException(e, ErrorCode.CONFIGURATION_ERROR);
} catch (IllegalArgumentException e) {
	throw new MyBusinessException(e, ErrorCode.UNEXPECTED);
}

六. 不能泛化异常

请看下面代码:

public void doNotGeneralizeException() throws Exception {
	try {
		doSomething();
	} catch (NumberFormatException e) {
		throw new Exception(e);
	} catch (IllegalArgumentException e) {
		throw new Exception(e);
	}
}


//-----调用者 判断异常类型
try {
	doNotGeneralizeException();
} catch (Exception e) {
	if (e.getCause() instanceof NumberFormatException) {
		log.error("NumberFormatException: " + e);
	} else if (e.getCause() instanceof IllegalArgumentException) {
		log.error("IllegalArgumentException: " + e);
	} else {
		log.error("Unexpected exception: " + e);
	}
}

你捕获一个特定的异常,比如NumberFormatException,然后抛出一个非特定的java.lang.Exception,这就叫泛化了异常,这是错误的。它不仅隐藏了API中有关特定错误情况的信息,而且还使访问变得困难。 而且在调用层如果想要获取类型,必须使用if判断,代码非常难懂。

你可以包装成自己的异常如下:

try {
	doSomething();
} catch (NumberFormatException e) {
	throw new MyBusinessException(e, ErrorCode.CONFIGURATION_ERROR);
} catch (IllegalArgumentException e) {
	throw new MyBusinessException(e, ErrorCode.UNEXPECTED);
}

七.何时使用检查异常和未检查异常

如果客户端可以合理地预期从异常中恢复,那么将其设置为检查异常。 如果客户端无法从异常中恢复,则将其设置为未检查异常。

例如,在打开文件之前,可以先验证输入文件名。 如果用户输入的文件名无效,我们可以抛出一个自定义检查异常:

if (!isCorrectFileName(fileName)) {
    throw new IncorrectFileNameException("Incorrect filename : " + fileName );
}

通过这种方式,我们可以通过接受另一个用户输入文件名来恢复系统。 但是,如果输入文件名是空指针或空字符串,则意味着代码中出现了一些错误。 在这种情况下,我们应该抛出一个未检查的异常:

if (fileName == null || fileName.isEmpty())  {
    throw new NullOrEmptyException("The filename is null or empty.");
}


强烈推荐一个 进阶 JAVA架构师 的博客

JAVA架构师修炼

支付宝打赏 微信打赏

如果文章对您有帮助,您可以鼓励一下作者