异常类的创建与序列化
本文首先介绍了Java异常类的层次结构,然后描述了如何创建异常类,最后介绍了在创建异常类的过程中会遇到的序列化问题。
我们在进行Java编程时经常需要抛出和捕获各种异常,用来处理程序执行过程中可能出现的各种错误。但是我们可能会遇到任何标准异常类都无法描述清楚的情况,这时就需要创建自己的异常类。
Java异常类的层次结构
Java中所有异常类都派生于Throwable
类。但是在Throwable
的下一层,有两个分支,分别是:
-
Error
:描述了各种我们无能为力的系统内部错误 -
Exception
:又分为两个分支,RuntimeException
类和其他异常(注意:
IOException
只是其他异常的一种)
区分RuntimeException
和其他异常的一般规则是:
- 由于编程错误导致的异常属于
RuntimeException
- 如果程序本身没有问题,那么是其他异常
Error
我们完全无法处理,RuntimeException
我们完全可以避免。所以Java编译器只会检查你是否为其他异常提供了异常处理器,而不会检查Error
和RuntimeException
。
因此Error
与RuntimeException
被称为**非检查型(unchecked)异常,其他异常被称为检查型(checked)**异常。
创建异常类
从上面的描述可以知道,我们需要创建一个检查型异常,即Exception
及其子类(除去RuntimeException
)的派生。
因此我们创建一个Exception
的子类作为示范。
根据书上描述的习惯做法,我们定义两个构造器,一个是默认的构造器,一个是包含详细信息的构造器,代码如下(注释省略):
public class IntervalConflictException extends Exception {
public IntervalConflictException() {
}
public IntervalConflictException(String message) {
super(message);
}
}
但是我们会发现上述代码在eclipse里运行的时候会有Warning
The serializable class IntervalConflictException does not declare a static final serialVersionUID field of type long
eclipse自动增加了一行代码后解决了问题:
public class IntervalConflictException extends Exception {
private static final long serialVersionUID = 1L;
public IntervalConflictException() {
}
public IntervalConflictException(String message) {
super(message);
}
}
那么为什么会出现这个Warning
,加了这行代码又为什么可以解决问题呢?
序列化与Serializable
接口
我们找到Throwable
的声明:
public class Throwable implements Serializable
发现Throwable
实现了接口Serializable
而我们创建的IntervalConflictException
是Throwable
的派生,根据继承规则,自动实现(implements
)了Serializable
,而不需要显式的声明。
那么Serializable
接口里到底有什么呢,答案是Serializable
接口里什么都没有,是一个空接口。
public interface Serializable {
}
空接口只是一个标识,只是为了将一些类区分出来。
就好比上课举手。任何听课的人都可以举手,举手只是为了告诉老师,举手的人有话要说。
同样地,Serializable
只是告诉Java可以对这个对象进行序列化
Serializable
是Java序列化的标识,实现了它就意味着它的对象可以被序列化。
序列化是一种跨平台储存和网络传输对象的机制。它将对象以特定的规则转化为字节数组,然后通过IO的方式进行跨平台存储和网络传输。反序列化是序列化的逆过程,将字节数组转化回对象。
简单来说,序列化是一种为了方便大家之间传递程序而产生的机制,它使得软件提供商可以将软件分发给你。如果你是单机玩家,那么序列化对你没什么意义。
因此我们创建的IntervalConflictException
的对象是可以序列化的,这个类应该满足序列化的要求。
serialVersionUID
序列化的要求之一就是要有serialVersionUID
。
serialVersionUID
是序列化的版本号,用来判断对象的版本有没有改变。
如果软件版本发生改变,你的软件可能会让你更新
serialVersionUID
可以使用两种方式生成:
- 默认的
1L
。版本变化之后手动改为2L
,3L
,4L
,以此类推;不一定要连续,跟之前的版本不一样就行,重点是手动更新 - 使用hash算法。通过包名,类名,继承关系,各种方法和属性等用哈希算法生成一个值,只要对代码进行了更改,计算出来的哈希值就会不一样。
如果不显式地声明一个serialVersionUID
,就会使用hash算法自动生成一个serialVersionUID
。
但是,自动生成的serialVersionUID
对类的细节高度敏感。这会造成两个后果:
-
只要类修改了一点点,
serialVersionUID
就会发生变化 -
即使我们没有修改代码,但是这些细节仍然可能因为编译器的不同而发生变化,从而使
serialVersionUID
发生变化,这会导致反序列化时出现问题(即出现
InvalidClassException
异常)
大多数时候我们不希望serialVersionUID
对细节如此敏感,因此Java编译器会强烈建议我们显式地声明serialVersionUID
,从而会弹出一开始的Warning
。
参考资料
书籍
Java核心技术 卷Ⅰ 基础知识(原书第11版) 第7章