Deep copy Map in Groovy

后端 未结 5 2103
眼角桃花
眼角桃花 2020-12-15 17:26

How can I deep copy a map of maps in Groovy? The map keys are Strings or Ints. The values are Strings, Primitive Objects or other maps, in a recursive way.

相关标签:
5条回答
  • 2020-12-15 17:49

    For Json (LazyMap) this wokred for me

    copyOfMap = new HashMap<>()
    originalMap.each { k, v -> copyOfMap.put(k, v) }
    copyOfMap = new JsonSlurper().parseText(JsonOutput.toJson(copyOfMap))
    

    EDIT: Simplification by: Ed Randall

    copyOfMap = new JsonSlurper().parseText(JsonOutput.toJson(originalMap))
    
    0 讨论(0)
  • 2020-12-15 17:53

    To go about deep copying each member in a class, the newInstance() exists for Class objects. For example,

    foo = ["foo": 1, "bar": 2]
    bar = foo.getClass().newInstance(foo)
    foo["foo"] = 3
    assert(bar["foo"] == 1)
    assert(foo["foo"] == 3)
    

    See http://groovy-lang.org/gdk.html and navigate to java.lang, Class, and finally the newInstance method overloads.

    UPDATE:

    The example I have above is ultimately an example of a shallow copy, but what I really meant was that in general, you almost always have to define your own reliable deep copy logic, with perhaps using the newInstance() method, if the clone() method is not enough. Here's several ways how to go about that:

    import groovy.transform.Canonical
    import groovy.transform.AutoClone
    import static groovy.transform.AutoCloneStyle.*
    
    // in @AutoClone, generally the semantics are
    //  1. clone() is called if property implements Cloneable else,
    //  2. initialize property with assignment, IOW copy by reference
    //
    // @AutoClone default is to call super.clone() then clone() on each property.
    //
    // @AutoClone(style=COPY_CONSTRUCTOR) which will call the copy ctor in a 
    //  clone() method. Use if you have final members.
    //
    // @AutoClone(style=SIMPLE) will call no arg ctor then set the properties
    //
    // @AutoClone(style=SERIALIZATION) class must implement Serializable or 
    //  Externalizable. Fields cannot be final. Immutable classes are cloned.
    //  Generally slower.
    //
    // if you need reliable deep copying, define your own clone() method
    
    def assert_diffs(a, b) {
        assert a == b // equal objects
        assert ! a.is(b) // not the same reference/identity
        assert ! a.s.is(b.s) // String deep copy
        assert ! a.i.is(b.i) // Integer deep copy
        assert ! a.l.is(b.l) // non-identical list member
        assert ! a.l[0].is(b.l[0]) // list element deep copy
        assert ! a.m.is(b.m) // non-identical map member
        assert ! a.m['mu'].is(b.m['mu']) // map element deep copy
    }
    
    // deep copy using serialization with @AutoClone 
    @Canonical
    @AutoClone(style=SERIALIZATION)
    class Bar implements Serializable {
       String s
       Integer i
       def l = []
       def m = [:]
    
       // if you need special serialization/deserialization logic override
       // writeObject() and/or readObject() in class implementing Serializable:
       //
       // private void writeObject(ObjectOutputStream oos) throws IOException {
       //    oos.writeObject(s) 
       //    oos.writeObject(i) 
       //    oos.writeObject(l) 
       //    oos.writeObject(m) 
       // }
       //
       // private void readObject(ObjectInputStream ois) 
       //    throws IOException, ClassNotFoundException {
       //    s = ois.readObject()
       //    i = ois.readObject()
       //    l = ois.readObject()
       //    m = ois.readObject()
       // }
    }
    
    // deep copy by using default @AutoClone semantics and overriding 
    // clone() method
    @Canonical
    @AutoClone
    class Baz {
       String s
       Integer i
       def l = []
       def m = [:]
    
       def clone() {
          def cp = super.clone()
          cp.s = s.class.newInstance(s)
          cp.i = i.class.newInstance(i)
          cp.l = cp.l.collect { it.getClass().newInstance(it) }
          cp.m = cp.m.collectEntries { k, v -> 
             [k.getClass().newInstance(k), v.getClass().newInstance(v)] 
          }
          cp
       }
    }
    
    // assert differences
    def a = new Bar("foo", 10, ['bar', 'baz'], [mu: 1, qux: 2])
    def b = a.clone()
    assert_diffs(a, b)
    
    a = new Baz("foo", 10, ['bar', 'baz'], [mu: 1, qux: 2])
    b = a.clone()
    assert_diffs(a, b)
    

    I used @Canonical for the equals() method and tuple ctor. See groovy doc Chapter 3.4.2, Code Generation Transformations.

    Another way to go about deep copying is using mixins. Let's say you wanted an existing class to have deep copy functionality:

    class LinkedHashMapDeepCopy {
       def deep_copy() {
          collectEntries { k, v -> 
             [k.getClass().newInstance(k), v.getClass().newInstance(v)]
          }
       }
    }
    
    class ArrayListDeepCopy {
       def deep_copy() {
          collect { it.getClass().newInstance(it) }
       } 
    }
    
    LinkedHashMap.mixin(LinkedHashMapDeepCopy)
    ArrayList.mixin(ArrayListDeepCopy)
    
    def foo = [foo: 1, bar: 2]
    def bar = foo.deep_copy()
    assert foo == bar
    assert ! foo.is(bar)
    assert ! foo['foo'].is(bar['foo'])
    
    foo = ['foo', 'bar']
    bar = foo.deep_copy() 
    assert foo == bar
    assert ! foo.is(bar)
    assert ! foo[0].is(bar[0])
    

    Or categories (again see the groovy doc) if you wanted deep copying semantics based on some sort of runtime context:

    import groovy.lang.Category
    
    @Category(ArrayList)
    class ArrayListDeepCopy {
       def clone() {
          collect { it.getClass().newInstance(it) }
       } 
    }
    
    use(ArrayListDeepCopy) {
       def foo = ['foo', 'bar']
       def bar = foo.clone() 
       assert foo == bar
       assert ! foo.is(bar)
       assert ! foo[0].is(bar[0]) // deep copying semantics
    }
    
    def foo = ['foo', 'bar']
    def bar = foo.clone() 
    assert foo == bar
    assert ! foo.is(bar)
    assert foo[0].is(bar[0]) // back to shallow clone
    
    0 讨论(0)
  • 2020-12-15 17:58

    I am afraid you have to do it the clone way. You could give Apache Commons Lang SerializationUtils a try

    0 讨论(0)
  • 2020-12-15 18:09

    I've just hit this issue as well, and I just found:

    deepCopy = evaluate(original.inspect())
    

    Although I've been coding in Groovy for less than 12 hours, I wonder if there might be some trust issues with using evaluate. Also, the above doesn't handle backslashes. This:

    deepCopy = evaluate(original.inspect().replace('\\','\\\\'))
    

    does.

    0 讨论(0)
  • 2020-12-15 18:15

    An easy way is this:

    // standard deep copy implementation
    def deepcopy(orig) {
         bos = new ByteArrayOutputStream()
         oos = new ObjectOutputStream(bos)
         oos.writeObject(orig); oos.flush()
         bin = new ByteArrayInputStream(bos.toByteArray())
         ois = new ObjectInputStream(bin)
         return ois.readObject()
    }
    
    0 讨论(0)
提交回复
热议问题