大多数商业项目使用数据库或内存映射文件或只是普通文件, 来知足持久性要求, 只有不多的项目依赖于 Java 中的序列化过程。不管如何,这篇文章不是 Java 序列化教程或如何序列化在 Java 的对象, 但有关序列化机制和序列化 API 的面试问题, 这是值得去任何 Java 面试前先看看以避免让一些未知的内容惊到本身。java
对于那些不熟悉 Java 序列化的人, Java 序列化是用来经过将对象的状态存储到带有.ser扩展名的文件来序列化 Java 中的对象的过程, 而且能够经过这个文件恢复重建 Java对象状态, 这个逆过程称为 deserialization。程序员
序列化是把对象改为能够存到磁盘或经过网络发送到其余运行中的 Java 虚拟机的二进制格式的过程, 并能够经过反序列化恢复对象状态. Java 序列化API给开发人员提供了一个标准机制, 经过 java.io.Serializable 和 java.io.Externalizable 接口, ObjectInputStream 及ObjectOutputStream 处理对象序列化. Java 程序员可自由选择基于类结构的标准序列化或是他们自定义的二进制格式, 一般认为后者才是最佳实践, 由于序列化的二进制文件格式成为类输出 API的一部分, 可能破坏 Java 中私有和包可见的属性的封装.面试
让 Java 中的类能够序列化很简单. 你的 Java 类只须要实现 java.io.Serializable 接口, JVM 就会把 Object 对象按默认格式序列化. 让一个类是可序列化的须要有意为之. 类可序列会可能为是一个长期代价, 可能会所以而限制你修改或改变其实现. 当你经过实现添加接口来更改类的结构时, 添加或删除任何字段可能会破坏默认序列化, 这能够经过自定义二进制格式使不兼容的可能性最小化, 但仍须要大量的努力来确保向后兼容性。序列化如何限制你更改类的能力的一个示例是 SerialVersionUID。数据库
若是不显式声明 SerialVersionUID, 则 JVM 会根据类结构生成其结构, 该结构依赖于类实现接口和可能更改的其余几个因素。假设你新版本的类文件实现的另外一个接口, JVM 将生成一个不一样的 SerialVersionUID 的, 当你尝试加载旧版本的程序序列化的旧对象时, 你将得到无效类异常 InvalidClassException。网络
问题 1) Java 中的可序列化接口和可外部接口之间的区别是什么?多线程
这是 Java 序列化访谈中最常问的问题。下面是个人版本 Externalizable 给咱们提供 writeExternal() 和 readExternal() 方法, 这让咱们灵活地控制 Java 序列化机制, 而不是依赖于 Java 的默认序列化。正确实现 Externalizable 接口能够显著提升应用程序的性能。框架
问题 2) 可序列化的方法有多少?若是没有方法,那么可序列化接口的用途是什么?函数
可序列化 Serializalbe 接口存在于java.io包中,构成了 Java 序列化机制的核心。它没有任何方法, 在 Java 中也称为标记接口。当类实现 java.io.Serializable 接口时, 它将在 Java 中变得可序列化, 并指示编译器使用 Java 序列化机制序列化此对象。工具
问题 3) 什么是 serialVersionUID ?若是你不定义这个, 会发生什么?性能
我最喜欢的关于Java序列化的问题面试问题之一。serialVersionUID 是一个 private static final long 型 ID, 当它被印在对象上时, 它一般是对象的哈希码,你可使用 serialver 这个 JDK 工具来查看序列化对象的 serialVersionUID。SerialVerionUID 用于对象的版本控制。也能够在类文件中指定 serialVersionUID。不指定 serialVersionUID的后果是,当你添加或修改类中的任何字段时, 则已序列化类将没法恢复, 由于为新类和旧序列化对象生成的 serialVersionUID 将有所不一样。Java 序列化过程依赖于正确的序列化对象恢复状态的, ,并在序列化对象序列版本不匹配的状况下引起 java.io.InvalidClassException 无效类异常,了解有关 serialVersionUID 详细信息,请参阅这篇文章, 须要 FQ。
问题 4) 序列化时,你但愿某些成员不要序列化?你如何实现它?
另外一个常常被问到的序列化面试问题。这也是一些时候也问, 如什么是瞬态 trasient 变量, 瞬态和静态变量会不会获得序列化等,因此,若是你不但愿任何字段是对象的状态的一部分, 而后声明它静态或瞬态根据你的须要, 这样就不会是在 Java 序列化过程当中被包含在内。
问题 5) 若是类中的一个成员未实现可序列化接口, 会发生什么状况?
关于Java序列化过程的一个简单问题。若是尝试序列化实现可序列化的类的对象,但该对象包含对不可序列化类的引用,则在运行时将引起不可序列化异常 NotSerializableException, 这就是为何我始终将一个可序列化警报(在个人代码注释部分中), 代码注释最佳实践之一, 指示开发人员记住这一事实, 在可序列化类中添加新字段时要注意。
问题 6) 若是类是可序列化的, 但其超类不是, 则反序列化后从超级类继承的实例变量的状态如何?
Java 序列化过程仅在对象层次都是可序列化结构中继续, 即实现 Java 中的可序列化接口, 而且从超级类继承的实例变量的值将经过调用构造函数初始化, 在反序列化过程当中不可序列化的超级类。一旦构造函数连接将启动, 就不可能中止, 所以, 即便层次结构中较高的类实现可序列化接口, 也将执行构造函数。正如你从陈述中看到的, 这个序列化面试问题看起来很是棘手和有难度, 但若是你熟悉关键概念, 则并不难。
问题 7) 是否能够自定义序列化过程, 或者是否能够覆盖 Java 中的默认序列化过程?
答案是确定的, 你能够。咱们都知道,对于序列化一个对象需调用 ObjectOutputStream.writeObject(saveThisObject), 并用 ObjectInputStream.readObject() 读取对象, 但 Java 虚拟机为你提供的还有一件事, 是定义这两个方法。若是在类中定义这两种方法, 则 JVM 将调用这两种方法, 而不是应用默认序列化机制。你能够在此处经过执行任何类型的预处理或后处理任务来自定义对象序列化和反序列化的行为。
须要注意的重要一点是要声明这些方法为私有方法, 以免被继承、重写或重载。因为只有 Java 虚拟机能够调用类的私有方法, 你的类的完整性会获得保留, 而且 Java 序列化将正常工做。在我看来, 这是在任何 Java 序列化面试中能够问的最好问题之一, 一个很好的后续问题是, 为何要为你的对象提供自定义序列化表单?
问题 8) 假设新类的超级类实现可序列化接口, 如何避免新类被序列化?
在 Java 序列化中一个棘手的面试问题。若是类的 Super 类已经在 Java 中实现了可序列化接口, 那么它在 Java 中已经能够序列化, 由于你不能取消接口, 它不可能真正使它没法序列化类, 可是有一种方法能够避免新类序列化。为了不 Java 序列化,你须要在类中实现 writeObject() 和 readObject() 方法, 而且须要从该方法引起不序列化异常NotSerializableException。这是自定义 Java 序列化过程的另外一个好处, 如上述序列化面试问题中所述, 而且一般随着面试进度, 它做为后续问题提出。
问题 9) 在 Java 中的序列化和反序列化过程当中使用哪些方法?
这是很常见的面试问题, 在序列化基本上面试官试图知道: 你是否熟悉 readObject() 的用法、writeObject()、readExternal() 和 writeExternal()。Java 序列化由java.io.ObjectOutputStream类完成。该类是一个筛选器流, 它封装在较低级别的字节流中, 以处理序列化机制。要经过序列化机制存储任何对象, 咱们调用 ObjectOutputStream.writeObject(savethisobject), 并反序列化该对象, 咱们称之为 ObjectInputStream.readObject()方法。调用以 writeObject() 方法在 java 中触发序列化过程。关于 readObject() 方法, 须要注意的一点很重要一点是, 它用于从持久性读取字节, 并从这些字节建立对象, 并返回一个对象, 该对象须要类型强制转换为正确的类型。
问题 10) 假设你有一个类,它序列化并存储在持久性中, 而后修改了该类以添加新字段。若是对已序列化的对象进行反序列化, 会发生什么状况?
这取决于类是否具备其本身的 serialVersionUID。正如咱们从上面的问题知道, 若是咱们不提供 serialVersionUID, 则 Java 编译器将生成它, 一般它等于对象的哈希代码。经过添加任何新字段, 有可能为该类新版本生成的新 serialVersionUID 与已序列化的对象不一样, 在这种状况下, Java 序列化 API 将引起 java.io.InvalidClassException, 所以建议在代码中拥有本身的 serialVersionUID, 并确保在单个类中始终保持不变。
11) Java序列化机制中的兼容更改和不兼容更改是什么?
真正的挑战在于经过添加任何字段、方法或删除任何字段或方法来更改类结构, 方法是使用已序列化的对象。根据 Java 序列化规范, 添加任何字段或方法都面临兼容的更改和更改类层次结构或取消实现的可序列化接口, 有些接口在非兼容更改下。对于兼容和非兼容更改的完整列表, 我建议阅读 Java 序列化规范。
12) 咱们能够经过网络传输一个序列化的对象吗?
是的 ,你能够经过网络传输序列化对象, 由于 Java 序列化对象仍以字节的形式保留, 字节能够经过网络发送。你还能够将序列化对象存储在磁盘或数据库中做为 Blob。
13) 在 Java 序列化期间,哪些变量未序列化?
这个问题问得不一样, 但目的仍是同样的, Java开发人员是否知道静态和瞬态变量的细节。因为静态变量属于类, 而不是对象, 所以它们不是对象状态的一部分, 所以在 Java 序列化过程当中不会保存它们。因为 Java 序列化仅保留对象的状态,而不是对象自己。瞬态变量也不包含在 Java 序列化过程当中, 而且不是对象的序列化状态的一部分。在提出这个问题以后,面试官会询问后续内容, 若是你不存储这些变量的值, 那么一旦对这些对象进行反序列化并从新建立这些变量, 这些变量的价值是多少?这是大家要考虑的。
另外一个棘手的核心 Java 问题,wait 和 notify。它们是在有 synchronized 标记的方法或 synchronized 块中调用的,由于 wait 和 modify 须要监视对其上调用 wait 或 notify-get 的 Object。
大多数Java开发人员都知道对象类的 wait(),notify() 和 notifyAll()方法必须在Java中的 synchronized 方法或 synchronized 块中调用, 可是咱们想过多少次, 为何在 Java 中 wait, notify 和 notifyAll 来自 synchronized 块或方法?
最近这个问题在Java面试中被问到个人一位朋友,他思索了一下,并回答说: 若是咱们不从同步上下文中调用 wait() 或 notify() 方法,咱们将在 Java 中收到 IllegalMonitorStateException。
他的回答从实际效果上年是正确的,但面试官对这样的答案不会彻底满意,并但愿向他解释这个问题。面试结束后 他和我讨论了一样的问题,我认为他应该告诉面试官关于 Java 中 wait()和 notify()之间的竞态条件,若是咱们不在同步方法或块中调用它们就可能存在。
让咱们看看竞态条件如何在Java程序中发生。它也是流行的线程面试问题之一,并常常在电话和面对面的Java开发人员面试中出现。所以,若是你正在准备Java面试,那么你应该准备这样的问题,而且能够真正帮助你的一本书是《Java程序员面试公式书》的。这是一本罕见的书,涵盖了Java访谈的几乎全部重要主题,例如核心Java,多线程,IO 和 NIO 以及 Spring 和 Hibernate 等框架。你能够在这里查看。
为何要等待来自 Java中的 synchronized 方法的 wait方法为何必须从 Java 中的 synchronized 块或方法调用 ?咱们主要使用 wait(),notify() 或 notifyAll() 方法用于 Java 中的线程间通讯。一个线程在检查条件后正在等待,例如,在经典的生产者 - 消费者问题中,若是缓冲区已满,则生产者线程等待,而且消费者线程经过使用元素在缓冲区中建立空间后通知生产者线程。调用notify()或notifyAll()方法向单个或多个线程发出一个条件已更改的通知,而且一旦通知线程离开 synchronized 块,正在等待的全部线程开始获取正在等待的对象锁定,幸运的线程在从新获取锁以后从 wait() 方法返回并继续进行。
让咱们将整个操做分红几步,以查看Java中wait()和notify()方法之间的竞争条件的可能性,咱们将使用Produce Consumer 线程示例更好地理解方案:
Producer 线程测试条件(缓冲区是是否完整)并确认必须等待(找到缓冲区已满)。
Consumer 线程在使用缓冲区中的元素后设置条件。
Consumer 线程调用 notify() 方法; 这是不会被听到的,由于 Producer 线程尚未等待。
Producer 线程调用 wait() 方法并进入等待状态。
所以,因为竞态条件,咱们可能会丢失通知,若是咱们使用缓冲区或只使用一个元素,生产线程将永远等待,你的程序将挂起。“在java同步中等待 notify 和 notifyall 如今让咱们考虑如何解决这个潜在的竞态条件?
这个竞态条件经过使用 Java 提供的 synchronized 关键字和锁定来解决。为了调用 wait(),notify() 或 notifyAll(), 在Java中,咱们必须得到对咱们调用方法的对象的锁定。因为 Java 中的 wait() 方法在等待以前释放锁定并在从 wait() 返回以前从新获取锁定方法,咱们必须使用这个锁来确保检查条件(缓冲区是否已满)和设置条件(从缓冲区获取元素)是原子的,这能够经过在 Java 中使用 synchronized 方法或块来实现。
我不肯定这是不是面试官实际期待的,但这个我认为至少有意义,请纠正我若是我错了,请告诉咱们是否还有其余使人信服的理由调用 wait(),notify() 或 Java 中的 notifyAll() 方法。
总结一下,咱们用 Java 中的 synchronized 方法或 synchronized 块调用 Java 中的 wait(),notify() 或 notifyAll() 方法来避免:
1) Java 会抛出 IllegalMonitorStateException,若是咱们不调用来自同步上下文的wait(),notify()或者notifyAll()方法。
2) Javac 中 wait 和 notify 方法之间的任何潜在竞争条件。
不,你不能在Java中覆盖静态方法,但在子类中声明一个彻底相同的方法不是编译时错误,这称为隐藏在Java中的方法。
你不能覆盖Java中的静态方法,由于方法覆盖基于运行时的动态绑定,静态方法在编译时使用静态绑定进行绑定。虽然能够在子类中声明一个具备相同名称和方法签名的方法,看起来能够在Java中覆盖静态方法,但实际上这是方法隐藏。Java不会在运行时解析方法调用,而且根据用于调用静态方法的 Object 类型,将调用相应的方法。这意味着若是你使用父类的类型来调用静态方法,那么原始静态将从父类中调用,另外一方面若是你使用子类的类型来调用静态方法,则会调用来自子类的方法。简而言之,你没法在Java中覆盖静态方法。若是你使用像Eclipse或Netbeans这样的Java IDE,它们将显示警告静态方法应该使用类名而不是使用对象来调用,由于静态方法不能在Java中重写。
输出:
Static method from parent class
此输出确认你没法覆盖Java中的静态方法,而且静态方法基于类型信息而不是基于Object进行绑定。若是要覆盖静态mehtod,则会调用子类或 ColorScreen 中的方法。这一切都在讨论中咱们能够覆盖Java中的静态方法。咱们已经确认没有,咱们不能覆盖静态方法,咱们只能在Java中隐藏静态方法。建立具备相同名称和mehtod签名的静态方法称为Java隐藏方法。IDE将显示警告:"静态方法应该使用类名而不是使用对象来调用", 由于静态方法不能在Java中重写。