Java 当深度拷贝遇到多态

作者: 刘笔尖儿 | 来源:发表于2019-04-09 17:01 被阅读0次

今天遇到一个处理对象,在类的定义中使用的是父类,但是实际接收的数据是子类类型,而且子类类型是多态的。
因为Java 对象都是引用赋值,所以导致浅拷贝后,对对象的操作影响了初始的对象。

经过查找,需要使用深度拷贝的方式克隆对象,但是对象的层次非常深,而且类型定义为父类,在深度拷贝的时候需要判断其实际类型。
尝试使用fastjson进行序列化,然后再序列化生成新的对象。但是因为多态的问题,在反序列化时实际的子类属性丢失,只保留了定义的父类类型。

搞了很久,还是没有好的解决办法。

下面的代码描述:

@Lombok
class Parent {
    private String parentName;
}

@Lombok
class ChildA extends Parent {
    private String childAName;
}

@Lombok
class ChildB extends Parent {
    private String childBName;
}

@Lombok
class Contains {
    private List<Parent> parentList;
}

public class TestClass {
    public static void main(String[] args){
        Contains contains = new Contains();
        List<ChildA> childAList = new ArrayList();
        List<ChildB> childBList = new ArrayList();
        //此时实际类型是ChildA
        contains.setParentLsit(childAList);
        //反序列化,丢失了ChildA的childAName属性
        JSONObject childAJson = JSONObject.parseObject(JSONObject.toJSONString(contains)),Contains.class);
        
        //此时实际类型是ChildB
        contains.setParentLsit(childBList);
        //反序列化,丢失了ChildB的childBName属性
        JSONObject childBJson = JSONObject.parseObject(JSONObject.toJSONString(contains)),Contains.class);
    }
}

通过以上测试,结果在反序列化的时候,丢失了子类属性。

目前这个问题还没有解决,因为实际的类结构非常复杂,多态情况也更复杂。单纯的使用深度复制估计也是最后的办法,而且还要考虑多态的情况。

Swagger在进行对象反序列化的时候可以处理这个问题,初步准备学习一下,结论将在后续补充。


后续。。。

JsonObect的底层的实现是Map,这点从Json的格式也看得出来。所以fastjson的序列化和反序列化的实现机制应该把类的field拿出来,然后通过用<成员名称,成员值> 作为KV值,组成Json字符串。反序列化的时候也是如此,根据反序列化的结果类的成员属性,从json字符串里找到对应的KV值,然后把成员值set回来。

所以这也是为什么需要序列化和类一定要有无参构造函数和最好实现Serializable接口的原因。

上面也说提到,因为fastjson在反序列化的时候是根据类的实际类型set属性的,所以子类的成员属性会丢失。


目前找到的解决办法:

首先简单介绍下fastJson的反序列化原理。

fastJson是使用JavaBeanDeserializer 的一系列deserialze来进行反序列化处理,创建类对象,然后查找field,往回set值

使用Java自带的二进制序列化功能,实现序列化和反序列化功能。

//Parent.java
public class Parent implements Serializable {
    private String parentName;

    public String getParentName() {
        return parentName;
    }

    public void setParentName(String parentName) {
        this.parentName = parentName;
    }

    //无成员变量,只有方法
    public String getNoName() {
        return parentName;
    }

    public void setNoName(String noName) {
        this.parentName = noName;
    }
}

//ChildA.java
public class ChildA extends Parent {
    //子类特殊属性
    private String childAName;

    public String getChildAName() {
        return childAName;
    }

    public void setChildAName(String childAName) {
        this.childAName = childAName;
    }
}

//ChildB.java
public class ChildB extends Parent{
    private String childBName;

    public String getChildBName() {
        return childBName;
    }

    public void setChildBName(String childBName) {
        this.childBName = childBName;
    }
}

//Contains.java
public class Contains implements Serializable {

    private List<Parent> parentList;

    public List<Parent> getParentList() {
        return parentList;
    }

    public void setParentList(List<Parent> parentList) {
        this.parentList = parentList;
    }
}

