as3: How to copy an object by value
Asked Answered
A

3

5

I need to have an instance of one common object in every other object, I have. I am doing modifications on the values of this object in every sub object I have.

For example. I have a map of tiles, and a bot moving over them in specific order. Each bot is marking tiles, which was already visited by him, as visited=true. But in general I don't want main map to be changed...

I've tried to set up an example:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" 
    applicationComplete="complete()">
    <mx:Script>
        <![CDATA[
            private var array:Array = new Array( 1, 2, 3, 4);
            public function complete():void
            {
                trace("here " + array);
                var a:Array = array;
                a[0] = 100;
                trace("here " + array);
            }
        ]]>
    </mx:Script>
</mx:Application>

Can somebody help me to understand how do I copy for example the array, by value, (not by reference)

Adultery answered 30/1, 2011 at 18:39 Comment(4)
Does the bot need to alter anything other than the Boolean field to check off that it completed its visit?Insurgent
No only that boolean field in the tile... The issue is that, I am cloning the map from static variable defined in Consts file... And nothing hepls me to do it by value... E.g. my clone method do not help... I see each bot poluting the main varAdultery
Would it suffice if you used a multi-dimensional array of booleans that existed independent of the tiles? It just seems like a lot of unnecessary redundancy to multiply the number of tiles by the number of bots.Insurgent
Not sure how it can be independent on tiles? I mean, I am using isVisited attribute to avoid bot to navigate to same tile 2 times... Not sure how I can avoid the copying...Adultery
D
8
function clone ( arr:Array ):Array
{
    return arr.map( function( item:*, ..r ):*
        {
            return item;
        } );
}

edit:

Might contain some syntax errors...

public class MyObject
{
    private var arr:Array;
    private var bool:Boolean;

    // ...

    public function clone ():MyObject
    {
        var obj:MyObject = new MyObject();

        // clone values
        obj.arr = this.arr.slice();
        obj.bool = this.bool;

        return obj;
    }
}
Depredation answered 30/1, 2011 at 18:43 Comment(12)
How do I clone complex object with several fields?Adultery
Define your own clone function for that particular object. There is no special function that does so automatically for any arbitrary object.Depredation
can you give an example for cloning object which for example has array, and a boolean fieldAdultery
So clonning the object will be as simple as myObj.clone() ?Adultery
Poke, you are incorrect. The built in way of allowing objects to be coppied is to implement the IExternalizable interface. Then you read and write your objects propery to and from a ByteArray. Then you use the read and write object methods of ByteArray. This is how AMF works. Let me know if you want an example.Gautama
@Joony: No, serialization has little to do with copying objects. That you get a copy of an object if you unserialize a serialized object twice, is just an additional effect of serialization. And implementing IExternalizable still requires you to define the serialization methods, in which you write/load data.Depredation
Lol, serialisation has a lot to do with copies of objects. Wiki states "...[serialization] it can be used to create a semantically identical clone of the original object." serialize your object then deserialize. Then you have the original and a copy. And by doing it this way you are at least doing it in a standard way. In fact, it's laughably similar to what you're implementing here, just using the built in methods instead of reinventing the wheel.Gautama
How is implementing an interface, and having to define two methods for serialization a standard built-in way for cloning? Having a clone method is actually a quite standard way for cloning complex objects.. Just because there isn't some interface you implement (which would be highly inappropriate, given that clone returns an object of a special type), that doesn't mean that it is incorrect, unusual or any kind of a reinvented wheel.Depredation
You've probably never used the ObjectUtil.clone(value:Object):Object method. It works in exactly the way I've described. Instance variables that are Public will be copied automatically. So you don't need to implement the IExternalizable interface if that's all you want. So your clone() method isn't the only way to copy an object. This is a generic way that can be used to determine if an object is copyable, to easily make copies (of a specific type), and to do this while not making your instance variables public or supplying a setter. That is encapsulation.Gautama
Please tell me how your code is meant to work as your instance variables are private. Also, why is it inappropriate to implement an interface, and what does that have to do with returning a specific type? You can cast the returning object to the specific type, not the interface.Gautama
“It works in exactly the way I've described” – Eh? If you read your comments again, then you'll notice that you never mentioned ObjectUtil before; you were always talking about serialization. Also if you read my comments again, you'll notice that I have never said that my method is the only one… Using a custom clone method has the big advantage, that you can exactly decide what will be copied, and how it will be copied, and that the return type is correct (i.e. you don't need to cast). And yes, private variables are accessible from within the class they are defined in.Depredation
The implementation of ObjectUtil.clone() does exactly what I said. It uses serialisation.Gautama
P
10

