Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

as3: How to copy an object by value

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)

like image 951
Oleg Tarasenko Avatar asked Jan 30 '11 18:01

Oleg Tarasenko


3 Answers

For cloning arrays you can use Array.slice.

var arrCopy:Array = arrOrig.slice();
like image 171
Alin Purcaru Avatar answered Nov 17 '22 13:11

Alin Purcaru


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;
    }
}
like image 38
poke Avatar answered Nov 17 '22 11:11

poke


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.

like image 23
Joony Avatar answered Nov 17 '22 13:11

Joony