30

In Java, why doesn't the following line of code work?

List<List<String>> myList = new ArrayList<ArrayList<String>>();

It works if I change it to

List<ArrayList<String>> myList = new ArrayList<ArrayList<String>>();

At first, I thought maybe you can't have lists of an interface, but I can create a List<Runnable> just fine.

Ideas?

nbro
  • 15,395
  • 32
  • 113
  • 196
Nosrettap
  • 10,940
  • 23
  • 85
  • 140
  • 5
    How about `List> myList = new ArrayList>();`? – madth3 Oct 12 '12 at 14:56
  • @madth3 new ArrayList>() will work only if List is a java.awt.List, not the same package then ArraList> – cl-r Oct 12 '12 at 15:12
  • List is a class in `java.awt` and an interface in `java.util` package. So you have to take care of what is the good for you. Because of ` ArrayList`, it seems you are working with `java.util`, and `java.util.List` may be listed in your `import`. : 1°) `new ArrayList>()` is good if you want an `ArrayList` of `ArrayList` 2°) `new ArrayList>()` will compile as `java.awt.List`. – cl-r Oct 15 '12 at 07:04

6 Answers6

46

Generic types are more pedantic.

List means List or any sub-type, but <List> means only List. If you want a sub-type you need to have <? extends List>

I suspect you can use

List<List<String>> myList = new ArrayList<List<String>>();

The reason you can't do this is that you can be using a reference to a reference and with an extra level of indirection you have to be careful.

// with one level of indirection its simple.
ArrayList alist = new ArrayList();
List list = aList; // all good
list = new LinkedList(); // alist is still good.

With generics you can have two level of indirection which can give you problems so they are more pedantic to avoid these issues.

// with two levels of indirection
List<ArrayList> alist = new ArrayList<ArrayList>();
List<List> list = (List) alist; // gives you a warning.
list.add(new LinkedList()); // adding a LinkedList into a list of ArrayList!!
System.out.println(alist.get(0)); // runtime error

prints

Exception in thread "main" java.lang.ClassCastException: java.util.LinkedList
     cannot be cast to java.util.ArrayList
Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
  • 1
    Yup, that seems to work. Can you explain a little more about why. Thanks – Nosrettap Oct 12 '12 at 14:59
  • 1
    You can add a `LinkedList` to a `List>`. You cannot add a `LinkedList` to an `ArrayList>`. Therefore, you can't create a variable of type `List>` referring to an `ArrayList>`, because they have different behavior. – Louis Wasserman Oct 12 '12 at 15:01
  • With non generic types you can assign a `ArrayList` to a `List` as it will cast it implicitly for you. For a generic type, you cannot cast a `` to a `` as they are different types. You can cast `` to a ` extends List>` as ArrayList extends a List. – Peter Lawrey Oct 12 '12 at 15:02
  • 1
    java.util.List is an interface (java.awt.List a class), and it is not possible to instantiated it. ArrayList is a concrete instance which implement java.util.List, it is recommended to substituate it by the interface in the field, as you have done it in your first sample – cl-r Oct 12 '12 at 15:14
  • The problem arises because it is possible to change the list, add object of non-conforming class. If you don't change the list, then the problem goes away. Is there a way to declare that you will not change the list? – fishinear Nov 21 '18 at 14:03
  • A `List>` can supports `get(n)` and `remove(e)` but not `add(e)` which prevents adding an invalid reference. (unless you cast it) – Peter Lawrey Nov 21 '18 at 18:58
10

Lets start with this:

    ArrayList<ArrayList<String>> myList = new ArrayList<ArrayList<String>>();

This is creating an ArrayList whose elements are ArrayLists.

Now suppose we could assign that to

    List<List<String>> myList2 = myList.

Now, we should be able to do this:

    myList2.add(new LinkedList<String>());

But that means we have added a LinkedList to a list whose elements are supposed to be ArrayLists. Ooops!!!

In reality, the assignment of myList to myList2 is not legal ... and that ensures that it is not possible to add the wrong kind of List<String> to the ArrayList<ArrayList<String>> object. (No Peter, it is not just pedantry :-) )

Stephen C
  • 698,415
  • 94
  • 811
  • 1,216
3

Only the top level collection can be declared as an implementing class, while the nested ones must remain interfaces until you actually create instances:

List<List<String>> rootList = new ArrayList<List<String>>(); 

and then when you create an element to go in, you make it an implementation:

List<String> nodeList = new ArrayList<String>();
rootList.add(nodeList);
amphibient
  • 29,770
  • 54
  • 146
  • 240
  • "... the nested ones must remain interfaces until you actually create instances" This is not true. List> x is a valid declaration and then x = new ArrayList>(); does work. – ars-longa-vita-brevis Jan 31 '14 at 17:56
1

Its comparing Type from left(declaration) side to Type from right(instantiation) side. In Left, your type is List<String> while in right, it's ArrayList<String>. If complaining about the difference.

Please update the right side(instatiation) as List i.e.

   List<List<String>> myList = new ArrayList<List<String>>();

This should work fine.

Yogendra Singh
  • 33,927
  • 6
  • 63
  • 73
0

I know this is an old question but I just wanted to share my idea.

Instead of making a List of Lists, I personally just make a List of Type[] (List<Type[]> listArray = new ArrayList<Type[]>();), I generate a separate List of just Type (List<Type> list = new ArrayList<Type>();), then .add(list.toArray()). This way, it's clearer and easier to read than the List of Lists syntax which is confusing.

For example, in a recent project where I had an input file where each line with only a "0" meant a new line in the original (it was an encryption algorithm):

String[] input = getInputContents(inFile);
List<String> currentBuffer = new ArrayList<String>();
List<String[]> buffers = new ArrayList<String[]>();

for(String line : input) {
    if(line.equals("0")) {
        buffers.add((String[])currentBuffer.toArray());
        currentBuffer = new ArrayList<String>();
    } else {
        currentBuffer.add(line);
    }
}
0

the list<list<string>> l1=new list<list<string>>(); is allowed if the list contains one more list inside the list.

public final class CPanelXMLBuilder extends PanelXMLBuilder {

    public CPanelXMLBuilder(AuthenticatedUser pAuthenticatedUser, Map<String, Object> pSessionMap, Map<String, Object> pRequestMap, String pPanelTemplate) throws Exception {
        super(pAuthenticatedUser, pSessionMap, pRequestMap, pPanelTemplate, null);
    }

    public Map<String, Object> buildXMLDocument(List<List<String>> pDetailsList) {

        if (pDetailsList.size() == 1) {
            List<String> pCustomerDetail = pDetailsList.get(0);
            xmlDocument.getRootElement().getChild("E_SHOW1").setText(pCustomerDetail.get(0));
            xmlDocument.getRootElement().getChild("E_SHOW2").setText(pCustomerDetail.get(1));
            xmlDocument.getRootElement().getChild("E_SHOW3").setText(pCustomerDetail.get(2));
            xmlDocument.getRootElement().getChild("E_SHOW4").setText(pCustomerDetail.get(3));
            xmlDocument.getRootElement().getChild("E_SHOW5").setText(pCustomerDetail.get(4));
            xmlDocument.getRootElement().getChild("ServerTimestamp").setText(pCustomerDetail.get(5).substring(0, 19));
        } else {
            xmlDocument.getRootElement().getChild("AlertType").setText("INFO");
            xmlDocument.getRootElement().getChild("Alert").setText("There is no matching record.");
        }

        requestMap.put(RequestMapKeys.XML_DOCUMENT, xmlDocument);

        return requestMap;
    }

}
lrnzcig
  • 3,868
  • 4
  • 36
  • 50