For cloning arrays you can use Array.slice.

var arrCopy:Array = arrOrig.slice();
Petrie answered 30/1, 2011 at 18:46 Comment(1)
+1 For some reason some speed tests are sticking in my head that says arr.concat() is the fastest way, they do the same thing though.Belorussia
D
8
function clone ( arr:Array ):Array
{
    return arr.map( function( item:*, ..r ):*
        {
            return item;
        } );
}

edit:

Might contain some syntax errors...

public class MyObject
{
    private var arr:Array;
    private var bool:Boolean;

    // ...

    public function clone ():MyObject
    {
        var obj:MyObject = new MyObject();

        // clone values
        obj.arr = this.arr.slice();
        obj.bool = this.bool;

        return obj;
    }
}
Depredation answered 30/1, 2011 at 18:43 Comment(12)
How do I clone complex object with several fields?Adultery
Define your own clone function for that particular object. There is no special function that does so automatically for any arbitrary object.Depredation
can you give an example for cloning object which for example has array, and a boolean fieldAdultery
So clonning the object will be as simple as myObj.clone() ?Adultery
Poke, you are incorrect. The built in way of allowing objects to be coppied is to implement the IExternalizable interface. Then you read and write your objects propery to and from a ByteArray. Then you use the read and write object methods of ByteArray. This is how AMF works. Let me know if you want an example.Gautama
@Joony: No, serialization has little to do with copying objects. That you get a copy of an object if you unserialize a serialized object twice, is just an additional effect of serialization. And implementing IExternalizable still requires you to define the serialization methods, in which you write/load data.Depredation
Lol, serialisation has a lot to do with copies of objects. Wiki states "...[serialization] it can be used to create a semantically identical clone of the original object." serialize your object then deserialize. Then you have the original and a copy. And by doing it this way you are at least doing it in a standard way. In fact, it's laughably similar to what you're implementing here, just using the built in methods instead of reinventing the wheel.Gautama
How is implementing an interface, and having to define two methods for serialization a standard built-in way for cloning? Having a clone method is actually a quite standard way for cloning complex objects.. Just because there isn't some interface you implement (which would be highly inappropriate, given that clone returns an object of a special type), that doesn't mean that it is incorrect, unusual or any kind of a reinvented wheel.Depredation
You've probably never used the ObjectUtil.clone(value:Object):Object method. It works in exactly the way I've described. Instance variables that are Public will be copied automatically. So you don't need to implement the IExternalizable interface if that's all you want. So your clone() method isn't the only way to copy an object. This is a generic way that can be used to determine if an object is copyable, to easily make copies (of a specific type), and to do this while not making your instance variables public or supplying a setter. That is encapsulation.Gautama
Please tell me how your code is meant to work as your instance variables are private. Also, why is it inappropriate to implement an interface, and what does that have to do with returning a specific type? You can cast the returning object to the specific type, not the interface.Gautama
“It works in exactly the way I've described” – Eh? If you read your comments again, then you'll notice that you never mentioned ObjectUtil before; you were always talking about serialization. Also if you read my comments again, you'll notice that I have never said that my method is the only one… Using a custom clone method has the big advantage, that you can exactly decide what will be copied, and how it will be copied, and that the return type is correct (i.e. you don't need to cast). And yes, private variables are accessible from within the class they are defined in.Depredation
The implementation of ObjectUtil.clone() does exactly what I said. It uses serialisation.Gautama
G
6

Here is an alternative to the method described by poke:

