序列化: 是指将Java对象转换为字节序列的过程
反序列化: 是将字节序列转换为Java对象的过程。
实现方式:只要对象实现了Serializable、Externalizable接口则该对象就实现了序列化。
下面举例说明:
首先是工具类:
public class MethodRef<T> {
private Field[] declaredFields;
private T objact;
public MethodRef(T objact) {
this.objact = objact;
this.declaredFields = objact.getClass().getDeclaredFields();
}
public List getMethodRefMapList(String fieldName, String mapName) {
try {
Map map = this.getMapReflect(mapName);
return (List) map.get(fieldName);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public Map getMapReflect(String fieldName) {
try {
for (Field field : this.declaredFields) {
//字段名称
String name = field.getName();
Class calazz = field.getType();
if (name.equals(fieldName)) {
//用于获取private成员变量
field.setAccessible(true);
System.out.println(name);
//字段值
return (Map) field.get(this.objact);
}
}
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
throw new RuntimeException("反射对象无指定参数[" + fieldName + "]");
}
public String getStringReflect(String fieldName) {
try {
for (Field field : this.declaredFields) {
//字段名称
String name = field.getName();
Class calazz = field.getType();
if (name.equals(fieldName)) {
//用于获取private成员变量
field.setAccessible(true);
System.out.println(name);
//字段值
return field.get(this.objact).toString();
}
}
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
throw new RuntimeException("反射对象无指定参数[" + fieldName + "]");
}
/* 关键步骤:将对象变成二进制数组。使用对象输出流将对象
* 写入ByteArrayOutputStream里再使用toByteArry()变成byte数组
* 然后存到数据库
*/
public static byte[] serialization(Object obj) {
byte[] std = new byte[0];
try (ByteArrayOutputStream byt = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(byt)) {
out.writeObject(obj);
std = byt.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}
return std;
}
/**
* 将取出的二进制数据反序列化成序列化的对象,依旧使用对象流处理
*
* @param std
* @return
*/
public static Object deserialization(byte[] std) {
Object obj = null;
try (ByteArrayInputStream byteInt = new ByteArrayInputStream(std);
ObjectInputStream objInt = new ObjectInputStream(byteInt)) {
obj = objInt.readObject();
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
return obj;
}
}
测试类:
public static void main(String[] args) throws IOException, ClassNotFoundException {
User user1 = new User(1L, "xm", 12L, "男");
TestDTO testDTO1 = new TestDTO("1", "2", "3", "4");
TestDTO testDTO2 = new TestDTO("3", "4", "5", "6");
Map<String, List> map = new HashMap<>();
List<TestDTO> testDTOList = new ArrayList<>();
testDTOList.add(testDTO1);
testDTOList.add(testDTO2);
map.put("list", testDTOList);
HeadLone headLone = new HeadLone<>();
headLone.setHead(user1);
headLone.setLines(map);
byte[] std = MethodRef.serialization(headLone);
Object o = MethodRef.deserialization(std);
//输出头数据
MethodRef methodRef = new MethodRef(headLone.getHead());
String name = methodRef.getStringReflect("name");
System.out.println("mane = " + name);
//输出行数据
MethodRef methodRef2 = new MethodRef(o);
List<TestDTO> methodRefMapList = methodRef2.getMethodRefMapList("list", "lines");
methodRefMapList.forEach(e -> System.out.println("TestDTO = " + e.toString()));
}
测试结果:
name
mane = xm
lines
TestDTO = TestDTO{item1='1', item2='2', item3='3', item4='4'}
TestDTO = TestDTO{item1='3', item2='4', item3='5', item4='6'}
这样就完成了序列化过程
简单的说一下,测试的序列化例子中就是将java对象通过ObjectOutputStream
转换成字节流,保存到ByteArrayOutputStream
对象中,然后转换成byte
数组使用,这样就由对象转换成字节数据了;反序列化就反过来,将byte
数组转化成ByteArrayInputStream
对象,再通过ObjectInputStream
对象转换成java对象;这里要记住一下特点(这篇文章说的比较好):
-
当一个父类实现序列化,子类就会自动实现序列化,不需要显式实现Serializable接口。
-
当一个对象的实例变量引用其他对象,序列化该对象时也把引用对象进行序列化。
-
并非所有的对象都可以进行序列化,比如:
安全方面的原因,比如一个对象拥有private,public等成员变量,对于一个要传输的对象,比如写到文件,或者进行RMI传输等等,在序列化进行传输的过程中,这个对象的private等域是不受保护的;
资源分配方面的原因,比如socket,thread类,如果可以序列化,进行传输或者保存,也无法对他们进行重新的资源分配,而且,也是没有必要这样实现。
-
声明为static和transient类型的成员变量不能被序列化。因为static代表类的状态,transient代表对象的临时数据。
-
序列化运行时会使用一个称为 serialVersionUID 的版本号,并与每个可序列化的类相关联,该序列号在反序列化过程中用于验证序列化对象的发送者和接收者是否为该对象加载了与序列化兼容的类。如果接收者加载的该对象的类的 serialVersionUID 与对应的发送者的类的版本号不同,则反序列化将会导致 InvalidClassException。可序列化类可以通过声明名为 “serialVersionUID” 的字段(该字段必须是静态 (static)、最终 (final) 的 long 型字段)显式声明其自己的 serialVersionUID。
如果序列化的类未显式的声明 serialVersionUID,则序列化运行时将基于该类的各个方面计算该类的默认 serialVersionUID 值,如“Java™ 对象序列化规范”中所述。不过,强烈建议 所有可序列化类都显式声明 serialVersionUID 值,原因是计算默认的 serialVersionUID 对类的详细信息具有较高的敏感性,根据编译器实现的不同可能千差万别,这样在反序列化过程中可能会导致意外的 InvalidClassException。因此,为保证 serialVersionUID 值跨不同 java 编译器实现的一致性,序列化类必须声明一个明确的 serialVersionUID 值。还强烈建议使用 private 修饰符显示声明 serialVersionUID(如果可能),原因是这种声明仅应用于直接声明类 – serialVersionUID 字段作为继承成员没有用处。数组类不能声明一个明确的 serialVersionUID,因此它们总是具有默认的计算值,但是数组类没有匹配 serialVersionUID 值的要求。
-
Java有很多基础类已经实现了serializable接口,比如String,Vector等。但是也有一些没有实现serializable接口的。
-
如果一个对象的成员变量是一个对象,那么这个对象的数据成员也会被保存!这是能用序列化解决深拷贝的重要原因。