(2)对象序列化
对象序列化的目标是将对象保存到磁盘中,或允许在网络中直接传输对象。对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上,通过网络将这种二进制流传输到另一个网络节点。其他程序一旦获得了这种二进制流(无论是从磁盘中获取的,还是通过网络获取的),都可以将这种二进制流恢复成原来的Java对象。
序列化机制允许将实现序列化的Java对象转换成字节序列,这些字节序列可以保存在磁盘上,或通过网络传输,以备以后重新恢复成原来的对象。序列化机制使得对象可以脱离程序的运行而独立存在。对象的序列化(Serialize)指将一个Java对象写入IO流中,与此对应的是,对象的反序列化(Deserialize)则指从IO流中恢复该Java对象。
如果需要让某个对象支持序列化机制,则必须让它的类是可序列化的(继承serializable接口,该接口是一个标记接口,实现该接口无须实现任何方法,它只是表明该类的实例是可序列化的)
使用Serializable来实现序列化非常简单,主要让目标类实现Serializable标记接口即可,无须实现任何方法。
一旦某个类实现了Serializable接口,该类的对象就是可序列化的,程序可以通过如下两个步骤来序列化该对象。
创建一个ObjectOutputStream
调用ObjectOutputStream对象的writeObject()方法输出可序列化对象
注意:
1.如果修改类是仅修改了方法,则反序列化不受任何影响。不需要修改serialVersionUID的值
2.如果修改类时仅仅修改了静态变量或者transient实例变量,则反序列化不受任何影响。不需要修改serialVersionUID的值。
3.如果修改类时修改了非瞬时的实例变量,则可能导致序列化版本不兼容。如果对象流中的对象和新类中包含同名的实例变量,而实例变量类型不同,则反序列化失败,类定义应该更新serialVersionUID类变量的值。如果对象流中的对象比新类中包含更多的实例变量,则多出的实例变量值被忽略,序列化版本可以兼容,类定义可以不更新serialVersionUID的值。如果新类比对象流中的对象包含更多的实例变量,则序列化版本也可以兼容,类定义可以不更新serialVersionUID的值。但反序列化的对的新对象中多出的实例变量值都是null。
//继承Serializable可以被序列化
public class Student implements Serializable {
//序列化版本号
private static final Long serialVersionUID = 1L;
private String name;
//transient修饰的实例变量不能被序列化
transient private Integer age;
public Student(String name, Integer age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
/**
* 序列化student对象
*/
public class ObjectOutputStreamDemo {
public static void main(String[] args) {
Student student = new Student("张三",18);
try(OutputStream os = new FileOutputStream("D:\\IoDemo\\SerializableDemo.txt");
ObjectOutputStream oos = new ObjectOutputStream(os)){
//序列化student对象
oos.writeObject(student);
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
/**
* 反序列化
*/
public class ObjectInputStreamDemo {
public static void main(String[] args) {
try(InputStream is = new FileInputStream("D:\\IoDemo\\SerializableDemo.txt");
ObjectInputStream ois = new ObjectInputStream(is)){
//反序列化student对象
try {
Student student = (Student)ois.readObject();
System.out.println(student);
}catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}catch (IOException e) {
throw new RuntimeException(e);
}
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
创建对象的方法有几种?
- new关键字
- Clone
- 浅克隆:浅克隆是将原对象中实例变量在堆内存中的内容完全复制,如果时基本类型,则复制值,如果是引用数据类型则复制其中的引用。有可能会出现克隆对象和被克隆对象中的某个实例变量指向同一对象。
- 深克隆:在深克隆中,无论原型对象的成员变量是值类型还是引用类型,都将复制一份给克隆对象,深克隆将原型对象的所有引用对象也复制一份给克隆对象。
- 反序列化
- 反射
学习心得
今天主要学习了字符流的输入输出,以及序列化与反序列化的基础知识。需要特别注意字节转换流是字节转化为指定字符编码的字符,序列化部分则需要重点关注影响反序列化的几种情况。需要通过练习更好的掌握主要的知识点,在实践中积累使用经验。
课后练习
遍历任意盘,打印出该盘中所有的只读文件的名称。
/**
* 遍历D盘,打印出该盘中所有的只读文件的名称。
*/
public class PrintFilename {
public static void main(String[] args) {
String path = "D:\\";
File file = new File(path);
getPathFileName(file);
}
private static void getPathFileName(File file) {
if(file.isDirectory()){
File[] files = file.listFiles();
if(files!=null&&files.length>0){
for (File f:files) {
if(f.isDirectory()){
getPathFileName(f);
}else if(!f.canWrite()){
System.out.println(f.getAbsolutePath());
}
}
}
}else {
System.out.println(file.getAbsolutePath());
}
}
}
遍历任意盘,打印出该盘中所有的隐藏文件的名称。
/**
* 遍历D盘,打印出该盘中所有的隐藏文件的名称。
*/
public class PrintFileName2 {
public static void main(String[] args) {
String path = "D:\\";
File file = new File(path);
getPathFileName(file);
}
private static void getPathFileName(File file) {
if(file.isDirectory()){
File[] files = file.listFiles();
if(files!=null&&files.length>0){
for (File f:files) {
if(f.isDirectory()){
getPathFileName(f);
}else if(f.isHidden()){
System.out.println(f.getAbsolutePath());
}
}
}
}else {
System.out.println(file.getAbsolutePath());
}
}
}