How to deep copy or shadow copy/clone a Collection(List/Set/Map)

1. Introduction

This post would demo how to deep copy or shadow copy/clone a Collection(List/Set/Map).

This example will include:

  • Shadow copy of the Collection
  • Deep copy of the Collection

Environments

  • Java 1.8

2. Examples

Because java always pass value to method parameters(When you mean pass by reference, it’s just the value of the object address). So if you pass a collection to a method, then its pointer(reference) would be passed to the method. So ,if you change the collection, the original collection would also be changed.

2.1 The Game class

I define a Game class for test. It’s just a simple pojo.

class Game {
    private String manu;
    private String name;
    private double price;

    public Game(String manu, String name, double price) {
        this.manu = manu;
        this.name = name;
        this.price = price;
    }

    @Override
    public String toString() {
        return "Game{" +
                "manu='" + manu + '\'' +
                ", name='" + name + '\'' +
                ", price=" + price +
                '}';
    }

        //getter and setters...
}

2.2 The util methods

We define a method to process a Collection of Games

private static void processGame(List<Game> game, Consumer<Game> consumer) {
    game.stream().forEach(consumer);
}

Here we use a Functional parameter Consumer to process the games collection.And then use the stream’s forEach method to process the items.

2.3 The shadow copy

If we just pass a collection reference to a method,then the param collection and the original list share the same real instance. Only the pointer value is copied.

We can test like this:

public static void main(String[] args) {
    List<Game> games1 = Arrays.asList(
            new Game("blizzard","starcraft",99.99),
            new Game("blizzard","warcraft",129.99)
    );

    System.out.println("before processed");
    processGame(games1,game-> System.out.println(game));

    Consumer<Game> c1 = game->game.price=game.price+0.01;
    processGame(games1,c1);

    System.out.println("\nafter processed");
    processGame(games1,game-> System.out.println(game));
}

The output:

before processed
Game{manu='blizzard', name='starcraft', price=99.99}
Game{manu='blizzard', name='warcraft', price=129.99}

after processed
Game{manu='blizzard', name='starcraft', price=100.0}
Game{manu='blizzard', name='warcraft', price=130.0}

Explanation:

  • We define a list of games and init with two instances of games
  • we pass the original collection to the util method processGame

As you can see, the original list is affected by the change.

2.4 Deep copy(clone)

If you want the collection stay unchanged after passing it to a method, you should deep copy it.

Like this:

  • Firstly, add a constructor to the Game class for cloning purpose
public Game(Game game) {
    this.manu = game.getManu();//String is immutable, so point to same instance is safe
    this.name = game.getName();
    this.price = game.getPrice();
}

This constructor only copy values from the old instance. Note the manu and the name is String, so it’s immutable, If the util method changes it, a new instance of String would be created, the original would not be affected.

  • Then we define a clone util method to clone a Collection like this:
public static List<Game> cloneList(List<Game> list) {
    List<Game> clone = new ArrayList<Game>(list.size());
    for (Game game : list) clone.add(new Game(game));
    return clone;
}

As you can see, it just call the Game constructor we just added.

  • Thirdly, test it.
public static void main(String[] args) {
    List<Game> games1 = Arrays.asList(
            new Game("blizzard","starcraft",99.99),
            new Game("blizzard","warcraft",129.99)
    );

    System.out.println("before deep copy processed");
    processGame(games1,game-> System.out.println(game));

    Consumer<Game> c1 = game->{
        game.price=game.price+0.01;
        game.name= game.name+"_changed"; // line 1
    };
    List<Game> clonedList = cloneList(games1); // line2
    processGame(clonedList,c1);

    System.out.println("\nafter deep copy processed,the original list");
    processGame(games1,game-> System.out.println(game));

    System.out.println("\nafter deep copy processed,the cloned list"); 
    processGame(clonedList,game-> System.out.println(game));
}

Note that: *line 1 changed the name of the game instance line 2 Before pass to util method, we called the cloneList to clone it

  • Run the code, we got this
before deep copy processed
Game{manu='blizzard', name='starcraft', price=99.99}
Game{manu='blizzard', name='warcraft', price=129.99}

after deep copy processed,the original list
Game{manu='blizzard', name='starcraft', price=99.99}
Game{manu='blizzard', name='warcraft', price=129.99}

after deep copy processed,the cloned list
Game{manu='blizzard', name='starcraft_changed', price=100.0}
Game{manu='blizzard', name='warcraft_changed', price=130.0}

We can see that the original list is NOT affected by the util method.

3. summary

In this post we demo how to do deep clone with Collections.You can find the whole code examples on github.

You can find detail documents about the java clone here: