CustardBun
CustardBun

Reputation: 3857

Trying to create a "copy" of an object using a constructor in Java, but what happens to what I pass in?

Ok - the title doesn't make much sense. I apologize in advance - I'm relatively new to some Java concepts:

I have a class, which looks a little like this (simplified):

public class SomeContainer {
    T someEntity;
    Map<String, Map<String, List<SomeOtherClass>>>> someCrazyMapping;
}

I have a variable of type SomeContainer, let's call it container1. I want to make container2, which is essentially an exact copy of container1, but I'm going to make some modifications to container2's "crazy mapping".

What I tried doing is creating a "copy" of container1 as follows:

SomeContainer container2 = new SomeContainer(container1.getSomeEntity(),  container1.getSomeCrazyMapping())

The constructor is an @AllArgsConstructor - and just copies the values as this.someEntity = someEntity (etc.)

If I put entries into the "crazyMapping" in container2, does container1 get affected at all? I am running it to check how it behaves, but if someone could explain how this works, it would be greatly appreciated.

Upvotes: 1

Views: 360

Answers (2)

Jason C
Jason C

Reputation: 40315

Note that this answer was prior to your edit regarding the nature of the constructor, so some of this stuff may be extraneous, but the concepts still hold, so there's not much for me to change here except to add that your situation is the 4th bullet point in the first list below, so the possibility of modifying container1 via changes to container2 does exist, and you'll want to do a shallow or deep copy of that map rather than storing a reference to it.

Basically, "what happens to container1" is exactly what you tell your code to do to container1. Nothing tricky will happen behind the scenes.

So, there's no way to give a definitive answer from the information given, but from this line:

SomeContainer container2 = new SomeContainer(container1.getSomeEntity(), 
                                             container1.getSomeCrazyMapping());

The best we can tell you from what you've shown is that container1 will not be "modified" (depending on what you consider "modified") as long as:

  • Your implementation of getSomeEntity() does not modify container1, and
  • Your implementation of getSomeCrazyMapping() does not modify container1, and
  • Your implementation of that constructor does not modify the parameters you pass in (e.g. by calling clear() on the map you pass it or something) in a way that causes container1 to be modified.
  • Your implementation of that constructor does not create situations where you could modify container1 via container2 later, e.g. you return the map directly in getSomeCrazyMapping(), store a reference to it in container2, then modify it via that reference.

I use the term "modified" loosely, since it really depends on your situation what you consider to be "modified", but no, nothing's going to just randomly happen, you have to analyze your code to see if you are explicitly modifying anything.


So to help you maintain careful control over things you have a few tools at your disposal. Here are some examples, and it's left as an exercise to you to figure out how you can use these effectively:

  • Most maps have shallow "copy constructors", e.g. HashMap. References to the same key and value objects will be stored in the copy but it will at least be a different map.
  • You've got e.g. Object#clone that you could implement on your containers, key and value objects, whatever. Note that some maps implement Cloneable as well, see for example HashMap#clone.
  • You've got e.g. Collections#unmodifiableMap that you could use to wrap the return value of getSomeCrazyMapping() if you want to programmatically ensure that nobody is adding/removing values to the map returned by that method.
  • Here are some techniques for deep copying generic Maps, if you are sticking to generic Map interfaces, which can be tricky.
  • Also don't forget you can do your copy on a "higher" level. For example if your containers have their own "add/insert" operations and their own "get"/iterator operations you can just not do the under-the-hood stuff and use your high level methods instead, which can greatly simplify your code (totally made up example), and remove dependence on the actual implementation details of your containers:

    public SomeContainer (SomeContainer other) {
        for (ExampleEntry entry : other.getEntries())
            this.addEntry(entry);
    }
    

    Fwiw, this is the approach I'd probably take myself if my container implementations were getting complicated.

Just a small set of things you have to work with here. Documentation is also your friend, clearly document your invariants and assumptions like "Modifying the map returned by this method will modify this container" or "This constructor creates deep copies of its parameters", etc. Then stick to those in your implementations (and test!).

Upvotes: 1

joshua0823
joshua0823

Reputation: 390

Declaring an object like this does not affect the initial object. If you declared the object like

SomeContainer container2 = container1;

then when you modify container2 it would modify container1 as well.

Upvotes: 0

Related Questions