I would first like to make some points about poke's post.

  1. "Define your own clone function for that particular object. There is no special function that does so automatically for any arbitrary object." False. ActionScript has a built in method of serialisation called AMF (ActionScript Message Format). AMF can be used to perform a copy of non-primitive objects.

  2. "Using a custom clone method has the big advantage, that you can exactly decide what will be copied, and how it will be copied..." This is exactly what you do when you serialise an object, so there is no big advantage of your custom clone method over that of serialisation.

  3. "...and that the return type is correct (i.e. you don't need to cast)." You don't need to cast a serialised object either. But serialisation has the added benefit of being generic, making the function for copying dynamic, and not limited to specific types.

  4. "[implementing an interface] (which would be highly inappropriate, given that clone returns an object of a special type)" Having to define a return type makes the process static which locks you in to using specific types. If we use the dynamic attributes of the language, we can make the a generic clone method and not care about type. There is nothing inappropriate about that.

  5. "That you get a copy of an object if you unserialize a serialized object twice, is just an additional effect of serialization." The fact you get a copy of an array by calling slice() or concat() without any arguments is just a side-effect of those methods. I don't really see your point here. Also, serialisation has copying at its heart. The act of serialising then de-serialising is the act of making a copy. You don't somehow get the exact same memory back, with references and all intact.

And I have one question: How would you deal with nested non-primitive types in your clone method?

In your question, you state "Can somebody help me to understand how do I copy for example the array, by value, (not by reference)" I think, when copying objects, it's important to know the difference between shallow and deep copies.

Shallow Copies

The solutions provided here (Array.slice(), and Array.concat()) are known as shallow copies. What you get is a copy of the array. If the contents of the array are primitive types (those that are passed by value, not by reference) then you have two unique objects, the original and the copy. However, if you're array contains objects that are passed by reference then you both the original and the copy of the array will have the exact same contents. If you make changes to an object in the original array, the changes will be reflected in the copied array. While this may sometimes what you desire, it isn't always.

Deep Copies

A deep copy will traverse the hierarchy of the object you wish to copy and make copies of any object it finds. You will then be allowed to make any changes to the copied object without any changes being reflected in the original.

If you are to define a custom clone method as poke suggests, then copying non-prinitive types becomes overly complicated. You would have to go through the object's properties and call a custom clone() method on any non-primitive type. However, if you encountered a built-in non-primitive type like Array or Dictionary, then you would have to recreate the object, loop through its contents, and start all over again by checking if it's non-primitive, calling its clone() method if it has one, or dealing with Arrays and Dictionaries. It gets overly-complicated. To summarise, this method has two problems: you have to deal with Arrays and Dictionaries (and any built-in non-primitive type) yourself; you have to specifically call the clone method on nested objects (and know that they have the clone method defined).

Another method is to use AMF to serialise and then de-serialise the object, giving you a deep copy. This works straight out-the-box for Arrays, Dictionaries, and any non-primitive that relies on public properties.

var t:Array = [];
t[0] = [1, 2, 3];
t[1] = new Dictionary();
t[1]['hello'] = 'world';
t[2] = {'my': 'object'}
trace(t, t[1]['hello'], t[2]['my']); // [trace] 1,2,3,[object Dictionary],[object Object] world object
var t2:Array = clone(t);
trace(t2, t2[1]['hello'], t2[2]['my']); // [trace] 1,2,3,[object Dictionary],[object Object] world object
t[0] = [4, 5, 6];
t[1]['hello'] = 'earth';
t[2]['my'] = 'other object';
trace('modified values');  // [trace] modified values
trace(t, t[1]['hello'], t[2]['my']);  // [trace] 4,5,6,[object Dictionary],[object Object] earth other object
trace(t2, t2[1]['hello'], t2[2]['my']);  // [trace] 1,2,3,[object Dictionary],[object Object] world object

function clone(source:*):* {
    var b:ByteArray = new ByteArray();
    b.writeObject(source);
    b.position = 0;
    return(b.readObject());
}

This covers the first problem with a custom clone method and point one above. As you can see, all objects and their contents have been copied using built-in methods.

I've shown an implementation of how to create the clone method here, but you can find one in: mx.utils.ObjectUtil.

If you want to deep copy an object that stores its data privately, then you have to implement the IExternalizable interface. That will force you to implement two methods:

public function writeExternal(output:IDataOutput):void
public function readExternal(input:IDataInput):void

Within these functions you write your private variables to the output object, then read them from the input to your private variables. Then when you call clone you will get a complete copy of your object. Remember to do this for all nested objects.

Here is a simple implementation example with two classes:

