1

when we have to convert single-dimensional array List type, we can simply use Arrays.asList(). But in the context of multidimensional arrays, I did not find anything similar to this. I have to manually create a List<List<T>> and fill values as per original multidimensional array. Is there any decent way to convert between these types? Java 8 way?

Andronicus
  • 25,419
  • 17
  • 47
  • 88

3 Answers3

4

You can use Arrays.stream and map inner arrays:

<T> List<List<T>> toNestedList(T[][] array) {
    return Arrays.stream(array)
        .map(Arrays::asList)
        .collect(Collectors.toList());
}

This will create a stream of inner arrays, map them to list and collect all inner lists to an outer one.

Andronicus
  • 25,419
  • 17
  • 47
  • 88
2

The currently proposed solution creates a new list. This means that the array has to be traversed and memory has to be allocated.

Analogously to Arrays#asList, it could be desirable to not create a new list, but only a view on the given array instead. This way, even if the array contains 1000000 elements, the memory- and performance overhead of creating this list-view is practically zero.

An example of how this could be implemented:

import java.util.AbstractList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.RandomAccess;

public class NestedAsList
{
    public static void main(String[] args)
    {
        String array[][] =
        {
            { "This", "is", "an" },
            { "example", "of", }, null,
            { "a", "jagged", "array" } 
        };

        List<List<String>> lists = asLists(array);
        lists.forEach(System.out::println);
    }

    /**
     * Returns a <i>view</i> on the given array, as a list where each element is
     * a <i>view</i> on the respective array element. When the array contains
     * <code>null</code> elements, then the list will contain an empty list at
     * this index.
     * 
     * @param array The array
     * @return The list view
     */
    private static <T> List<List<T>> asLists(T array[][])
    {
        Objects.requireNonNull(array);
        class ResultList extends AbstractList<List<T>> implements RandomAccess
        {
            @Override
            public List<T> get(int index)
            {
                T[] a = array[index];
                if (a == null)
                {
                    // Could return null or the empty list here...
                    return Collections.emptyList();
                }
                return Arrays.asList(a);
            }

            @Override
            public int size()
            {
                return array.length;
            }

        }
        return new ResultList();
    }
}

(By the way: I wonder where you get a multidimensional generic array from. These should be rare nowadays...)


Note: The answer has been updated, based on a comment by Holger: It can be beneficial to let the returned list also implement the java.util.RandomAccess interface, to indicate that it has constant-time indexed access. Many of the static utility methods in the Collections class check whether a given list is RandomAccess, and are able to perform their task more efficiently in this case.

Unfortunately, one has to create a new, local class in order to implement the additional interface, but depending on how the resulting class is supposed to be used, this can be worthwhile.

The original implementation of the asLists method was this one, which simply returned a new AbstractList, not implementing the RandomAccess interface:

private static <T> List<List<T>> asLists(T array[][])
{
    Objects.requireNonNull(array);
    return new AbstractList<List<T>>()
    {

        @Override
        public List<T> get(int index)
        {
            T[] a = array[index];
            if (a == null)
            {
                // Could return null or the empty list here...
                return Collections.emptyList();
            }
            return Arrays.asList(a);
        }

        @Override
        public int size()
        {
            return array.length;
        }
    };
}
Marco13
  • 53,703
  • 9
  • 80
  • 159
  • That is inspiring. Just curious - where do you have this idea from? – Andronicus Sep 11 '19 at 16:38
  • 1
    @Andronicus It's hard to name a definite "source" of this idea, but it's basically the same as `Arrays.asList` does, for 2 dimensions. More generally: "Letting **anything** that has a size and indexed access appear as a `List`" is one of the ideas behind `AbstractList`. I also used this e.g. in https://stackoverflow.com/a/53032263/3182664 , and some places in my own codebase. The nicest thing is that there is no memory- or performance overhead. (One could argue that with each access, `Arrays.asList` creates a new instance, but this is in turn also only a view, and thus, negligible) – Marco13 Sep 11 '19 at 18:36
  • 1
    It’s beneficial to let this class implement `RandomAccess`… – Holger Sep 18 '19 at 10:26
  • @Holger That's a valid point (and considering how often I'm using this pattern in my own codebase, it's somewhat embarassing that I overlooked this until now). Unfortunately, one has to define a local class for that, which is a bit clumsy compared to the `return new AbstractList`, but it may indeed be worthwhile. I'll probably have an `AbstractRandomAccessList` soon. Updated the answer with the new implementation and additional information. – Marco13 Sep 18 '19 at 10:49
  • @Marco13 I need to say, that I think every answer of yours (out of more than a thousand) is a little masterpiece. I can get knowledge as well as inspiration from them. I believe they reflect a mind behind them, that I would like to become some day - thoughtful, thorough and comprehensive. I'm really sorry you stopped contributing to the site, but I understand the rationale. If you ever want to come back, be sure you have an audience for your great input. – Andronicus Jan 03 '20 at 20:34
0

Above solution is correct but does not handle null rows in the 2D array so I have below solution using stream APIs from Java 8 and it will work for null rows and generic arrays as well.

import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

public class TwoDArrayToListOfList {
    public static void main(String[] args) {
        String [][] stringArray = new String[][]{{"John", "Alley", "Neon"}, {null}, {"Huston", "Gary"}, {"Ellen", "James", "Chris", "Jacob"}};
        List<List<String>> stringList = getListFrom2DArray(stringArray);
        stringList.forEach(list -> list.forEach(System.out::println));

        Integer [][] intArray = new Integer[][]{{1,3, 6}, {2, 4}, {null}, {6, 4, 3, 5}, {null}};
        List<List<Integer>> intList = getListFrom2DArray(intArray);
        intList.forEach(list -> list.forEach(System.out::println));

        Double [][] doubleArray = new Double[][]{{1.0,3.9, 6.7}, {2.4, 4.4}, {6.5, 4.1, 3.3, 5.2}, {null}};
        List<List<Double>> doubleList = getListFrom2DArray(doubleArray);
        doubleList.forEach(list -> list.forEach(System.out::println));
    }

    private static <T> List<List<T>> getListFrom2DArray(T[][] array) {
        return Arrays.stream(Objects.requireNonNull(array)).map(row -> Arrays.asList((row != null) ? row : (T[]) new Object[0])).collect(Collectors.toList());
    }
}
tejp124
  • 356
  • 3
  • 12