某些java类为什么要实现Serializablejava 泛型接口 实现类

6339人阅读
java基础(1)
java对象实现Serializable接口
在还没有深入了解serializable接口之前,像很多程序员一样,以为一个对象实现serializable接口就被序列化了。
最近在接触ehcache缓存的时候,将对象缓存起来,该对象需要先实现Serializable接口,然而,我们会发现对象并没有真正的被序列化。
下面让我们一起来总结一下Serializable接口的实现原理。
当一个类实现了Seializable接口(该接口仅为标记接口,不包含任何方法定义),表示该类可以序列化,序列化的目的是将一个实现了Serializable接口的对象可以转换成一个字节序列,保存对象的状态。
把该字节序列保存起来(例如:保存在一个文件里),以后可以随时将该字节序列恢复为原来的对象。甚至可以将该字节序列放到其他计算机上或者通过网络传输到其他计算机上恢复,只有该计算机平台存在相应的类就可以正常恢复为原来的对象。
一个对象实现Serializable接口序列化,先要创建某些OutputStream对象,然后将其封装在一个ObjectOutputStream对象内,再调用writeObject()方法,即可序列化一个对象,反序列化,InputStream,再调用readObject()方法。(writeObject和readObject本身就是线程安全的,传输过程中是不允许被并发访问的,所以对象只能一个一个接连不断的传过来)。
如果某个类能够被序列化,其子类也可以被序列化。声明为static和transient类型的成员数据不能被序列化。因为static代表类的状态,transient代表对象的临时数据,static对象变量在反序列化时取得的值为当前jvm中对应类中对应static变量的值,而transient(瞬态)关键字则一般用于标识那些在序列化时不需要传递的状态变量。
package org.test.
import java.io.S
public class Person implements Serializable{
private static final long serialVersionUID = 1L;
protected S
public Person(){}
public Person(String name,int age){
this.name =
this.age =
public String getName() {
public void setName(String name) {
this.name =
public int getAge() {
public void setAge(int age) {
this.age =
public String toString()
return &this is person:&+&name:&+this.name+&——age:&+this.
package org.test.
import java.io.S
public class User extends Person{
private static final long serialVersionUID = 1L;
public User() {
public User(String name,String password,int age)
this.name =
this.password =
this.age =
public String getName() {
public void setName(String name) {
this.name =
public String getPassword() {
public void setPassword(String password) {
this.password =
public String toString()
return &this is user:&+&name:&+this.name+&——password:&+this.password+&——age:&+this.
SerializableTest类
package org.test.
import java.io.ByteArrayInputS
import java.io.ByteArrayOutputS
import java.io.IOE
import java.io.ObjectInputS
import java.io.ObjectOutputS
import org.test.domain.P
import org.test.domain.U
public class SerializableTest {
public static void main(String[] args) {
Person p1 = (Person)deSerialByte(serialByte(new User(&user&,&1234&,15)));
//Person p2 = (Person)deSerialByte(serialByte(new Person(&person&,10)));
System.out.println(&p1:&+p1.toString());
//System.out.println(&p2:&+p2.toString());
//序列化一个对象(可以存储到一个文件也可以存储到字节数组)这里存储到自己数组
public static byte[] serialByte(Object obj)
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputS
oos = new ObjectOutputStream(baos);
oos.writeObject(obj);
oos.close();
return baos.toByteArray();
} catch (IOException e) {
throw new RuntimeException(e.getMessage());
//反序列化一个对象
public static Object deSerialByte(byte[] by)
ObjectInputS
ois = new ObjectInputStream(new ByteArrayInputStream(by));
return ois.readObject();
} catch (Exception e) {
throw new RuntimeException(e.getMessage());
serialVersionUID作用:&
&&&&&& 序列化时为了保持版本的兼容性,即在版本升级时反序列化仍保持对象的唯一性。
有两种生成方式:
&&&&&& 一个是默认的1L,比如:private static final long serialVersionUID = 1L;
&&&&&& 一个是根据类名、接口名、成员方法及属性等来生成一个64位的哈希字段,比如:
&&&&&& private static final&& long&&&& serialVersionUID = xxxxL;
当你一个类实现了Serializable接口,如果没有定义serialVersionUID,Eclipse会提供这个提示功能告诉你去定义 。在Eclipse中点击类中warning的图标一下,Eclipse就会自动给定两种生成的方式。如果不想定义它,在Eclipse的设置中也
&&&&&& 可以把它关掉的,设置如下:&
&&&&&&& Window ==& Preferences ==& Java ==& Compiler ==& Error/Warnings ==&
&&&&&&& Potential programming problems&
&&&&&&& 将Serializable class without serialVersionUID的warning改成ignore即可。
如果你没有考虑到兼容性问题时,就把它关掉,不过有这个功能是好的,只要任何类别实现了Serializable这个接口的话,如果没有加入serialVersionUID,Eclipse都会给你warning提示,这个serialVersionUID为了让该类别Serializable向后兼容。
如果你的类Serialized存到硬盘上面后,可是后来你却更改了类别的field(增加或减少或改名),当你Deserialize时,就会出现Exception的,这样就会造成不兼容性的问题。
但当serialVersionUID相同时,它就会将不一样的field以type的预设值Deserialize,可避开不兼容性问题。
注意以下几点:
1、若继承的父类没有实现Serializable接口,但是又想让子类可序列化,子类实现Serializable接口,子类必须有可访问的无参构造方法,用于保存和恢复父类的public或protected或同包下的package字段的状态,否则在序列化或反序列化时会抛出RuntimeException异常,对于序列化后的子类,在进行反序列化时,理论上无法初始化父类中private(不可访问)对象变量的状态或值。
2、在对可序列化类中的属性进行序列化时,如果遇到不可序列化的对象变量,此时会针对不可序列化的类抛出NotSerializableException异常
3、对于可序列化的非数组类,强烈建议显示声明static型、long型、final型serialVersionUID字段用于标识当前序列化类的版本号,否则在跨操作系统、跨编译器之间进行序列化和反序列化时容易出现InvalidClassException异常
&&相关文章推荐
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:14111次
排名:千里之外
(1)(1)(2)(1)(1)(1)(2)(1)java(48)
一个对象序列化的接口,一个类只有实现了,它的对象才是可序列化的。因此如果要序列化某些类的对象,这些类就必须实现。而实际上,Serializable是一个空接口,没有什么具体内容,它的目的只是简单的标识一个类的对象可以被序列化。
a)比如说你的内存不够用了,那计算机就要将内存里面的一部分对象暂时的保存到硬盘中,等到要用的时候再读入到内存中,硬盘的那部分存储空间就是所谓的。在比如过你要将某个特定的对象保存到文件中,我隔几天在把它拿出来用,那么这时候就要实现;
b)在进行java的Socket编程的时候,你有时候可能要传输某一类的对象,那么也就要实现Serializable接口;最常见的你传输一个字符串,它是JDK里面的类,也实现了Serializable接口,所以可以在网络上传输。
c)如果要通过远程的方法调用(RMI)去调用一个远程对象的方法,如在计算机A中调用另一台计算机B的对象的方法,那么你需要通过JNDI服务获取计算机B目标对象的引用,将对象从B传送到A,就需要实现序列化接口。
&&相关文章推荐
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:113720次
积分:2719
积分:2719
排名:第13107名
原创:155篇
转载:63篇
评论:18条
(11)(16)(38)(1)(5)(12)(19)(11)(9)(15)(38)(17)(13)(11)(3)关于Java你可能不知道的10件事 - ImportNew
呃,你是不是写Java已经有些年头了?还依稀记得这些吧: 那些年,它还叫做Oak;那些年,OO还是个热门话题;那些年,C++同学们觉得Java是没有出路的;那些年,Applet还风头正劲……
但我打赌下面的这些事中至少有一半你还不知道。这周我们来聊聊这些会让你有些惊讶的Java内部的那些事儿吧。
1. 其实没有受检异常(checked exception)
是的!JVM才不知道这类事情,只有Java语言才会知道。
今天,大家都赞同受检异常是个设计失误,一个Java语言中的设计失误。正如 Bruce Eckel 中说的, Java之后的其它语言都没有再涉及受检异常了,甚至Java 8的新式流API(Streams API)都不再拥抱受检异常 (。)
想证明JVM不理会受检异常?试试下面的这段代码:
public class Test {
// 方法没有声明throws
public static void main(String[] args) {
doThrow(new SQLException());
static void doThrow(Exception e) {
Test.&RuntimeException& doThrow0(e);
@SuppressWarnings(&unchecked&)
static &E extends Exception&
void doThrow0(Exception e) throws E {
不仅可以编译通过,并且也抛出了SQLException,你甚至都不需要用上Lombok的。
更多细节,可以再看看,或Stack Overflow上的。
2. 可以有只是返回类型不同的重载方法
下面的代码不能编译,是吧?
class Test {
Object x() { return &abc&; }
String x() { return &123&; }
是的!Java语言不允许一个类里有2个方法是『重载一致』的,而不会关心这2个方法的throws子句或返回类型实际是不同的。
但是等一下!来看看方法的Javadoc:
注意,可能在一个类中会有多个匹配的方法,因为尽管Java语言禁止在一个类中多个方法签名相同只是返回类型不同,但是JVM并不禁止。 这让JVM可以更灵活地去实现各种语言特性。比如,可以用桥方法(bridge method)来实现方法的协变返回类型;桥方法和被重载的方法可以有相同的方法签名,但返回类型不同。
嗯,这个说的通。实际上,当写了下面的代码时,就发生了这样的情况:
abstract class Parent&T& {
abstract T x();
class Child extends Parent&String& {
String x() { return &abc&; }
查看一下Child类所生成的字节码:
// Method descriptor #15 ()Ljava/lang/S
// Stack: 1, Locals: 1
java.lang.String x();
ldc &String &abc&& [16]
Line numbers:
[pc: 0, line: 7]
Local variable table:
[pc: 0, pc: 3] local: this index: 0 type: Child
// Method descriptor #18 ()Ljava/lang/O
// Stack: 1, Locals: 1
bridge synthetic java.lang.Object x();
aload_0 [this]
invokevirtual Child.x() : java.lang.String [19]
Line numbers:
[pc: 0, line: 1]
在字节码中,T实际上就是Object类型。这很好理解。
合成的桥方法实际上是由编译器生成的,因为在一些调用场景下,Parent.x()方法签名的返回类型期望是Object。 添加泛型而不生成这个桥方法,不可能做到二进制兼容。 所以,让JVM允许这个特性,可以愉快解决这个问题(实际上可以允许协变重载的方法包含有副作用的逻辑)。 聪明不?呵呵~
你是不是想要扎入语言规范和内核看看?可以在找到更多有意思的细节。
3. 所有这些写法都是二维数组!
class Test {
int[][] a()
{ return new int[0][]; }
int[] b() [] { return new int[0][]; }
int c() [][] { return new int[0][]; }
是的,这是真的。尽管你的人肉解析器不能马上理解上面这些方法的返回类型,但都是一样的!下面的代码也类似:
class Test {
int[][] a = {{}};
int[] b[] = {{}};
int c[][] = {{}};
是不是觉得这个很2B?想象一下在上面的代码中使用。 语法糖的数目要爆炸了吧!
@Target(ElementType.TYPE_USE)
@interface Crazy {}
class Test {
@Crazy int[][]
a1 = {{}};
int @Crazy [][] a2 = {{}};
int[] @Crazy [] a3 = {{}};
@Crazy int[] b1[]
int @Crazy [] b2[] = {{}};
int[] b3 @Crazy [] = {{}};
@Crazy int c1[][]
int c2 @Crazy [][] = {{}};
int c3[] @Crazy [] = {{}};
类型注解。这个设计引入的诡异在程度上仅仅被它解决问题的能力超过。
或换句话说:
在我4周休假前的最后一个提交里,我写了这样的代码,然后。。。
【译注:然后,亲爱的同事你,就有得火救啦,哼,哼哼,哦哈哈哈哈~】
请找出上面用法合适的使用场景,还是留给你作为一个练习吧。
4. 你没有掌握条件表达式
呃,你认为自己知道什么时候该使用条件表达式?面对现实吧,你还不知道。大部分人会下面的2个代码段是等价的:
Object o1 = true ? new Integer(1) : new Double(2.0);
Object o2;
o2 = new Integer(1);
o2 = new Double(2.0);
让你失望了。来做个简单的测试吧:
System.out.println(o1);
System.out.println(o2);
打印结果是:
哦!如果『需要』,条件运算符会做数值类型的类型提升,这个『需要』有非常非常非常强的引号。因为,你觉得下面的程序会抛出NullPointerException吗?
Integer i = new Integer(1);
if (i.equals(1))
Double d = new Double(2.0);
Object o = true ? i : // NullPointerException!
System.out.println(o);
关于这一条的更多的信息可以在找到。
5. 你没有掌握复合赋值运算符
是不是觉得不服?来看看下面的2行代码:
直觉上认为,2行代码是等价的,对吧?但结果即不是!JLS(Java语言规范)指出:
复合赋值运算符表达式 E1 op= E2 等价于 E1 = (T)((E1) op (E2)) 其中T是E1的类型,但E1只会被求值一次。
这个做法太漂亮了,请允许我引用在Stack Overflow上的:
使用*=或/=作为例子可以方便说明其中的转型问题:
byte b = 10;
System.out.println(b); // prints 57
byte b = 100;
System.out.println(b); // prints 40
char ch = '0';
ch *= 1.1;
System.out.println(ch); // prints '4'
char ch = 'A';
ch *= 1.5;
System.out.println(ch); // prints 'a'
为什么这个真是太有用了?如果我要在代码中,就地对字符做转型和乘法。然后,你懂的……
6. 随机Integer
这条其实是一个迷题,先不要看解答。看看你能不能自己找出解法。运行下面的代码:
for (int i = 0; i & 10; i++) {
System.out.println((Integer) i);
…… 然后要得到类似下面的输出(每次输出是随机结果):
这怎么可能?!
. 我要剧透了…… 解答走起……
好吧,解答在这里(), 和用反射覆盖JDK的Integer缓存,然后使用自动打包解包(auto-boxing/auto-unboxing)有关。 同学们请勿模仿!或换句话说,想想会有这样的状况,再说一次:
在我4周休假前的最后一个提交里,我写了这样的代码,然后。。。
【译注:然后,亲爱的同事你,就有得火救啦,哼,哼哼,哦哈哈哈哈~】
这条是我的最爱。Java是有GOTO的!打上这行代码:
int goto = 1;
Test.java:44: error: &identifier& expected
int goto = 1;
这是因为goto是个,保留了为以后可以用……
但这不是我要说的让你兴奋的内容。让你兴奋的是,你是可以用break、continue和有标签的代码块来实现goto的:
// do stuff
if (check)
// do more stuff
对应的字节码是:
iload_1 [check]
label: do {
// do stuff
if (check)
// do more stuff
} while(true);
对应的字节码是:
iload_1 [check]
8. Java是有类型别名的
在别的语言中(比如,), 可以方便地定义类型别名:
interface People =& Set&Person&;
这样定义的People可以和Set&Person&互换地使用:
Set&Person&? p2 = p1;
在Java中不能在顶级(top level)定义类型别名。但可以在类级别、或方法级别定义。 如果对Integer、Long这样名字不满意,想更短的名字:I和L。很简单:
class Test&I extends Integer& {
&L extends Long& void x(I i, L l) {
System.out.println(
i.intValue() + &, & +
l.longValue()
上面的代码中,在Test类级别中I是Integer的『别名』,在x方法级别,L是Long的『别名』。可以这样来调用这个方法:
new Test().x(1, 2L);
当然这个用法不严谨。在例子中,Integer、Long都是final类型,结果I和L 效果上是个别名 (大部分情况下是。赋值兼容性只是单向的)。如果用非final类型(比如,Object),还是要使用原来的泛型参数类型。
玩够了这些恶心的小把戏。现在要上干货了!
9. 有些类型的关系是不确定的
好,这条会很稀奇古怪,你先来杯咖啡,再集中精神来看。看看下面的2个类型:
// 一个辅助类。也可以直接使用List
interface Type&T& {}
class C implements Type&Type&? super C&& {}
class D&P& implements Type&Type&? super D&D&P&&&& {}
类型C和D是啥意思呢?
这2个类型声明中包含了递归,和的声明类似 (但有微妙的不同):
public abstract class Enum&E extends Enum&E&& { ... }
有了上面的类型声明,一个实际的enum实现只是语法糖:
// 这样的声明
enum MyEnum {}
// 实际只是下面写法的语法糖:
class MyEnum extends Enum&MyEnum& { ... }
记住上面的这点后,回到我们的2个类型声明上。下面的代码可以编译通过吗?
class Test {
Type&? super C& c = new C();
Type&? super D&Byte&& d = new D&Byte&();
很难的问题,回答过这个问题。答案实际上是不确定的:
C是Type&? super C&的子类吗?
步骤 0) C &?: Type&? super C&
步骤 1) Type&Type&? super C&& &?: Type (继承)
步骤 2) C (检查通配符 ? super C)
步骤 . . . (进入死循环)
D是Type&? super D&Byte&&的子类吗?
步骤 0) D&Byte& &?: Type&? super C&Byte&&
步骤 1) Type&Type&? super D&D&Byte&&&& &?: Type&? super D&Byte&&
步骤 2) D&Byte& &?: Type&? super D&D&Byte&&&
步骤 3) List&List&? super C&C&&& &?: List&? super C&C&&
步骤 4) D&D&Byte&& &?: Type&? super D&D&Byte&&&
步骤 . . . (进入永远的展开中)
试着在你的Eclipse中编译上面的代码,会Crash!(别担心,我已经提交了一个Bug。)
我们继续深挖下去……
在Java中有些类型的关系是不确定的!
如果你有兴趣知道更多古怪Java行为的细节,可以读一下Ross Tate的论文 (由Ross Tate、Alan Leung和Sorin Lerner合著),或者也可以看看我们在方面的思索。
10. 类型交集(Type intersections)
Java有个很古怪的特性叫类型交集。你可以声明一个(泛型)类型,这个类型是2个类型的交集。比如:
class Test&T extends Serializable & Cloneable& {
绑定到类Test的实例上的泛型类型参数T必须同时实现Serializable和Cloneable。比如,String不能做绑定,但Date可以:
// 编译不通过!
Test&String& s =
// 编译通过
Test&Date& d =
Java 8保留了这个特性,你可以转型成临时的类型交集。这有什么用? 几乎没有一点用,但如果你想强转一个lambda表达式成这样的一个类型,就没有其它的方法了。 假定你在方法上有了这个蛋疼的类型限制:
&T extends Runnable & Serializable& void execute(T t) {}
你想一个Runnable同时也是个Serializable,这样你可能在另外的地方执行它并通过网络发送它。lambda和序列化都有点古怪。
lambda是可以序列化的:
如果lambda表达式的目标类型和它捕获的参数(captured arguments)是可以序列化的,则这个lambda表达式是可序列化的。
但即使满足这个条件,lambda表达式并没有自动实现Serializable这个标记接口(marker interface)。 为了强制成为这个类型,就必须使用转型。但如果只转型成Serializable …
execute((Serializable) (() -& {}));
… 则这个lambda表达式不再是一个Runnable。
同时转型成2个类型:
execute((Runnable & Serializable) (() -& {}));
一般我只对SQL会说这样的话,但是时候用下面的话来结束这篇文章了:
Java中包含的诡异在程度上仅仅被它解决问题的能力超过。
原文链接:
- 译文链接: [ 转载请保留原文出处、译者和译文链接。]
关于作者:
花名哲良 PAAS平台@阿里云/分布式服务&服务治理框架dubbo。
新浪微博:
Hi,请到伯乐在线的小组发帖提问,支持微信登录。链接是: http://group.jobbole....
关于ImportNew
ImportNew 专注于 Java 技术分享。于日 11:11正式上线。是的,这是一个很特别的时刻 :)
ImportNew 由两个 Java 关键字 import 和 new 组成,意指:Java 开发者学习新知识的网站。 import 可认为是学习和吸收, new 则可认为是新知识、新技术圈子和新朋友……
新浪微博:
推荐微信号
反馈建议:@
广告与商务合作QQ:
– 好的话题、有启发的回复、值得信赖的圈子
– 写了文章?看干货?去头条!
– 为IT单身男女服务的征婚传播平台
– 优秀的工具资源导航
– 活跃 & 专业的翻译小组
– 国内外的精选博客文章
– UI,网页,交互和用户体验
– JavaScript, HTML5, CSS
– 专注Android技术分享
– 专注iOS技术分享
– 专注Java技术分享
– 专注Python技术分享
& 2017 ImportNewJava中实体实现Serializable接口的目的是什么,Serializable有什么作用? - 知乎15被浏览2638分享邀请回答1添加评论分享收藏感谢收起解释一下Java.io.Serializable接口
实现 java.io.Serializable 接口的类是可序列化的。没有实现此接口的类将不能使它们的任一状态被序列化或逆序列化。序列化类的所有子类本身都是可序列化的。这个序列化接口没有任何方法和域,仅用于标识序列化的语意。
通俗点说,所谓的Serializable,就是java提供的通用数据保存和读取的接口。至于从什么地方读出来和保存到哪里去都被隐藏在函数参数的背后了。这样子,任何类型只要实现了Serializable接口,就可以被保存到文件中,或者作为数据流通过网络发送到别的地方,也可以用管道来传输到系统的其他程序中,这样子极大的简化了类的设计。只要设计一个保存一个读取功能就能解决上面说得所有问题。
允许非序列化类的子类型序列化,子类型可以假定负责保存和恢复父类型的公有的、保护的和(如果可访问)包的域的状态。只要该类(扩展)有一个无参构造子,可初始化它的状态,那么子类型就可承担上述职责。在这种情况下申明一个可序列化的类是一个错误。此错误将在运行时被检测。
在逆序列化期间,将使用非序列化类的公用的或保护的无参构造子对它的域进行初始化。可序列化子类必须能访问父类的无参构造子,它的所有域将从相应流中被恢复。
当遍历一个图象时,很可能遇到一个不支持序列化接口的对象。此时将抛出 NotSerializableException 异常,且标识该非序列化对象类。
在序列化处理过程中需要特定句柄的类,必须使用如下这些恰当的标记实现特定的方法:
private void writeObject(java.io.ObjectOutputStream out)
throws IOException
private void readObject(java.io.ObjectInputStream in)
throws IOException, ClassNotFoundE
writeObject 方法负责写特定类的对象的状态,以至相应的 readObject 方法能恢复它。 通过调用 out.defaultWriteObject 可激活保存对象域的缺省机制,此方法不必关心状态是属于它的父类还是子类。 使用 writeObject 方法或基本类型支持的 DataOutput 方法将每个域的状态保存到 ObjectOutputStream 中。
readObject 方法负责从此流中读取并恢复类的域。它可能调用 in.defaultReadObject 方法来调用该对象的非静态和非暂时的域的缺省恢复机制。 defaultReadObject 方法使用该流中的信息,用当前对象中的已命名域分配保存在流中该对象的相应域。当类已改进增加了新域时,这个接口也做相应处理。此方法不必关心状态是属于它的父类还是子类。 使用 writeObject 方法或基本类型支持的 DataOutput 方法将每个域的状态保存到 ObjectOutputStream 中。
原文链接:/news/1762.html
责任编辑:
声明:本文由入驻搜狐号的作者撰写,除搜狐官方账号外,观点仅代表作者本人,不代表搜狐立场。
首推零首付,先就业,后付款。培训4个月,升职ceo,迎娶白富美!
教育部授予:“软件工程专业大学生实习实训基地 ”
今日搜狐热点

我要回帖

更多关于 java 接口的实现类 的文章

 

随机推荐