16

I'm new to Java and I need to make a list of lists of lists. I could do it in python because an element of a list can be a list so in an embedded list list[0] would refer to a list and list[0][0] would refer to the zeroeth element of the embedded list. Is there any easy way to implement this behavior in java?

Eng.Fouad
  • 115,165
  • 71
  • 313
  • 417

5 Answers5

28

All the other answers are technically correct, but IMHO if you implement a rough List of Lists of Lists you are not treating your data at the right level of abstraction. For example I am pretty sure that a List of Lists already means "something" in your business domain. Encapsulate this "something" in another object so you can just have a List<Something> instead of a difficult to use and maintain List<List<List<Object>>>.

Mario Fusco
  • 13,548
  • 3
  • 28
  • 37
  • 2
    In some languages (admittedly, not typically Java, due to its verbose syntax), using a list-of-list-of-list is quite common for prototyping. For exploratory programming, it can be much easier to make changes when you don't have explicit names embedded everywhere. Once you're writing an enterprisey web app, sure, you need "right level of abstraction" and "your business domain", but if you're writing a program to do "I don't know what yet but let's see where this goes", lists are great. The Linux kernel didn't start out perfectly factored, either, or even as an OS kernel. – Ken Jun 23 '10 at 18:05
  • 8
    I heard this justification thousands of times in my working life: "I will start with a List of List of Lists, I will see what happen and then I will refactor it." This is a wishful think in the bast case or more probably just a lie and the final result of it is that the awful List of List of Lists will remain forever in your code. If you already know there is a better way to write your code (and it seems you agree that you can do something better) why don't start with the best possible way? – Mario Fusco Jun 23 '10 at 18:32
  • You'll spend just few minutes to encapsulate... just do it and be elegant ! – Magno C Sep 01 '15 at 13:46
12

As Mario says, you probably need to abstract out your data a little further. But, the following will do what you need.

In Java you would so something like:

List<List<List<Object>>> listOfListsOfLists =new ArrayList<List<List<Object>>>();

Then to access the items, you would use:

listOfListsOfLists.get(a).get(b).get(c);

Or, to iterate over everything:

for (List<List<Object>> list2: listOfListsOfLists) {
    for (List<Object> list1: list2) {
        for (Object o: list1) {
            // use `o`
        }
    }
}
Community
  • 1
  • 1
jjnguy
  • 136,852
  • 53
  • 295
  • 323
5

Since all of these answers make me barf, can I just add the suggestion that you either

  1. Create a data type to express your data while encapsulating the details of the structure, or at least

  2. Create a key type that wraps an int[] (but overrides equals and hashCode properly) and use a HashMap instead? It's typically rare that your whole 3-dimensional structure will be filled up much anyway.

Even better you could encapsulate that map and use varargs for clean access.

public class NDimensionalArray<V> {
    private final int dimensions;
    private final Map<Key, V> values = new HashMap<Key, V>();

    private NDimensionalArray(int dimensions) {
        this.dimensions = dimensions;
    }

    public V get(int... indices) {
        checkIndices(indices);
        return values.get(new Key(indices));
    }

    public void set(V value, int... indices) {
        checkIndices(indices);
        values.put(new Key(indices), value);
    }

    private void checkIndices(int[] indices) {
        if ( indices.length != dimensions ) {
            throw new IllegalArgumentException();
        }
    }

    private static final class Key {
        private final int[] indices;

        private Key(int[] indices) {
            this.indices = indices;
        }

        @Override
        public int hashCode() {
            return Arrays.hashCode(indices);
        }

        @Override
        public boolean equals(Object obj) {
            return Arrays.equals(indices, ((Key)obj).indices);
        }
    }
}

If people have examples of established collections libraries that already do this sort of thing, let me know and I'll add links.

Mark Peters
  • 80,126
  • 17
  • 159
  • 190
  • Either I am missing something, or there's a flaw here - array hashcodes are based on identity, not on the values in the array - your map keys won't work as expected? A dataholder class which defines a hashCode based on the values would work if the value ranges allow for a non-colliding hashCode (i.e. indexes 0 .. 1000 and hashCode = 1000000 * a[2] + 1000 * a[1] + a[0]) – rsp Jun 23 '10 at 15:20
  • @rsp: Thanks, you're absolutely right. Was a bit too hastily thrown together. It's fixed now (though I still haven't tested it). The hashcode doesn't have to be non-colliding as long as equals is implemented properly (as, after all, all hashcodes for an infinite-space class will eventually collide). – Mark Peters Jun 23 '10 at 15:27
3

While it is certainly true that you can construct a List<List<List<whatever>>> in Java, I can't help but wonder, Why do you want to do this? Not that it's inconceivable that this is the best solution to your problem, but wow, like why?

I guess I could imagine something like

public class Employee ...
List<Employee> store; // all the employees in a store
List<List<Employee>> city; // all the store lists for a city
List<List<List<Employee>>> nation; // all the store lists for the nation

But would you really want to process it that way? I don't know, it depends on what you need to do with it.

Jay
  • 26,876
  • 10
  • 61
  • 112
2

A comprehensive example showing List-of-List with collections and generics (Java 1.5+)

// declare the list of lists
List<List<String>> listOfListOfStrings = new ArrayList<List<String>>();

// populate
List<String> listOfStrings = new ArrayList<String>(); // one inner list
listOfStrings.add("one-one");
listOfStrings.add("one-two");
listOfListOfStrings.add(listOfStrings);

listOfStrings = new ArrayList<String>(); // and another one
listOfStrings.add("two-one");
listOfStrings.add("two-two");
listOfListOfStrings.add(listOfStrings);

// access
String oneOne = listOfListOfStrings.get(0).get(0);   // first element of first inner list
String twoTwo = listOfListOfStrings.get(1).get(1);   // second element of second inner list
Andreas Dolk
  • 113,398
  • 19
  • 180
  • 268
  • 1
    The compiler "thinks" that `add` is a class inside the package `listOfStrings` (which is obviously wrong). `listOfStrings` is a local variable, the declaration is right below the `//populate` comment. Verify your import statements and double check that the code is inside a method. (do you have a class named `add`?) – Andreas Dolk Mar 20 '12 at 14:16
  • I found the error before I saw your comment. (Sorry for deleting it.) I had put the code by mistake on the class but not inside a method. Thanks Andreas_D. – FILIaS Mar 20 '12 at 14:36