对象流
使用
DataInputStream
或者
DataOutputStream
可以读写对象数据,但是操作比较繁琐
SUN
提供了
ObjectInputStream/ObjectOutputStream
可以直接将
Object
写入或读出
针对
8
种简单类型及其包装类的操作方法,以及针对
String
类型的操作方法
1.readObject():Object
2.writeObject(Object):void
读写一个对象的前提是这个类型的对象是可以被序列化的;
NotSerializableException
对象序列化【简单来说就是将对象可以直接转换为二进制数据流】
/
对象的反序列化【可以将二进制数据流转换为对象】,这一般依靠JVM
实现,编程中只做声明对象序列化的目标是将对象保存到磁盘中,或允许在网络中直接传输对象。对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上,通过网络将这种二进制流传输到另一个网络节点,其他程序一旦获取到这种二进制流,都可以将这种二进制流恢复成原来的
Java
对象
1、如何声明对象所属于的类可以进行序列化和反序列化Serializable/Externalizable接口
其中的接口没有任何定义,仅仅只起到了说明的作用,这种接口叫做标志接口或者旗标接口
2
、可以通过
ObjectInputStream
【
readObject():Object
】和
ObjectOutputStream
【
writeObject(Object):void
】提供的方法直接操作对象
3
、输出对象
User user
=
new
User
();
user
.
setId
(
100L
);
user
.
setUsername
(
"zhangsan"
);
user
.
setPassword
(
"123456"
);
ObjectOutputStream oos
=
new
ObjectOutputStream
(
new
FileOutputStream
(
"users.data"
));
oos
.
writeObject
(
user
);
oos
.
close
();
4
、读取对象
ObjectInputStream ois
=
new
ObjectInputStream
(
new
FileInputStream
(
"users.data"
));
Object
temp
=
ois
.
readObject
();
if
(
temp
!=
null
&&
temp
instanceof
User
) {
User user
=
(
User
)
temp
;
System
.
out
.
println
(
user
);
}
ois
.
close
();
编码细节
1
、需要通过对象流读写的对象必须实现了序列化接口,否则
java.io.NotSerializableException
class
User
implements
Serializable
2
、
Serializable
接口是标志接口,没有需要实现的方法,所有的序列化和反序列化操作由
VM
负责实现。Externalizable接口定义为
public interface Externalizable extends java.io.Serializable
,这个接口中包含两个方法需要实现writeExternal
自定义实现对象的序列化,
readExternal
自定义实现对象的反序列化。除非特殊需求一般不使用Externalizable
接口,因为没有必要自定义
3
、类型转换问题:
Object
temp
=
ois
.
readObject
();
if
(
temp
!=
null
&&
temp
instanceof
User
) {
User user
=
(
User
)
temp
;
System
.
out
.
println
(
user
);
}
4
、
private static final long serialVersionUID = 6889840055394511246L
如果不添加序列号,则会有警告信息,但是不是错误信息
一般选择
Add generated serial version ID
会生成一个在项目中永不重复的的序列版本编号
序列版本号可以不用添加
,
这个序列版本号是一种序列化和反序列化中快速识别类型的简单方法,比不加序列号的识别效率高。引入的功能是如果版本号不对应,不会进行类型识别,而是直接报异常InvalidClassException
5
、一般针对敏感数据不应该进行序列化操作,针对不需要进行序列操作的属性可以添加一个关键字transient,表示该属性不参与序列化和反序列化操作
class
User
implements
Serializable
{
private transient
String
password
;
//transient
用于声明该属性不支持序列化操作
6
、读文件的判断:读取文件时可以通过
EOFException
异常来判断文件读取结束
已经向文件中写入数据后,继续追加存储,则读取数据会出现
StreamCorruptedException
ObjectOutputStream oos=new ObjectOutputStream(new BufferedOutputStream(new
FileOutputStream("users.data",true)));
序列化总结
Java
序列化就是将一个对象转化为一个二进制表示的字节数组,通过保存或则转移这些二进制数组达到持久化的目的。要实现序列化,需要实现java.io.Serializable
接口。反序列化是和序列化相反的过程,就是把二进制数组转化为对象的过程。在反序列化的时候,必须有原始类的模板才能将对象还原。
1.当父类实现了
Serializable
接口的时候,所有的子类都能序列化
2.子类实现了
Serializable
接口,父类没有,父类中的属性不能被序列化
(
不报错,但是数据会丢失
)
3.如果序列化的属性是对象,对象必须也能序列化,否则会报错
4.反序列化的时候,如果对象的属性有修改或则删减,修改的部分属性会丢失,但是不会报错
5.在反序列化的时候
serialVersionUID
被修改的话,会反序列化失败
6.在存
Java
环境下使用
Java
的序列化机制会支持的很好,但是在多语言环境下需要考虑别的序列化机制,比如xml
、
json
或
protobuf
等
serialVersionUID
值是用于确保类序列化与反序列化的兼容性问题的,如果序列化和反序列化过程中这两个值不一样,那么将导致序列化失败
可以看到编译器推荐两种方式,一种是生成默认的
versionID
,这个值为
1L
,还有一种方式是根据类名、接口名、成员方法及属性等来生成一个 64
位的哈希字段,只要类名、方法名、变量有修改或者有空格、注释、换行等操作,计算出来的哈希字段都会不同,当然这里需要注意,每次有以上的操作的时候尽量都要重新生成一次serialVerionUID
,编译器并不会自动修改
1.Java
序列化只是针对对象的属性的传递,至于方法和序列化过程无关
2.当一个父类实现了序列化,那么子类会自动实现序列化,不需要显示实现序列化接口,反过来,子类实现序列化,而父类没有实现序列化则序列化会失败---
即序列化具有传递性
3.当一个对象的实例变量引用了其他对象,序列化这个对象的时候会自动把引用的对象也进行序列化(实现深度克隆)
4.当某个字段被申明为
transient
后,默认的序列化机制会忽略这个字段
5.被申明为
transient
的字段,如果需要序列化,可以添加两个私有方法
writeObject
和
readObject
或
者实现
Externalizable
接口
对象克隆
在
Java
中所有的类都是缺省的继承自
Java
语言包中的
Object
类的,查看它的源码
克隆的对象可能包含一些已经修改过的属性,而
new
出来的对象的属性都还是初始化时候的值,所以当需要一个新的对象来保存当前对象的状态就靠clone
方法。那么把这个对象的临时属性一个一个的赋值给新new
的对象不也行嘛?可以是可以,但是一来麻烦不说,通过上面的源码都发现了
clone
是一个
native方法,在底层实现的。
常见的
Object a=new Object();Object b;b=a;
这种形式的代码复制的是引用,即对象在内存中的地址,
a和b
对象仍然指向了同一个对象。而通过
clone
方法赋值的对象跟原来的对象时同时独立存在的。
两种不同的克隆方法,浅克隆
ShallowClone
和深克隆
DeepClone
。浅克隆和深克隆的主要区别在于是否支持引用类型的成员变量的复制
浅克隆
1.
被复制的类需要实现
Clonenable
接口(不实现的话在调用
clone
方法会抛出 CloneNotSupportedException异常
)
, 该接口为标记接口,不含任何方法
2.
覆盖
clone()
方法,访问修饰符设为
public
。方法中调用
super.clone()
方法得到需要的复制对象。
native
为本地方法
public class
Student
implements
Cloneable
{
private
int
number
;
public
int
getNumber
() {
return
number
;
}
public
void
setNumber
(
int
number
) {
this
.
number
=
number
;
}
@Override
public
Object
clone
() {
Student stu
=
null
;
try
{
stu
=
(
Student
)
super
.
clone
();
}
catch
(
CloneNotSupportedException e
) {
e
.
printStackTrace
();
}
return
stu
;
}
}
调用
Student stu1
=
new
Student
();
stu1
.
setNumber
(
12345
);
Student stu2
=
(
Student
)
stu1
.
clone
();
在浅克隆中,如果原型对象的成员变量是值类型,将复制一份给克隆对象;如果原型对象的成员变量是引用类型,则将引用对象的地址复制一份给克隆对象,也就是说原型对象和克隆对象的成员变量指向相同的内存地址。
简单来说,在浅克隆中,当对象被复制时只复制它本身和其中包含的值类型的成员变量,而引用类型的成员对象并没有复制。
深克隆
在深克隆中,无论原型对象的成员变量是值类型还是引用类型,都将复制一份给克隆对象,深克隆将原型对象的所有引用对象也复制一份给克隆对象。
简单来说,在深克隆中,除了对象本身被复制外,对象所包含的所有成员变量也将复制。
在
Java
语言中,如果需要实现深克隆,可以通过覆盖
Object
类的
clone()
方法实现,也可以通过序列化Serialization等方式来实现。一般使用序列化的方式实现
序列化就是将对象写到流的过程,写到流中的对象是原有对象的一个拷贝,而原对象仍然存在于内存中。通过序列化实现的拷贝不仅可以复制对象本身,而且可以复制其引用的成员对象,因此通过序列化 将对象写到一个流中,再从流里将其读出来,可以实现深克隆。需要注意的是能够实现序列化的对象其类必须实现Serializable
接口,否则无法实现序列化操作。
原型模式
原型模式
Prototype Pattern
是用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式
意图:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
优点:
1
、性能提高。
2
、逃避构造函数的约束。
缺点:
1
、配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定很容易,特别当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。
2
、必须实现
Cloneable
接口。
注意:与通过对一个类进行实例化来构造新对象不同的是,原型模式是通过拷贝一个现有对象生成新对象的。浅拷贝实现 Cloneable
,重写,深拷贝是通过实现
Serializable
读取二进制流。
抽象类定义
public abstract class Shape implements Cloneable {
private
String
id
;
protected
String
type
;
abstract
void
draw
();
public
String
getType
(){
return
type
;
}
public
String
getId
() {
return
id
;
}
public
void
setId
(
String
id
) {
this
.
id
=
id
;
}
public
Object
clone
() {
Object
clone
=
null
;
try
{
clone
=
super
.
clone
();
}
catch
(
CloneNotSupportedException e
) {
e
.
printStackTrace
();
}
return
clone
;
}
}
扩展抽象类的实体类
public class
Rectangle
extends
Shape
{
public
Rectangle
(){
type
=
"Rectangle"
;
}
@Override
public
void
draw
() {
System
.
out
.
println
(
"Inside Rectangle::draw() method."
);
}
}
创建一个类获取实体类,并把它们存储在一个
Hashtable
中
public class
ShapeCache
{
private static
Hashtable
<
String
,
Shape
>
shapeMap
=
new
Hashtable
<
String
,
Shape
>
();
public static
Shape getShape
(
String
shapeId
) {
Shape cachedShape
=
shapeMap
.
get
(
shapeId
);
return
(
Shape
)
cachedShape
.
clone
();
}
//
对每种形状都运行操作创建该形状
shapeMap.put(shapeKey, shape);
public static
void
loadCache
() {
Circle circle
=
new
Circle
();
circle
.
setId
(
"1"
);
shapeMap
.
put
(
circle
.
getId
(),
circle
);
}
}
使用
ShapeCache
类来获取存储在
Hashtable
中的形状的克隆
public class
PrototypePatternDemo
{
public static
void
main
(
String
[]
args
) {
ShapeCache
.
loadCache
();
Shape clonedShape
=
(
Shape
)
ShapeCache
.
getShape
(
"1"
);
System
.
out
.
println
(
"Shape : "
+
clonedShape
.
getType
());
}
}