Java学习笔记(十七)——java序列化
【前面的话】
做项目总是要用到很多东西,遇到一个新的知识,并不是这个知识出来的时间短,而是对于自己来说是新的,所以就需要自己去学习,希望今后可以提高学习的效率。
这篇文章是关于Java 序列化的,选择性阅读。
【知识点】
一、什么叫序列化?
我们都知道对象是暂时保存在内存中的,不能用U盘考走了,有时为了使用介质转移对象,并且把对象的状态保持下来,就需要把对象保存下来,这个过程就叫做序列化,通俗点,就是把人的魂(对象)收伏成一个石子(可传输的介质)。
二、什么叫反序列化?
就是再把介质中的东西还原成对象,把石子还原成人的过程。
三、可能的使用情况
1. 当你想把的内存中的对象写入到硬盘的时候;
比如说你的内存不够用了,那计算机就要将内存里面的一部分对象暂时的保存到硬盘中,等到要用的时候再读入到内存中,硬盘的那部分存储空间就是所谓的虚拟内存。在比如过你要将某个特定的对象保存到文件中,我隔几天在把它拿出来用,那么这时候就要实现Serializable接口;
2. 当你想用套接字在网络上传送对象的时候;
在进行java的Socket编程的时候,你有时候可能要传输某一类的对象,那么也就要实现Serializable接口;最常见的你传输一个字符串,它是JDK里面的类,也实现了Serializable接口,所以可以在网络上传输。
3. 当你想通过RMI传输对象的时候;
如果要通过远程的方法调用(RMI)去调用一个远程对象的方法,如在计算机A中调用另一台计算机B的对象的方法,那么你需要通过JNDI服务获取计算机B目标对象的引用,将对象从B传送到A,就需要实现序列化接口。
四、Serializable接口
- Serializable接口:一个对象序列化的接口,一个类只有实现了Serializable接口,它的对象才是可序列化的。因此如果要序列化某些类的对象,这些类就必须实现Serializable接口。
- Serializable实现代码:
1 public interface Serializable { 2 }
可以看出Serializable接口是一个空的接口,目的只有一个就是表示一个类的对象可以被序列化。这个标签是类可以被序列化的特性,表示这个类可以被序列化。
五、Externalizable接口
- Externalizable接口:他是Serializable接口的子类
- Externalizable实现代码:
1 public interface Externalizable extends java.io.Serializable { 2 void writeExternal(ObjectOutput out) throws IOException; 3 void readExternal(ObjectInput in) throws IOException, ClassNotFoundException; 4 }
六、Serializable接口和Externalizable接口区别
- Serializable:一个对象想要被序列化,那么它的类就要实现此接口,这个对象的所有属性(包括private属性、包括其引用的对象)都可以被序列化和反序列化来保存、传递。
- Externalizable:他是Serializable接口的子类,有时我们不希望序列化那么多,可以使用这个接口,这个接口的writeExternal()和readExternal()方法可以指定序列化哪些属性;
【学习demo And 解释】
一、Java实现序列化
1. java.io.ObjectOutputStream代表对象输出流,它的writeObject(Object obj)方法可对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中。
2. java.io.ObjectInputStream代表对象输入流,它的readObject()方法从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并将其返回。
3. 对象序列化包括如下步骤:
1)创建一个对象输出流,它可以包装一个其他类型的目标输出流,如文件输出流;
2)通过对象输出流的writeObject()方法写对象。
4. 对象反序列化的步骤如下:
1)创建一个对象输入流,它可以包装一个其他类型的源输入流,如文件输入流;
2)通过对象输入流的readObject()方法读取对象。
5. 代码实现:
SerializableTest.java
1 import java.io.*; 2 import java.util.Date; 3 4 public class SerializableTest { 5 6 public static void main(String[] args) throws Exception { 7 ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("objectFile.obj")); 8 //序列化对象 9 Customer customer = new Customer("阿蜜果", 24); 10 out.writeObject("你好!"); 11 out.writeObject(new Date()); 12 out.writeObject(customer);//写入实现了序列化的对象 13 out.writeInt(123); //写入基本类型数据 14 out.close(); 15 //反序列化对象 16 ObjectInputStream in = new ObjectInputStream(new FileInputStream("objectFile.obj")); 17 System.out.println("obj1=" + (String) in.readObject()); 18 System.out.println("obj2=" + (Date) in.readObject()); 19 Customer obj3 = (Customer) in.readObject(); 20 System.out.println("obj3=" + obj3); 21 int obj4 = in.readInt(); 22 System.out.println("obj4=" + obj4); 23 in.close(); 24 } 25 } 26 class Customer implements Serializable { 27 private String name; 28 private int age; 29 public Customer(String name, int age) { 30 this.name = name; 31 this.age = age; 32 } 33 public String toString() { 34 return "name=" + name + ", age=" + age; 35 } 36 }
6. 运行结果:
1 obj1=你好! 2 obj2=Thu Apr 03 09:12:09 CST 2014 3 obj3=name=阿蜜果, age=24 4 obj4=123
二、Java实现序列化的时候使用关键字
1. transient是Java语言的关键字,用来表示一个域不是该对象序列化的一部分。当一个对象被序列化的时候,transient型变量的值不包括在序列化的表示中,然而非transient型的变量是被包括进去的。
2. demo代码
其中Password定义为transient型,在输出的时候,就会不被序列化。输出null。
SerializableTest.java
1 import java.io.*; 2 3 public class SerializableTest { 4 public static void main(String args[]) { 5 testObjectSeri(); 6 testObjectInSeri(); 7 } 8 /** 9 * 对象序列化测试 10 */ 11 public static void testObjectSeri() { 12 Person person = new Person("熔岩", "341022225562156", "lavasoft"); 13 FileOutputStream fos = null; 14 ObjectOutputStream oos = null; 15 try { 16 fos = new FileOutputStream("person.dat"); 17 oos = new ObjectOutputStream(fos); 18 oos.writeObject(person); 19 } catch (FileNotFoundException e) { 20 System.out.println("找不到指定的文件!"); 21 e.printStackTrace(); 22 } catch (IOException e) { 23 e.printStackTrace(); 24 } finally { 25 try { 26 oos.flush(); 27 oos.close(); 28 } catch (IOException e) { 29 e.printStackTrace(); 30 } 31 } 32 } 33 /** 34 * 对象反序列化测试 35 */ 36 public static void testObjectInSeri() { 37 FileInputStream fis = null; 38 ObjectInputStream ois = null; 39 Person person = null; 40 try { 41 fis = new FileInputStream("person.dat"); 42 ois = new ObjectInputStream(fis); 43 person = (Person) ois.readObject(); 44 } catch (FileNotFoundException e) { 45 e.printStackTrace(); 46 } catch (IOException e) { 47 e.printStackTrace(); 48 } catch (ClassNotFoundException e) { 49 e.printStackTrace(); 50 } finally { 51 try { 52 ois.close(); 53 } catch (IOException e) { 54 e.printStackTrace(); 55 } 56 } 57 System.out.println(person.toString()); 58 } 59 } 60 /** 61 * 测试序列化所用的类 62 */ 63 class Person implements Serializable { 64 private String username; 65 private String cardNumber; 66 private transient String password; 67 public Person(String username, String cardNumber, String password) { 68 this.username = username; 69 this.cardNumber = cardNumber; 70 this.password = password; 71 } 72 public String getUsername() { 73 return username; 74 } 75 public void setUsername(String username) { 76 this.username = username; 77 } 78 public String getCardNumber() { 79 return cardNumber; 80 } 81 public void setCardNumber(String cardNumber) { 82 this.cardNumber = cardNumber; 83 } 84 public String getPassword() { 85 return password; 86 } 87 public void setPassword(String password) { 88 this.password = password; 89 } 90 public String toString() { 91 StringBuffer sb = new StringBuffer(this.getClass().getName()); 92 sb.append("["); 93 sb.append("\n\t"); 94 sb.append("username=" + this.username); 95 sb.append("\n\t"); 96 sb.append("cardNumber=" + this.cardNumber); 97 sb.append("\n\t"); 98 sb.append("password=" + this.password); 99 sb.append("]"); 100 return sb.toString(); 101 } 102 }
4. 运行结果:
1 Person[ 2 username=熔岩 3 cardNumber=341022225562156 4 password=null]
【几个问题】
一、序列化版本serialVersionUID
1. serialVersionUID作用:
序列化时为了保持版本的兼容性,即在版本升级时反序列化仍保持对象的唯一性。
类的serialVersionUID的默认值完全依赖于Java编译器的实现,对于同一个类,用不同的Java编译器编译,有可能会导致不同的serialVersionUID,也有可能相同。为了提高serialVersionUID的独立性和确定性,强烈建议在一个可序列化类中显示的定义serialVersionUID,为它赋予明确的值。显式地定义serialVersionUID有两种用途:
1)在某些场合,希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有相同的serialVersionUID;
2)在某些场合,不希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有不同的serialVersionUID。
2. 有两种生成方式:
一个是默认的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接口;如果一个类中有引用类型的实例变量,这个引用类型也要实现Serializable接口。
如果不想让引用类实现Serializable接口,并且让本类成功序列化也可以,使用transient关键字。
【参考资料】
【后面的话】
随波逐流虽易,努力生活不易,且行且珍惜。
——TT