7

I have an object (not SObject!) with a member of type List, when I clone the object and add something to the list, the original record will also be modified:

public class ThingToClone {
    public List<Integer> values = new List<Integer>();    
}

ThingToClone original = new ThingToClone();
ThingToClone clone = original.clone();
clone.values.add(1);

System.debug(LoggingLevel.ERROR, original);

results in

12:04:15:022 USER_DEBUG [5]|ERROR|ThingToClone:[values=(1)]

This is not what I expected, and I was not able to find any "Apex Object" documentation. Is there something like that? All I found was SObject related.

Edit:

To see if it creates a shadow object, I also tried to modify a class member, that was not part of the list. called it Integer anyNumber

ThingToClone original = new ThingToClone();
ThingToClone clone = original.clone();
clone.values.add(1);
clone.anyNumber = 2;
original.anyNumber = 3;

The new debug result is something really strange:

System.debug(LoggingLevel.ERROR, clone);
System.debug(LoggingLevel.ERROR, original);

USER_DEBUG [6]|ERROR|ThingToClone:[anyNumber=2, values=(1)]
USER_DEBUG [7]|ERROR|ThingToClone:[anyNumber=3, values=(1)]

So it's clearly not the same(shadow) object

1

I disagree and say it works as expected. My assumption is that Apex clone() uses Java's clone() internally. Java's clone() is a shallow clone, meaning the list in ThingToClone will just point to the original objects list.

As described here (http://howtodoinjava.com/core-java/cloning/a-guide-to-object-cloning-in-java/#deep_cloning):

By default, java cloning is ‘field by field copy’ i.e. as the Object class does not have idea about the structure of class on which clone() method will be invoked. So, JVM when called for cloning, do following things:

1) If the class has only primitive data type members then a completely new copy of the object will be created and the reference to the new object copy will be returned.

2) If the class contains members of any class type then only the object references to those members are copied and hence the member references in both the original object as well as the cloned object refer to the same object.

So I guess @IllusiveBrain is right in his comment, where he states that clone() copies primitive variables by value and non-primitive variables by reference. I'd expect this to be NO platform bug but intended behavior.

5

Initially, I thought that clone() creates shallow copy of the Original and a pointer to the original one.

I tried the following to support my argument

public class ThingToClone {
    public List<Integer> values = new List<Integer>();    
}

ThingToClone original = new ThingToClone();
ThingToClone clone = original.clone();
ThingToClone clone_of_clone = clone.clone();
clone.values.add(1);
original.values.add(2);

System.debug(LoggingLevel.ERROR, 'Original Instance:' + original);
System.debug(LoggingLevel.ERROR, 'Clone of  Original:' +clone);
System.debug(LoggingLevel.ERROR, 'Clone of clone :' + clone_of_clone);

Debug log proves all cloned object instances of wrapper class, have the same values

Debug log

06:17:02.1 (4199907)|USER_DEBUG|[11]|ERROR|Original Instance:ThingToClone:[values=(1, 2)]
06:17:02.1 (4310866)|USER_DEBUG|[12]|ERROR|Clone of  Original:ThingToClone:[values=(1, 2)]
06:17:02.1 (4410739)|USER_DEBUG|[13]|ERROR|Clone of clone :ThingToClone:[values=(1, 2)]

A similar question is also present on SFSE, that might help you investigate further.

EDIT

With the recent turn of events, I played around a bit and found workaround to your problem.

Updated code

public class ThingToClone {
    public List<Integer> values;    
    public integer testInt;
    public Account acc;
}

ThingToClone original = new ThingToClone();
ThingToClone clone = original.clone();
ThingToClone clone_of_clone = clone.clone();

// integer member
original.testInt = 20;
clone.testInt = 2;

// sobject member
Account testAcc = new Account(Name = 'Test Account');
clone.acc = testAcc;

// collection member
clone.values = new List<integer>();
clone.values.add(1);
original.values = new List<integer>();
original.values.add(2);

System.debug(LoggingLevel.ERROR, 'Original Instance:' + original);
System.debug(LoggingLevel.ERROR, 'Clone of  Original:' +clone);
System.debug(LoggingLevel.ERROR, 'Clone of clone :' + clone_of_clone);

Updated debug log

07:19:35.1 (4421596)|USER_DEBUG|[22]|ERROR|Original Instance:ThingToClone:[acc=null, testInt=20, values=(2)]
07:19:35.1 (4743354)|USER_DEBUG|[23]|ERROR|Clone of Original:ThingToClone:[acc=Account:{Name=Test Account}, testInt=2, values=(1)]
07:19:35.1 (4864965)|USER_DEBUG|[24]|ERROR|Clone of clone :ThingToClone:[acc=null, testInt=null, values=null]

Explanation

If I don't create the instance of the List<> in the wrapper class, instead create them after cloning the object, then the Values are preserved. I know, that other member types are getting expected response, but this is just a workaround that I found to get you on the move, if you are stuck.

  • 2
    Idea – Rohit Mourya Mar 2 '17 at 12:33
  • 2
    Interesting, isn't it? :) Seems like you have found a bug. – Mahmood Mar 2 '17 at 12:57
  • 1
    I tagged SalesforceSupport on twitter, let's see where this goes. Thanks for contributing guys! – Basti Mar 2 '17 at 13:04
  • 1
    @Basti: I think I have some new update for you. ;-) Check my updated answer, plz. – Mahmood Mar 2 '17 at 13:31
  • 4
    I must be missing something, what exactly is being called a bug here? Primitives are value types, collections and classes are reference types, you would not expect integer i = 0; integer z = i; z = 5; to end up with i equal to 5, but you would expect List<Integer> i = new list<integer>(); List<Integer> z = i; z.add(5) to modify the one list being referenced by both variables, since you are assigning a reference to both variables. Alternatively, try using primitives vs. collections as method parameters, you'll see the same behavior. – IllusiveBrian Mar 2 '17 at 17:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.