在工作中,会经常涉及到对象的拷贝和序列化,看似平常普通的知识点,其实还是有很多需要我们去学习、研究、注意的,对日后学习很多技术都有很大的帮助,今天就整理一下有关对象拷贝和序列化的知识。
对象的复制有三种方式:引用复制,clone复制,序列化复制
1. 引用复制
新建一个实体类如下:
- public class Customer {
-
- private String userName;
-
- private String password;
-
- public String getUserName() {
- return userName;
- }
-
- public void setUserName(String userName) {
- this.userName = userName;
- }
-
- public String getPassword() {
- return password;
- }
-
- public void setPassword(String password) {
- this.password = password;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
- Customer customer = (Customer) o;
- return Objects.equals(userName, customer.userName) &&
- Objects.equals(password, customer.password);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(userName, password);
- }
-
- @Override
- public String toString() {
- return "Customer{" +
- "userName='" + userName + '\'' +
- ", password='" + password + '\'' +
- '}';
- }
- }
测试引用复制
- public static void main(String[] args) {
-
- Customer c1 = new Customer();
- c1.setUserName("张三");
- c1.setPassword("123456");
- Customer c2 = c1;
-
- System.out.println(c1.equals(c2));
- System.out.println(c1 == c2);
- System.out.println("c1 = " + c1);
- System.out.println("c2 = " + c2);
-
- }
-
-
-
- D:\java\jdk1.8\bin\java.exe ...
- true
- true
- c1 = Customer{userName='张三', password='123456'}
- c2 = Customer{userName='张三', password='123456'}
-
- Process finished with exit code 0
引用复制 由于是两个对象的引用是相同,共同指向堆中的同一个对象,所以其中一个对象修改属性,会影响另一个对象的属性
- public static void main(String[] args) {
-
- Customer c1 = new Customer();
- c1.setUserName("张三");
- c1.setPassword("123456");
- Customer c2 = c1;
- c2.setPassword("654321");
- System.out.println(c1.equals(c2));
- System.out.println(c1 == c2);
- System.out.println("c1 = " + c1);
- System.out.println("c2 = " + c2);
-
- }
-
-
-
- D:\java\jdk1.8\bin\java.exe ...
- true
- true
- c1 = Customer{userName='张三', password='654321'}
- c2 = Customer{userName='张三', password='654321'}
-
- Process finished with exit code 0
所以使用引用复制对象时需要注意这一点.
2. clone拷贝复制
clone拷贝复制分为浅拷贝和深拷贝
2.1 浅拷贝
新建实体类实现cloneable接口,重写clone方法
- public class Customer implements Cloneable {
-
- private String userName;
-
- private String password;
-
- public String getUserName() {
- return userName;
- }
-
- public void setUserName(String userName) {
- this.userName = userName;
- }
-
- public String getPassword() {
- return password;
- }
-
- public void setPassword(String password) {
- this.password = password;
- }
-
- @Override
- protected Object clone() throws CloneNotSupportedException {
- return super.clone();
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
- Customer customer = (Customer) o;
- return Objects.equals(userName, customer.userName) &&
- Objects.equals(password, customer.password);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(userName, password);
- }
-
- @Override
- public String toString() {
- return "Customer{" +
- "userName='" + userName + '\'' +
- ", password='" + password + '\'' +
- '}';
- }
- }
测试浅拷贝
- public static void main(String[] args) throws CloneNotSupportedException {
-
- Customer c1 = new Customer();
- c1.setUserName("张三");
- c1.setPassword("123456");
- Customer c2 = (Customer)c1.clone();
- System.out.println(c1.equals(c2));
- System.out.println(c1 == c2);
- System.out.println("c1 = " + c1);
- System.out.println("c2 = " + c2);
-
- }
-
-
- D:\java\jdk1.8\bin\java.exe ...
- true
- false
- c1 = Customer{userName='张三', password='123456'}
- c2 = Customer{userName='张三', password='123456'}
-
- Process finished with exit code 0
从面的代码可以看出浅拷贝是重新生成了一个对象,两个对象的引用并不相同,这一点和引用复制不同,我们再试一下浅拷贝是否存在和引用复制一样的问题
- public static void main(String[] args) throws CloneNotSupportedException {
-
- Customer c1 = new Customer();
- c1.setUserName("张三");
- c1.setPassword("123456");
- Customer c2 = (Customer)c1.clone();
- c2.setPassword("654321");
- System.out.println(c1.equals(c2));
- System.out.println(c1 == c2);
- System.out.println("c1 = " + c1);
- System.out.println("c2 = " + c2);
-
- }
-
-
-
- D:\java\jdk1.8\bin\java.exe ...
- false
- false
- c1 = Customer{userName='张三', password='123456'}
- c2 = Customer{userName='张三', password='654321'}
-
- Process finished with exit code 0
这里我们可以看出浅拷贝不会出现和引用复制一样的问题,因为来个对象指向的引用并不相同.
但是浅拷贝也一样存在他自己的问题.
我们重新新建一个实体类,在实体类中加入引用其他类的成员变量
- public class Customer implements Cloneable {
-
- private String userName;
-
- private String password;
-
- private User user;
-
- public String getUserName() {
- return userName;
- }
-
- public void setUserName(String userName) {
- this.userName = userName;
- }
-
- public String getPassword() {
- return password;
- }
-
- public void setPassword(String password) {
- this.password = password;
- }
-
- @Override
- protected Object clone() throws CloneNotSupportedException {
- return super.clone();
- }
-
- public User getUser() {
- return user;
- }
-
- public void setUser(User user) {
- this.user = user;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
- Customer customer = (Customer) o;
- return Objects.equals(userName, customer.userName) &&
- Objects.equals(password, customer.password) &&
- Objects.equals(user, customer.user);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(userName, password, user);
- }
-
- @Override
- public String toString() {
- return "Customer{" +
- "userName='" + userName + '\'' +
- ", password='" + password + '\'' +
- ", user=" + user +
- '}';
- }
- }
-
-
-
-
- public class User {
-
- private int age;
-
- private int sex;
-
- public int getAge() {
- return age;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
- User user = (User) o;
- return age == user.age &&
- sex == user.sex;
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(age, sex);
- }
-
- public void setAge(int age) {
- this.age = age;
- }
-
- public int getSex() {
- return sex;
- }
-
- public void setSex(int sex) {
- this.sex = sex;
- }
-
- @Override
- public String toString() {
- return "User{" +
- "age=" + age +
- ", sex=" + sex +
- '}';
- }
- }
然后对实体类进行浅拷贝
- public static void main(String[] args) throws CloneNotSupportedException {
-
- Customer c1 = new Customer();
- c1.setUserName("张三");
- c1.setPassword("123456");
- User user = new User();
- user.setAge(18);
- user.setSex(0);
- c1.setUser(user);
- Customer c2 = (Customer)c1.clone();
- System.out.println(c1.equals(c2));
- System.out.println(c1 == c2);
- System.out.println("c1 = " + c1);
- System.out.println("c2 = " + c2);
- user.setSex(1);
- user.setAge(16);
- System.out.println("c1_1 = " + c1);
- System.out.println("c2_1 = " + c2);
- System.out.println(c1.getUser() == c2.getUser());
- System.out.println(c1.getUser().equals(c2.getUser()));
- }
-
-
-
-
- D:\java\jdk1.8\bin\java.exe ...
- true
- false
- c1 = Customer{userName='张三', password='123456', user=User{age=18, sex=0}}
- c2 = Customer{userName='张三', password='123456', user=User{age=18, sex=0}}
- c1_1 = Customer{userName='张三', password='123456', user=User{age=16, sex=1}}
- c2_1 = Customer{userName='张三', password='123456', user=User{age=16, sex=1}}
- true
- true
-
- Process finished with exit code 0
我们发现clone方法之后引用类型的成员变量并没有被拷贝出一个新的对象,而是直接被引用过来的,修改任何一个对象的引用变量,另一个对象的引用变量都会改变,这里是秉着不对引用变量的对象强加意义和不改变其设计原则的原则,所以并没有对引用的对象进行clone,这里如果需要对引用变量进行clone,也就是深拷贝就需要将引用变量的对象得类也实现cloneable接口,并重写clone方法,这样就会在clone时将引用对象也一起拷贝.
2.2 深拷贝
代码如下:
- public class Customer implements Cloneable {
-
- private String userName;
-
- private String password;
-
- private User user;
-
- public String getUserName() {
- return userName;
- }
-
- public void setUserName(String userName) {
- this.userName = userName;
- }
-
- public String getPassword() {
- return password;
- }
-
- public void setPassword(String password) {
- this.password = password;
- }
-
- @Override
- protected Object clone() throws CloneNotSupportedException {
- Customer customer = (Customer)super.clone();
- customer.user = (User)user.clone();
- return customer;
- }
-
- public User getUser() {
- return user;
- }
-
- public void setUser(User user) {
- this.user = user;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
- Customer customer = (Customer) o;
- return Objects.equals(userName, customer.userName) &&
- Objects.equals(password, customer.password) &&
- Objects.equals(user, customer.user);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(userName, password, user);
- }
-
- @Override
- public String toString() {
- return "Customer{" +
- "userName='" + userName + '\'' +
- ", password='" + password + '\'' +
- ", user=" + user +
- '}';
- }
- }
-
-
- public class User implements Cloneable{
-
- private int age;
-
- private int sex;
-
- public int getAge() {
- return age;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
- User user = (User) o;
- return age == user.age &&
- sex == user.sex;
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(age, sex);
- }
-
- public void setAge(int age) {
- this.age = age;
- }
-
- public int getSex() {
- return sex;
- }
-
- public void setSex(int sex) {
- this.sex = sex;
- }
-
- @Override
- public String toString() {
- return "User{" +
- "age=" + age +
- ", sex=" + sex +
- '}';
- }
-
- @Override
- protected Object clone() throws CloneNotSupportedException {
- return super.clone();
- }
- }
-
-
- public static void main(String[] args) throws CloneNotSupportedException {
-
- Customer c1 = new Customer();
- c1.setUserName("张三");
- c1.setPassword("123456");
- User user = new User();
- user.setAge(18);
- user.setSex(0);
- c1.setUser(user);
- Customer c2 = (Customer)c1.clone();
- System.out.println(c1.equals(c2));
- System.out.println(c1 == c2);
- System.out.println("c1 = " + c1);
- System.out.println("c2 = " + c2);
- user.setAge(16);
- user.setSex(1);
- System.out.println("c1_1 = " + c1);
- System.out.println("c2_1 = " + c2);
- System.out.println(c1.getUser().equals(c2.getUser()));
- System.out.println(c1.getUser() == c2.getUser());
- }
-
-
-
- D:\java\jdk1.8\bin\java.exe ...
- true
- false
- c1 = Customer{userName='张三', password='123456', user=User{age=18, sex=0}}
- c2 = Customer{userName='张三', password='123456', user=User{age=18, sex=0}}
- c1_1 = Customer{userName='张三', password='123456', user=User{age=16, sex=1}}
- c2_1 = Customer{userName='张三', password='123456', user=User{age=18, sex=0}}
- false
- false
-
- Process finished with exit code 0
3. 序列化复制
序列化是将对象的拷贝写到流中,原对象还存在于内存中
反序列化是将流中的对象读到内存中
将序列化主要目的是为了实现对象的跨域跨平台传输
java实现RMI、JMS等的基础
学习一下java中关于对象的序列化知识.
常用序列化方式有:jdk, json, xml, hessian, kryo, thrift, protostuff, fst.选择什么方式的序列化主要从以下几点去考虑:跨平台,序列化的性能,序列化文件大小
jdk序列化的实现方式: 实现序列化接口
- public class Customer implements Serializable {
-
- private String userName;
-
- private String password;
-
- public String getUserName() {
- return userName;
- }
-
- public void setUserName(String userName) {
- this.userName = userName;
- }
-
- public String getPassword() {
- return password;
- }
-
- public void setPassword(String password) {
- this.password = password;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
- Customer customer = (Customer) o;
- return Objects.equals(userName, customer.userName) &&
- Objects.equals(password, customer.password);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(userName, password);
- }
-
- @Override
- public String toString() {
- return "Customer{" +
- "userName='" + userName + '\'' +
- ", password='" + password + '\'' +
- '}';
- }
- }
-
-
-
- public static void main(String[] args) {
-
- Customer c1 = new Customer();
- c1.setUserName("张三");
- c1.setPassword("123456");
- ObjectOutputStream os = null;
- try {
- os = new ObjectOutputStream(new FileOutputStream("E:\\新建文件夹\\test.txt"));
- os.writeObject(c1);
- } catch (IOException e) {
- e.printStackTrace();
- }finally {
- try {
- os.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
执行完后会对象写到对应位置文件中
然后我们写在将文件中的对象反序列化到内存中
- public static void main(String[] args) {
-
- ObjectInputStream is = null;
- Customer customer;
- try {
- is = new ObjectInputStream(new FileInputStream("E:\\新建文件夹\\test.txt"));
- customer = (Customer)is.readObject();
- System.out.println(customer);
- } catch (Exception e) {
- e.printStackTrace();
- }finally {
- try {
- is.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
-
-
-
- D:\java\jdk1.8\bin\java.exe ...
- Customer{userName='张三', password='123456'}
-
- Process finished with exit code 0
序列化需要注意的几点
3.1 SerialVersionUID(该字段必须是静态 (static)、最终 (final) 的 long 型字段)用于对象的版本控制。当您添加或修改类中的任何字段时,已经序列化的类将无法恢复,因为serialVersionUID已为新类生成与旧的序列化对象将不同。Java序列化过程依赖于正确的serialVersionUID恢复序列化对象的状态,并在serialVersionUID不匹配时抛出java.io.InvalidClassException 异常。
添加版本号之后将对象序列化到文件,再修改对象属性,反序列化得到的还是修改前的对象,代码结果如图
- D:\java\jdk1.8\bin\java.exe
- Customer{userName='张三', password='123456', age='null'}
-
- Process finished with exit code 0
挡序列化一个类的实例后,希望更改一个字段或添加一个字段,不设置serialVersionUID,所做的任何更改都将导致无法反序化旧有实例,并在反序列化时抛出一个异常。如果你添加了serialVersionUID,在反序列旧有实例时,新添加或更改的字段值将设为初始化值(对象为null,基本类型为相应的初始默认值),字段被删除将不设置。
序列化和反序列化序列号不一致,会抛异常
- D:\java\jdk1.8\bin\java.exe ...
- java.io.InvalidClassException: Customer; local class incompatible: stream classdesc serialVersionUID = 1, local class serialVersionUID = 2
- at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:699)
- at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1885)
- at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1751)
- at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2042)
- at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1573)
- at java.io.ObjectInputStream.readObject(ObjectInputStream.java:431)
- at Demo.main(Demo.java:16)
-
- Process finished with exit code 0
3.2 transient修饰符仅适用于变量,不适用于方法和类。在序列化时,如果我们不想序列化特定变量以满足安全约束,那么我们应该将该变量声明为transient。执行序列化时,JVM会忽略transient变量的原始值并将默认值保存到文件中。因此,transient意味着不要序列化。
将password加transient修饰后,代码运行结果
- D:\java\jdk1.8\bin\java.exe ...
- Customer{userName='张三', password='null'}
-
- Process finished with exit code 0
3.3 静态变量不是对象状态的一部分,因此它不参与序列化。
将userName加static修饰后,代码运行结果
- D:\java\jdk1.8\bin\java.exe ...
- Customer{userName='null', password='123456'}
-
- Process finished with exit code 0
3.4 当一个父类实现序列化,子类自动实现序列化,不需要显式实现Serializable接口