So I have:
ArrayList<ArrayList<String>>
Which contains an x number of ArrayLists which contain another y number of Strings.. To demonstrate:
Index 0:
String 1
String 2
String 3
Index 1:
String 4
Index 2:
Index 3:
String 5
String 6
Where index refers to the array index containing a string.
How can I transform this into a 2D array which looks like:
{{String1, String2, String3},{String4}, {}, {String5, String6}}
Thank you so much.
Welcome to a world with Java 8!
It only took me all night with no sleep to learn what was needed to write this one freaking line of code. I'm sure it is already out there somewhere but I couldn't find it. So I'm sharing my hours and hours of research, enjoy. Woot!
Assuming:
ArrayList<ArrayList<String>> mainList = new ArrayList<ArrayList<String>>();
// populate this list here
(Or, rather, in Java 8:
ArrayList<ArrayList<String>> mainList = new ArrayList();
//Populate
)
Then all you need is:
String[][] stringArray = mainList.stream().map(u -> u.toArray(new String[0])).toArray(String[][]::new);
Bam! One line.
I'm not sure how fast it is compared to the other options. But this is how it works:
Take a stream of the mainList
2D ArrayList. This stream is a bit like a Vector hooked up with a LinkedList and they had a kid. And that kid, later in life, dosed up on some NZT-48. I digress; mainList.stream()
is returning a stream of ArrayList<String>
elements. Or in even geekier speak: mainList.stream()
returns a Stream<ArrayList<String>>
, sorta.
Call the .map
function on that stream which will return a new stream with contents that match a new type specified by the parameters passed into map
. This map
function will covert each element in our stream for us. It has a built in foreach
statement. In order to accomplish this; the map
function takes a lambda expression as its parameter. A Lambda expression is like a simple inline one-line function. Which has two data types gettin' Jiggy wit it. First is the type of data in the stream upon which it was called (mainList.stream()
). The next type is the type of data it will map it out to, which is in the right half of the lambda expression: u -> u.toArray(new String[0])
. Here u
is an identifier you choose just like when using a foreach
statement. The first half declares this like so: u ->
. And like a foreach
, the variable u
will now be each element in the stream as it iterates through the stream. Thus, u
is of the data type that the elements of the original stream are because it is them. The right half of the Lambda expression shows what to do with each element: u.toArray(new String[0])
. With the results being stored in their rightful place in a new stream. In this case we convert it to a String[]
.. because after all, this is a 2D array of String
.. or rather from this point in the code, a 1D array of String[]
(string arrays). Keep in mind that u
is ultimately an ArrayList
. Note, calling toArray
from an ArrayList
object will create a new array of the type passed into it. Here we pass in new String[0]
. Therefore it creates a new array of type String[]
and with length equal to the length of the ArrayList u
. It then fills this new array of strings with the contents of the ArrayList
and returns it. Which leaves the Lambda expression and back into map
. Then, map
collects these string arrays and creates a new stream with them, it has the associated type String[]
and then returns it. Therefore, map
returns a Stream<String[]>
, in this case. (Well, actually it returns a Stream<Object[]>
, which is confusing and needs conversion, see below)
Therefore we just need to call toArray
on that new stream of arrays of strings. But calling toArray
on a Stream<Object[]>
is a bit different than calling it on an ArrayList<String>
, as we did before. Here, we have to use a function reference confusing thing. It grabs the type from this: String[][]::new
. That new
function has type String[][]
. Basically, since the function is called toArray it will always be an []
of some sort. In our case since the data inside was yet another array we just add on another []
. I'm not sure why the NZT-48 wasn't working on this one. I would have expected a default call to toArray()
would be enough, seeing that its a stream and all. A stream thats specifically Stream<String[]>
. Anyone know why map
actually returns a Stream<Object[]>
and not a stream of the type returned by the Lambda expression inside?
Now that we got the toArray
from our mainList
stream acting properly. We can just dump it into a local variable easy enough: String[][] stringArray = mainList.stream...
Now, I know some of you are out there going. "This doesn't work for ints!" As was my case. It does however work for "Ents", see above. But, if you want a 2D primitive int
array from a 2D ArrayList
of Integer
(ie. ArrayList<ArrayList<Integer>>
). You gotta change around that middle[earth] mapping. Keep in mind that ArrayLists can't have primitive types. Therefore you can't call toArray
on the ArrayList<Integer>
and expect to get a int[]
. You will need to map it out... again.
int[][] intArray = mainList.stream().map( u -> u.stream().mapToInt(i->i).toArray() ).toArray(int[][]::new);
I tried to space it out for readability. But you can see here that we have to go through the same whole mapping process again. This time we can't just simply call toArray
on the ArrayList u
; as with the above example. Here we are calling toArray
on a Stream
not an ArrayList
. So for some reason we don't have to pass it a "type", I think its taking brain steroids. Therefore, we can take the default option; where it takes a hit of that NZT-48 and figures out the obvious for us this [run]time. I'm not sure why it couldn't just do that on the above example. Oh, thats right.... ArrayLists don't take NZT-48 like Streams do. Wait... what am I even talking about here?
Annnyhoow, because streams are sooo smart. Like Sheldon, we need a whole new protocol to deal with them. Apparently advanced intelligence doesn't always mean easy to deal with. Therefore, this new mapToInt
is needed to make a new Stream
which we can use its smarter toArray
. And the i->i
Lambda expression in mapToInt
is a simple unboxing of the Integer
to int
, using the implicit auto-unboxing that allows int = Integer
. Which, now, seems like a dumb trivial thing to do, as if intelligence has its limits. During my adventure learning all this: I actually tried to use mapToInt(null)
because I expected a default behavior. Not an argument!! (cough Sheldon cough) Then I say in my best Husker valley girl accent, "Afterall, it is called mapToInt
, I would guess that, like, 84% of the time (42x2) it will be, like, passed i->i
by, like, everyone, so like, omgawd!" Needless to say, I feel a bit... like.. this guy. I don't know, why it doesn't work that way.
Well, I'm red-eye and half delirious and half asleep. I probably made some mistakes; please troll them out so I can fix them and let me know if there is an even better way!
PT