1、java异常类层次结构
在java中,所有的异常都是由**Throwable**
继承而来,Throwable
有两个体系:Error
和Exception
,继承关系如下:
**Error**
类表示Java运行时系统的内部错误和资源耗尽错误,程序一般对此无能为力,例如:
**OutOfMemoryError**
:内存耗尽**NoClassDefFoundError**
:无法加载某个Class**StackOverflowError**
:栈溢出
**Exception**
异常是设计java程序时需要关注的异常,包括两个分支:
**RuntimeException**
,运行时异常,程序错误导致的异常- 非
**RuntimeException**
异常 (包括IOException
、SQLException
、ReflectiveOperationException
等等) ,这类异常是应用程序逻辑处理的一部分,应该捕获并处理
java语言规范规定:
- 非受查**(
**unchecked**
)**(异常必须捕获的异常):包括Exception
及其子类,但不包括RuntimeException
及其子类- 受查**(
**checked**
)**异常(不需要捕获的异常):派生于Error
类或RuntimeException
类的所有异常。
2、异常使用
2.1声明异常
方法应该在其首部声明所有可能抛出的异常。这样可以从首部反映出这个方法可能抛出哪类受查异常。 异常声明使用**throws**
关键字,放在方法签名的尾部。
public void hello() throws Exception{
}
调用方在调用声明异常的方法时,必须抛出或捕获这些异常,否则编译器会报错。
2.2 抛出异常
throw new Exception()
2.2.1 异常的传递:
当某个方法抛出了异常时,如果当前方法没有捕获异常,异常就会被抛到上层调用方法,直到遇到某个**try ... catch**
被捕获为止: 通过**printStackTrace()**
可以打印出方法的调用栈,类似:
java.lang.NumberFormatException: null
at java.base/java.lang.Integer.parseInt(Integer.java:614)
at java.base/java.lang.Integer.parseInt(Integer.java:770)
at Main.process2(Main.java:16)
at Main.process1(Main.java:12)
at Main.main(Main.java:5)
在catch
子句中也可以抛出一个新的异常,这样做的目的是改变异常的类型:
try {
//业务代码
} catch (XXException e) {
throw new ZZZException();
}
但是这种方式将会丢失原始的异常类型,可以通过在构造异常的时候,把原始的Exception
实例传进去,新的Exception
就可以持有原始Exception
信息
try {
//业务代码
} catch (XXException e) {
throw new ZZZException(e);
}
2.3异常捕获
如果某个异常发生的时候没有在任何地方进行捕获,那程序就会终止执行,并在控制台上打印出异常信息,其中包括异常的类型和堆栈的内容。
**try ... catch**
语句可以捕获异常:
try{
// 业务代码
}catch(Exception e){
异常处理代码
}
try语句块的任何代码抛出一个在catch子句中说明的异常类时,程序将立即跳出try语句块,执行catch字句中的代码。
2.3.1 捕获多个异常
在一个try语句块中可以捕获多个异常类型,并对不同类型的异常做出不同的处理。 存在多个catch的时候,catch的顺序非常重要:子类必须写在前面。 比如下面的代码,当try中的代码抛出UnsupportedEncodingException异常时候,会被前面的IOException** **捕获并执行。
public static void main(String[] args) {
try {
process1();
process2();
process3();
} catch (IOException e) {
System.out.println("IO error");
} catch (UnsupportedEncodingException e) { // 永远捕获不到
System.out.println("Bad encoding");
}
}
2.3.2 finally语句
无论是否有异常发生,如果我们都希望执行一些语句,**finally**
语句块保证有无错误都会执行。
finally语句的特点特点:
- finally语句不是必须的,可写可不写;
- finally总是最后执行。
2.4 自定义异常
在项目中,经常会遇到任何标准异常类都没有能够充分地描述清楚的问题,这时可以创建新的异常类型。
异常类通常是通过类名来区分不同程序异常的,比如我们看到在异常
SQLException
,就会知道是SQL执行的异常,所以我们自定义异常时也应该结合业务类型定义异常类名。
通常情况下,定义新的异常类只需派生于Exception
,或者派生于Exception
子类的类。同时应该包含两个构造器,一个是默认的构造器,另一个是带有详细描述信息的构造器:
public class BaseException extends RuntimeException {
public BaseException() {
super()
}
public BaseException(String message) {
super(message);
}
}
2.5 try with resource
传统的手动释放外部资源一般放在一般放在**try.catch.finally**
机制的finally代码块中,JDK1.7之后有了**try-with-resource**
处理机制,可以更加方便的自动释放资源。
**注意:**使用
**try-with-resource**
要求被自动关闭的资源需要实现**Closeable**
或者**AutoCloseable**
接口)
带资源的try语句(try-with-resources)的最简形式为: try (Resource res){}
,try块退出时,会自动调用res.clos(),简化了关闭连接时出现异常的处理。
try( InputStrem inputStrem = new FileInputStream("test.gz" )){
}
3、断言
断言(Assertion)是一种调试程序的方式,断言机制允许在测试期间向代码中插入一些检查语句。当代码发布时, 这些插入的检测语句将会被自动地移走。在Java中,使用assert关键字来实现断言。
assert有两种形式:
assert 条件 ;
assert 条件 : 表达式;
这两种形式都会对条件进行检测,如果结果为false,则抛出AssertionError异常。在第二种形式中,表达式将被传入AssertionError的构造器,并转换成一个消息字符串。
PS:实际开发中,很少使用断言。更好的方法是编写单元测试。
4、日志
在开发中,会经常使用System.out.println
方法调用来帮助观察程序运行的操作过程,记录日志API就是为了简化这个操作而设计的。
使用日志有以下几点好处:
- 可以设置输出样式,避免自己每次都写"ERROR: " + var;
- 可以设置输出级别,禁止某些级别输出。例如,只输出错误日志;
- 可以被重定向到文件,这样可以在程序运行结束后查看日志;
- 可以按包名控制日志级别,只输出某些包打的日志;
4.1 Commons Logging 与 Log4的使用
Java标准库内置了日志包**java.util.logging**
,但是它有一些局限,使用并不是很广泛。
**Commons Logging**
是一个第三方日志库,它是由Apache创建的日志模块。可以作为**日志接口**
来使用。而真正的**日志实现**
可以使用Log4j。
**Commons Logging**
定义了6个日志级别:
FATAL
ERROR
WARNING
INFO
DEBUG
TRACE
使用**Commons Logging**
只需要和两个类打交道,并且只有两步:
第一步,通过LogFactory获取Log类的实例; 第二步,使用Log实例的方法打日志。
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class Main {
public static void main(String[] args) {
Log log = LogFactory.getLog(Main.class);
log.info("start...");
log.warn("end.");
}
}
Log4j作为真正的日志实现,在实际的使用中,只需要配置配置文件,不需要关心它的API。
4.2 SLF4J和Logback 的使用
SLF4J
和Logback
是另一对好搭档。**SLF4J**
类似于Commons Logging
,也是一个日志接口,而**Logback**
类似于Log4j
,是一个日志的实现。
SLF4J使用与Commons Logging类似:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
class Main {
final Logger logger = LoggerFactory.getLogger(getClass());
logger.ingo("")
}
参考: java核心技术卷1 廖雪峰java教程 java编程思想