//MainTest.java
public class MainTest {
    public static  void main(String[] args) {
        Contains contains = new Contains();
        //父类类型
        List<Parent> parentList = new ArrayList<>();
        
        ChildA childA = new ChildA();
        childA.setParentName("ChildA Parent");
        childA.setChildAName("ChildA");
        childA.setNoName("ChildA noName");

        ChildB childB = new ChildB();
        childB.setParentName("ChildB Parent");
        childB.setChildBName("ChildB");

        //添加实际子类对象
        parentList.add(childA);
        parentList.add(childB);
        contains.setParentList(parentList);

        //json 序列化
        String object = JSON.toJSONString(contains);
        Contains contains1 = JSONObject.parseObject(object, Contains.class);

        //二进制 序列化
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        try{
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            FileOutputStream fos = new FileOutputStream("contains.txt");
           
            oos.writeObject(contains);
            fos.write(bos.toByteArray());
            fos.flush();
            fos.close();
            oos.flush();
            oos.close();
        } catch (IOException e){
            System.out.println("Error");
            e.printStackTrace();
        }
        byte[] bytes= bos.toByteArray();
        try {
            ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
            ObjectInputStream ois = new ObjectInputStream(bis);
            Contains byteContains = (Contains)ois.readObject();
            ois.close();
        }catch (IOException | ClassNotFoundException e) {
            System.out.println("Error");
            e.printStackTrace();
        }
        System.out.println("FINISH");
    }
}

那么接下来让我们看看序列化出来的二进制文件:

¬í^@^Esr^@ com.test.ContainsFqRÿn<97>û©^B^@^AL^@
parentListt^@^PLjava/util/List;xpsr^@^Sjava.util.ArrayListx<81>Ò^]<99>Ça<9d>^C^@^AI^@^Dsizexp^@^@^@^Bw^D^@^@^@^Bsr^@^^com.test.ChildA²Áö<95><9f>^@±<96>^B^@^AL^@
childANamet^@^RLjava/lang/String;xr^@^^com.test.ParentÐÃS¸©Ð<95><82>^B^@^AL^@
parentNameq^@~^@^Fxpt^@^MChildA noNamet^@^FChildAsr^@^^com.test.ChildBwÑìg­2^AD^B^@^AL^@
childBNameq^@~^@^Fxq^@~^@^Gt^@^MChildB Parentt^@^FChildBx

可以发现文件保存了类的实际类型。这样在反序列化的时候就可以根据保存的实际类型创建对象,保证了成员的完整。

同时在Parent中增加了getNoName/setNoName 方法,验证一下序列化时对方法的处理,结果不出意外,不处理。这可能就涉及到JVM对类的编译机制和.class文件的字节码结构了。

通过二进制流,实现了对象的深度克隆,暂且实现了工程目标。

但是总是感觉哪里姿势不对。。。

遗留几个问题:

  1. fastjson是一个性能很高的JSON框架,但是既然json解决不了多态的问题,那么是要避免多态的设计模式,还是使用其他的序列化工具呢?
  2. 目前主流的序列化工具有哪些?可不可以避免重复造轮子?

接下来准备研究一下这两个问题。

相关文章

  • Java 当深度拷贝遇到多态

    今天遇到一个处理对象,在类的定义中使用的是父类,但是实际接收的数据是子类类型,而且子类类型是多态的。因为Java ...

  • java 深度拷贝

    利用文件操作进行java深度拷贝。

  • java基础概念

    java_basic 介绍 java基础 三大特性之—封装 三大特性之—继承 三大特性之—多态 抽象 接口 深拷贝...

  • Java深度拷贝

    标签: Android 内存相关 Why 直接赋值无法断开引用浅拷贝在含有内部对象引用的时候无法断开引用断开引用防...

  • Java深度拷贝方式和性能对比

    前言 Java的深度拷贝大致分为克隆(实现Java的Clone接口)和序列化(实现Java的Serializabl...

  • 面经一

    目的: 总结下面试时遇到的问题,并附上自己的答案. 1 Java基础 1.1 Java的继承,多态。 以及多态...

  • Java对象的拷贝

    对象的拷贝 深度拷贝一个对象 java.lang.Object 根类已经定义了 clone() 方法。子类只需要事...

  • java多态面试题

    java多态性 多态分两种: (1) 编译时多态(设计时多态):方法重载。 (2) 运行时多态:JAVA运...

  • java多态面试题

    java多态性 多态分两种: (1) 编译时多态(设计时多态):方法重载。 (2) 运行时多态:JAVA运行时...

  • 2018-01-25

    多态机制 java语言,实现多态...

网友评论

    本文标题:Java 当深度拷贝遇到多态

    本文链接:https://www.haomeiwen.com/subject/qsjiiqtx.html