Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Collections.nCopies not creating copies of list

Tags:

java

arraylist

I have created a List of Lists with a default size set. Therefore, I am adding the data to the lists using the set method.

My Code:

package test;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Test2 {
public static void main(String args[]) {
    List<List<String>> keys = new ArrayList<>(Collections.nCopies(3, new ArrayList<>()));
    List<String> values = keys.get(2);
    values.add("Hello");
    values.add("One Two");
    keys.set(2, values);
    List<String> tempList = keys.get(1);
    tempList.add("Now adding at 1");
    keys.set(1, tempList);
    System.out.println("Position 1 " + keys.get(1).toString());
    System.out.println("Position 2 " + keys.get(2).toString());
}
}

My Output:

Position 1 [Hello, One Two, Now adding at 1]
Position 2 [Hello, One Two, Now adding at 1]

Why is it doing this? The first data get's appended onto the second as well? What am I doing wrong ?

like image 388
chrisrhyno2003 Avatar asked Jul 14 '15 17:07

chrisrhyno2003


1 Answers

List<List<String>> keys = new ArrayList<>(Collections.nCopies(3, new ArrayList<>()))

In this statement it's not creating three new instances of ArrayList<>. It creates one instance of it and creates a list with three references to the same instance. Hence each location in the list of lists is a reference to the same ArrayList instance.

The simplest thing you can do to get around this is to use a for loop:

for(int i = 0; i < numCopies; i++) {
    keys.add(new ArrayList<>());
}

An equivalent solution using Java 8's streaming API:

IntStream.range(0, n).mapToObj(i -> new ArrayList<String>()).forEach(keys::add);

Or you can create keys in one go like so:

List<List<String>> keys = IntStream.range(0, numCopies)
                                   .mapToObj(i -> new ArrayList<String>())
                                   .collect(Collectors.toList());

We use i -> new ArrayList<String>() instead of ArrayList<String>::new because there is an ArrayList<T> constructor that takes in a single integer argument for the initial capacity, which means that it ends up actually being new ArrayList<String>(0), new ArrayList<String>(1), and so on, which is not what we want.

A more elegant option is to use Stream.generate:

Stream.generate(ArrayList<String>::new).limit(n);
like image 102
Vivin Paliath Avatar answered Oct 22 '22 05:10

Vivin Paliath