Etienne Savary
Etienne Savary

Reputation: 19

In Java : How to duplicate "entirely" a Map with Collections as value?

The code below is illustrating my problem :

public class TreeMapCopyProblem {

    private static TreeMap<Integer, SortedSet<Integer>> treeMap1;

    private static TreeMap<Integer, SortedSet<Integer>> treeMap2;

    public static void main(String[] args) {

        fillTreeMap1();
        initializeTreeMap2();
        test();
    }

    private static void initializeTreeMap2() {
        treeMap2 = (TreeMap<Integer, SortedSet<Integer>>) treeMap1.clone();
    }

    private static void fillTreeMap1() {
        treeMap1 = new TreeMap<>();
        SortedSet<Integer> values0 = Stream.of(0).collect(Collectors.toCollection(TreeSet::new));
        SortedSet<Integer> values1 = Stream.of(1).collect(Collectors.toCollection(TreeSet::new));
        SortedSet<Integer> values2 = Stream.of(2).collect(Collectors.toCollection(TreeSet::new));

        treeMap1.put(0, values0);
        treeMap1.put(1, values1);
        treeMap1.put(2, values2);

        //Once fill, this treeMap does not have to be modified anymore
    }

    private static void test(){
        treeMap2.get(0).add(99);
        System.out.println(treeMap1);
    }

}

TreeMap1 is modified too because it's a shallow Copy referring to the same set.

I had the same behaviour using

treeMap2 = new TreeMap<>(treeMap1); or treeMap2.putAll(treeMap1);

So... How do we simply do a non shallow copy of an existing Tree Map so we can initialize a new TreeMap from an existing one and then modify only the second one?

Upvotes: -1

Views: 92

Answers (2)

John Bollinger
John Bollinger

Reputation: 181008

How do we simply do a non shallow copy of an existing Tree Map so we can initialize a new TreeMap from an existing one and then modify only the second one?

Supposing that you don't feel the need to make copies of the Integer objects involved (which are, after all, immutable), we only need to go one level deeper to make a sufficiently deep copy of your original map. I would probably do that by streaming and then collecting the original map's entry set:

private static void initializeTreeMap2() {
    treeMap2 = treeMap1.entrySet()
        .stream()
        .collect(TreeMap::new,
            (m, e) -> m.put(e.getKey(), new TreeSet<>(e.getValue())),
            (m1, m2) -> m1.putAll(m2));
}

This is similar in concept to your forEach()-based approach, but it feels a bit cleaner to me. Among other things, there is no way for any thread to observe a partially filled state of treeMap2. Also, this stream-based approach is auto-parallelizable, whereas forEach() is unavoidably serial.

Upvotes: 1

Etienne Savary
Etienne Savary

Reputation: 19

  private static void initializeTreeMap2() {
    treeMap2 = (new TreeMap<>());
    treeMap1.forEach((integer, integers) -> {
        SortedSet<Integer> newSet = new TreeSet<>(integers);
        treeMap2.put(integer, newSet);
    });
}

This works. But is it the good approach ?

(Note putAll method also give a shallow copy.)

Upvotes: 0

Related Questions