音乐播放器
scraty's Blog
 
文章 标签
9

Powered by Gridea | Theme: Fog

异常类的创建与序列化

本文首先介绍了Java异常类的层次结构,然后描述了如何创建异常类,最后介绍了在创建异常类的过程中会遇到的序列化问题

我们在进行Java编程时经常需要抛出和捕获各种异常,用来处理程序执行过程中可能出现的各种错误。但是我们可能会遇到任何标准异常类都无法描述清楚的情况,这时就需要创建自己的异常类。

Java异常类的层次结构

Java中所有异常类都派生于Throwable类。但是在Throwable的下一层,有两个分支,分别是:

  • Error:描述了各种我们无能为力的系统内部错误

  • Exception:又分为两个分支,RuntimeException类和其他异常

    (注意:IOException只是其他异常的一种

Java中的异常层次结构

区分RuntimeException和其他异常的一般规则是:

  • 由于编程错误导致的异常属于RuntimeException
  • 如果程序本身没有问题,那么是其他异常

Error我们完全无法处理,RuntimeException我们完全可以避免。所以Java编译器只会检查你是否为其他异常提供了异常处理器,而不会检查ErrorRuntimeException

因此ErrorRuntimeException被称为**非检查型(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

而我们创建的IntervalConflictExceptionThrowable的派生,根据继承规则,自动实现(implements)了Serializable,而不需要显式的声明。

那么Serializable接口里到底有什么呢,答案是Serializable接口里什么都没有,是一个空接口

public interface Serializable {
}

空接口只是一个标识,只是为了将一些类区分出来。

就好比上课举手。任何听课的人都可以举手,举手只是为了告诉老师,举手的人有话要说。

同样地,Serializable只是告诉Java可以对这个对象进行序列化

Serializable是Java序列化的标识,实现了它就意味着它的对象可以被序列化

序列化是一种跨平台储存网络传输对象的机制。它将对象以特定的规则转化字节数组,然后通过IO的方式进行跨平台存储和网络传输。反序列化是序列化的逆过程,将字节数组转化回对象。

简单来说,序列化是一种为了方便大家之间传递程序而产生的机制,它使得软件提供商可以将软件分发给你。如果你是单机玩家,那么序列化对你没什么意义。

因此我们创建的IntervalConflictException的对象是可以序列化的,这个类应该满足序列化的要求

serialVersionUID

序列化的要求之一就是要有serialVersionUID

serialVersionUID是序列化的版本号,用来判断对象的版本有没有改变。

如果软件版本发生改变,你的软件可能会让你更新

serialVersionUID可以使用两种方式生成:

  1. 默认的1L。版本变化之后手动改为2L3L4L,以此类推;不一定要连续,跟之前的版本不一样就行,重点是手动更新
  2. 使用hash算法。通过包名,类名,继承关系,各种方法和属性等用哈希算法生成一个值,只要对代码进行了更改,计算出来的哈希值就会不一样。

如果不显式地声明一个serialVersionUID,就会使用hash算法自动生成一个serialVersionUID

但是,自动生成的serialVersionUID对类的细节高度敏感。这会造成两个后果:

  1. 只要类修改了一点点,serialVersionUID就会发生变化

  2. 即使我们没有修改代码,但是这些细节仍然可能因为编译器的不同而发生变化,从而使serialVersionUID发生变化,这会导致反序列化时出现问题

    (即出现InvalidClassException异常)

大多数时候我们不希望serialVersionUID对细节如此敏感,因此Java编译器会强烈建议我们显式地声明serialVersionUID,从而会弹出一开始的Warning

参考资料

书籍

Java核心技术 卷Ⅰ 基础知识(原书第11版) 第7章

网站

关于Throwable

关于serialVersionUID

依旧关于serialVersionUID

关于Serializable接口

关于序列化

依旧关于序列化

关于eclipse报的Warning

关于继承