package {

    import flash.utils.IExternalizable;
    import flash.utils.IDataInput;
    import flash.utils.IDataOutput;
    import flash.net.registerClassAlias;

    public class Car implements IExternalizable {

    private var type:String;
        private var contents:Array;

        public function Car() {
            registerClassAlias("Car", Car);
        }

    public function setVars(type:String, contents:Array):void {
            this.type = type;
            this.contents = contents;
        }

    public function setType(type:String):void {
            this.type = type;
        }


        public function writeExternal(output:IDataOutput):void {
            output.writeUTF(type);
            output.writeObject(contents);
        }

    public function readExternal(input:IDataInput):void {
            type = input.readUTF();
            contents = input.readObject();
    }

        public function toString():String {
            return "[Car type = " + type + ", contents = " + contents + "]";
    }

    }

}

And:

package {

    import flash.utils.IExternalizable;
    import flash.utils.IDataInput;
    import flash.utils.IDataOutput;
    import flash.net.registerClassAlias;

    public class Person implements IExternalizable {

    private var firstName:String;
        private var secondName:String;

        public function Person() {
            registerClassAlias("Person", Person);
        }

    public function setVars(firstName:String, secondName:String):void {
            this.firstName = firstName;
            this.secondName = secondName;
        }

        public function writeExternal(output:IDataOutput):void {
            output.writeUTF(firstName);
            output.writeUTF(secondName);
        }

    public function readExternal(input:IDataInput):void {
            firstName = input.readUTF();
            secondName = input.readUTF();
        }

        public function toString():String {
            return "[Person firstName = " + firstName + ", secondName = " + secondName + "]";
        }

    }

}

To test them:

package {

    import flash.display.Sprite;
    import flash.utils.ByteArray;
    import flash.utils.Dictionary;

    public class Serial extends Sprite {

    public function Serial() {

            var person0:Person = new Person();
            person0.setVars("John", "Doe");
            var person1:Person = new Person();
            person1.setVars("Jane", "Doe");
            var car0:Car = new Car();
            car0.setVars("Ford", [person0, person1]);

            var person2:Person = new Person();
            person2.setVars("Joe", "Bloggs");
            var car1:Car = new Car();
            car1.setVars("Vauxhall", [person2]);

            var street:Array = [car0, car1];
            trace("street = " + street); // [trace] street = [Car type = Ford, contents = [Person firstName = John, secondName = Doe],[Person firstName = Jane, secondName = Doe]],[Car type = Vauxhall, contents = [Person firstName = Joe, secondName = Bloggs]]

            var street2:Array = clone(street);
            trace("street2 = " + street2); // [trace] street2 = [Car type = Ford, contents = [Person firstName = John, secondName = Doe],[Person firstName = Jane, secondName = Doe]],[Car type = Vauxhall, contents = [Person firstName = Joe, secondName = Bloggs]]

            person0.setVars("Max", "Headroom");
            person1.setVars("Simon", "Le Bon");
            car0.setType("Mini");

            person2.setVars("Harry", "Wotsit");
            car1.setType("Austin");

        trace("modified values of street"); // [trace] modified values of street
            trace("street = " + street); // [trace] street = [Car type = Mini, contents = [Person firstName = Max, secondName = Headroom],[Person firstName = Simon, secondName = Le Bon]],[Car type = Austin, contents = [Person firstName = Harry, secondName = Wotsit]]
            trace("street2 = " + street2); // [trace] street2 = [Car type = Ford, contents = [Person firstName = John, secondName = Doe],[Person firstName = Jane, secondName = Doe]],[Car type = Vauxhall, contents = [Person firstName = Joe, secondName = Bloggs]]

        }

        private function clone(source:*):* {
            var b:ByteArray = new ByteArray();
            b.writeObject(source);
            b.position = 0;
            return(b.readObject());
    }

    }

}

This covers the second problem with a custom clone method. As you can see, we haven't had to worry about calling any clone methods, this has all been taken care for us.

I'm not saying it isn't completely without drawbacks, but it does provide some functionality to deep copy objects.

Some drawbacks include:

  1. You cannot have any required parameters in the constructor.
  2. It doesn't store reference data, so if two objects contain the same reference, you will get two different objects back.
  3. Where to put the clone method. I think it really belongs on Object, on the prototype maybe. This would have the added benefit of making every object copyable, and if you want you can specify exactly how your object is copied.

See Adobe's point of view on copying arrays: http://livedocs.adobe.com/flex/3/html/help.html?content=10_Lists_of_data_6.html

Also note that Adobe stole this technique from Java.

Gautama answered 6/2, 2011 at 11:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.