Java Arrays are like the Seats in a Car
You can’t drive the seats very far without the rest of the car.
Splitting a String into an Array
Split a String
in Java and we get an array, or more specifically, a String[]
, which is an array of String
.
Here are two examples of String
instances split into arrays of String
.
String[] nameArray = "apple,mango,pear".split(",");
String[] emojiArray = "🍎,🥭,🍐".split(",");
So we can split a String
into an array. So what can the array do for us? The answer is, not very much.
Seats won’t take you far
The only methods we can call on an array are toString
, equals
, and hashCode
. None of these methods are useful on array. Support is needed from other classes like java.util.Arrays
to provide useful versions of these methods.
The following test shows the uselessness of these methods on arrays, and how java.util.Arrays
provides useful versions.
@Test
public void testArrayToStringEqualsHashCodeLength()
{
String[] nameArray = "apple,mango,pear".split(",");
String[] emojiArray = "🍎,🥭,🍐".split(",");
Assertions.assertEquals(
"[Ljava.lang.String;@7193666c", nameArray.toString());
Assertions.assertEquals(
"[Ljava.lang.String;@72057ecf", emojiArray.toString());
Assertions.assertEquals(
"[apple, mango, pear]", Arrays.toString(nameArray));
Assertions.assertEquals(
"[🍎, 🥭, 🍐]", Arrays.toString(emojiArray));
String[] nameArray2 = "apple,mango,pear".split(",");
String[] emojiArray2 = "🍎,🥭,🍐".split(",");
Assertions.assertNotEquals(nameArray, nameArray2);
Assertions.assertNotEquals(emojiArray, emojiArray2);
Assertions.assertNotEquals(
nameArray.hashCode(), nameArray2.hashCode());
Assertions.assertNotEquals(
emojiArray.hashCode(), emojiArray2.hashCode());
Assertions.assertTrue(
Arrays.equals(nameArray, nameArray2));
Assertions.assertTrue(
Arrays.equals(emojiArray, emojiArray2));
Assertions.assertEquals(
Arrays.hashCode(nameArray), Arrays.hashCode(nameArray2));
Assertions.assertEquals(
Arrays.hashCode(emojiArray), Arrays.hashCode(emojiArray2));
Assertions.assertEquals(
nameArray.length, nameArray2.length);
Assertions.assertEquals(
emojiArray.length, emojiArray2.length);
}
While Arrays provides methods for toString
, equals
, and hashCode
, there is not much else Arrays
provides beyond sort
, fill
, and binarySearch
. This methods, while occasionally useful, are not frequently used methods. A lot more is necessary to provide useful functionality for arrays.
The Classic Array from Smalltalk
In the Smalltalk language, Array
is a subtype of Collection
. This gives Array
a wide variety of useful inherited behaviors, in addition to some Array
specific ones. I missed having drivable arrays when I started programming in the Java language. The following shows some of the commonly used iteration methods that were available to the Smalltalk Array class.

When I created Eclipse Collections, it was originally named Caramel, which was roughly an acronym for Collection, Array, Map Enumeration Library. My first goal for Caramel was to provide a rich and consistent set of functional iteration methods for Java Collections, Arrays and Maps. Arrays were the least useful of the Collection-like objects in Java. In the early days of Caramel, I created a utility class named ArrayIterate
, which provided useful iteration methods for Java arrays.
Turbo-charging Java Arrays with my favorite Smalltalk methods
ArrayIterate
adds support for select
, reject
, collect
, detect
, injectInto
, anySatisfy
, allSatisfy
, noneSatisfy
to Java arrays. These are some of my favorite collection methods that were inspired by the Smalltalk Collection class.
The following test shows examples of each of these methods on ArrayIterate
working with Java arrays.
@Test
public void myFavoriteSmalltalkMethodsWorkWithJavaArrays()
{
String[] nameArray = "apple,mango,pear".split(",");
String[] emojiArray = "🍎,🥭,🍐".split(",");
MutableList<String> selected =
ArrayIterate.select(nameArray, "mango"::equals);
Assertions.assertEquals(List.of("mango"), selected);
MutableList<String> rejected =
ArrayIterate.reject(emojiArray, "🥭"::equals);
Assertions.assertEquals(List.of("🍎", "🍐"), rejected);
MutableList<String> collected =
ArrayIterate.collect(nameArray, String::toUpperCase);
Assertions.assertEquals(List.of("APPLE", "MANGO", "PEAR"), collected);
String detected =
ArrayIterate.detect(emojiArray, "🍐"::equals);
Assertions.assertEquals("🍐", detected);
MutableList<Object> injected =
ArrayIterate.injectInto(
Lists.mutable.empty(),
nameArray,
MutableList::with);
Assertions.assertEquals(List.of("apple", "mango", "pear"), injected);
Assertions.assertTrue(ArrayIterate.anySatisfy(nameArray, "mango"::equals));
Assertions.assertFalse(ArrayIterate.allSatisfy(emojiArray, "🥭"::equals));
Assertions.assertTrue(ArrayIterate.noneSatisfy(emojiArray, "🍋"::equals));
}
Missing wheels, doors, windows, steering wheel
Arrays are only as useful as seats in a car in Java, so we have to add everything that is missing. This includes basic Collection
methods like contains
and forEach
. While we were at it, we supplemented arrays with more advanced features like forEachWithIndex
, forEachInBoth
, zip
, and zipWithIndex
.
ArrayIterate.contains
The simplest iteration method that is available on all Collections and Maps is contains
. There is no contains equivalent for arrays. ArrayIterate
lets us check if an array contains
an Object.
@Test
public void contains()
{
String[] nameArray = "apple,mango,pear".split(",");
String[] emojiArray = "🍎,🥭,🍐".split(",");
Assertions.assertTrue(ArrayIterate.contains(nameArray, "mango"));
Assertions.assertTrue(ArrayIterate.contains(emojiArray, "🥭"));
Assertions.assertFalse(ArrayIterate.contains(nameArray, "lemon"));
Assertions.assertFalse(ArrayIterate.contains(emojiArray, "🍋"));
}
ArrayIterate.forEach
Since Iterable
, Collection
and Map
all got forms of forEach
in Java 8, it makes sense for arrays to have forEach
support as well. There is a forEach
method on ArrayIterate
which takes a Functional Interface named Procedure
as an argument.
@Test
public void forEach()
{
String[] nameArray = "apple,mango,pear".split(",");
String[] emojiArray = "🍎,🥭,🍐".split(",");
List<String> result = new ArrayList<>();
ArrayIterate.forEach(nameArray, result::add);
Assertions.assertEquals(
List.of("apple", "mango", "pear"), result);
ArrayIterate.forEach(emojiArray, result::add);
Assertions.assertEquals(
List.of("apple", "mango", "pear", "🍎","🥭","🍐"), result);
}
ArrayIterate.forEachWithIndex
Arrays are indexed data structures. While they don’t have get
methods like a list, we can reference elements at indexes using array[index]
syntax. ArrayIterate
has a method name forEachWithIndex
which takes an array and a Functional Interface named ObjectIntProcedure
as arguments. The following test shows some examples of using forEachWithIndex
.
@Test
public void forEachWithIndex()
{
String[] nameArray = "apple,mango,pear".split(",");
String[] emojiArray = "🍎,🥭,🍐".split(",");
List<String> result = new ArrayList<>();
ArrayIterate.forEachWithIndex(nameArray,
(each, index) -> result.add(index, each));
Assertions.assertEquals(
List.of("apple", "mango", "pear"), result);
ArrayIterate.forEachWithIndex(emojiArray,
(each, index) -> result.add(index, each));
Assertions.assertEquals(
List.of("🍎", "🥭", "🍐", "apple", "mango", "pear"), result);
}
ArrayIterate.forEachInBoth
If we have two arrays of the same size, we can iterate over them together using forEachInBoth
. The forEachInBoth method takes two arrays and a Functional Interface named Procedure2
as arguments.
@Test
public void forEachInBoth()
{
String[] nameArray = "apple,mango,pear".split(",");
String[] emojiArray = "🍎,🥭,🍐".split(",");
Map<String, String> expected =
Map.of("apple", "🍎", "mango", "🥭", "pear", "🍐");
Map<String, String> result = new LinkedHashMap<>();
ArrayIterate.forEachInBoth(nameArray, emojiArray, result::put);
Assertions.assertEquals(expected, result);
}
ArrayIterate.zip
An alternative to forEachInBoth
is the method zip
, which can take two arrays and return a MutableList<Pair>
.
@Test
public void zip()
{
String[] nameArray = "apple,mango,pear".split(",");
String[] emojiArray = "🍎,🥭,🍐".split(",");
Map<String, String> expected =
Map.of("apple", "🍎", "mango", "🥭", "pear", "🍐");
MutableList<Pair<String, String>> zipped =
ArrayIterate.zip(nameArray, emojiArray);
Map<String, String> result1 =
zipped.toMap(Pair::getOne, Pair::getTwo);
Assertions.assertEquals(expected, result1);
Map<String, String> result2 =
zipped.toMap(Pair::getOne, Pair::getTwo, new LinkedHashMap<>());
Assertions.assertEquals(expected, result2);
}
ArrayIterate.zipWithIndex
In addition to forEachWithIndex
, ArrayIterate has support for zipWithIndex
, which zips each element with its corresponding index as a Pair<Element, Integer>
in a MutableList
.
@Test
public void zipWithIndex()
{
String[] nameArray = "apple,mango,pear".split(",");
String[] emojiArray = "🍎,🥭,🍐".split(",");
MutableList<Pair<String, Integer>> zippedNames =
ArrayIterate.zipWithIndex(nameArray);
MutableList<Pair<String, Integer>> zippedEmojis =
ArrayIterate.zipWithIndex(emojiArray);
List<Triple<String, String, Integer>> result =
Lists.mutable.empty();
zippedNames.forEachInBoth(
zippedEmojis,
(pair1, pair2) ->
result.add(
Tuples.triple(
pair1.getOne(),
pair2.getOne(),
pair1.getTwo() + pair2.getTwo())));
List<Triple<String, String, Integer>> expected = List.of(
Tuples.triple("apple", "🍎", 0),
Tuples.triple("mango", "🥭", 2),
Tuples.triple("pear", "🍐", 4));
Assertions.assertEquals(expected, result);
}
ArrayIterate.makeString
The last method of ArrayIterate
we will look at is makeString
. We used split
on a comma separated String
to create two arrays. We will use makeString
to create two comma separated String
instances.
@Test
public void makeString()
{
String[] nameArray = "apple,mango,pear".split(",");
String[] emojiArray = "🍎,🥭,🍐".split(",");
String names = ArrayIterate.makeString(nameArray, ",");
String emojis = ArrayIterate.makeString(emojiArray, ",");
Assertions.assertEquals("apple,mango,pear", names);
Assertions.assertEquals("🍎,🥭,🍐", emojis);
}
Symmetry for the Win
There are many other methods available on ArrayIterate
. Instead of listing them all and showing examples, here’s a link to the Javadoc. The symmetry between ArrayIterate
and interfaces like MutableList
is not perfect, but is very good.
If we want arrays to have the full capabilities of MutableList
, we can move from using the ArrayIterate
static utility class to wrapping a Java array in an ArrayAdapter
. The benefit of the ArrayIterate
utility class is that it saves an object creation. ArrayAdapter
creates a lightweight wrapper around an array. ArrayAdapter
provides perfect symmetry with the MutableList
interface, because ArrayAdapter
implements the MutableList
interface.
Here’s what my favorite Smalltalk methods would look like using an ArrayAdapter
instead of ArrayIterate
.
@Test
public void myFavoriteSmalltalkMethodsWorkWithArrayAdapters()
{
MutableList<String> nameArray =
ArrayAdapter.adapt("apple,mango,pear".split(","));
MutableList<String> emojiArray =
ArrayAdapter.adapt("🍎,🥭,🍐".split(","));
MutableList<String> selected =
nameArray.select("mango"::equals);
Assertions.assertEquals(List.of("mango"), selected);
MutableList<String> rejected =
emojiArray.reject("🥭"::equals);
Assertions.assertEquals(List.of("🍎", "🍐"), rejected);
MutableList<String> collected =
nameArray.collect(String::toUpperCase);
Assertions.assertEquals(List.of("APPLE", "MANGO", "PEAR"), collected);
String detected =
emojiArray.detect("🍐"::equals);
Assertions.assertEquals("🍐", detected);
MutableList<Object> injected =
nameArray.injectInto(Lists.mutable.empty(), MutableList::with);
Assertions.assertEquals(List.of("apple", "mango", "pear"), injected);
Assertions.assertTrue(nameArray.anySatisfy("mango"::equals));
Assertions.assertFalse(emojiArray.allSatisfy("🥭"::equals));
Assertions.assertTrue(emojiArray.noneSatisfy("🍋"::equals));
}
What about Java Stream?
Since Java 8 was released, we can use Stream
to augment Java arrays with useful behaviors. Unfortunately, a Stream
can only be used once, and requires more bun methods than ArrayIterate
or ArrayAdapter
. Using Stream is like renting a new car every time you want to take a drive. Here’s an example of filter
and map
with Stream
to provide the same functionality as select
, reject
, collect
, detect
, any
/all
/noneSatisfy
in the example above.
@Test
public void filterMapFindFirstAnyAllNoneWithArrayAsStream()
{
String[] nameArray = "apple,mango,pear".split(",");
String[] emojiArray = "🍎,🥭,🍐".split(",");
List<String> filtered =
Stream.of(nameArray)
.filter("mango"::equals)
.toList();
Assertions.assertEquals(List.of("mango"), filtered);
List<String> filteredNot =
Stream.of(emojiArray)
.filter(Predicate.not("🥭"::equals))
.toList();
Assertions.assertEquals(List.of("🍎", "🍐"), filteredNot);
List<String> mapped =
Stream.of(nameArray)
.map(String::toUpperCase)
.toList();
Assertions.assertEquals(List.of("APPLE", "MANGO", "PEAR"), mapped);
String found =
Stream.of(emojiArray)
.filter("🍐"::equals)
.findFirst()
.orElse(null);
Assertions.assertEquals("🍐", found);
MutableList<String> collected =
Stream.of(nameArray)
.collect(Lists.mutable::empty,
MutableList::with,
MutableList::withAll);
Assertions.assertEquals(List.of("apple", "mango", "pear"), collected);
Assertions.assertTrue(Stream.of(nameArray).anyMatch("mango"::equals));
Assertions.assertFalse(Stream.of(emojiArray).allMatch("🥭"::equals));
Assertions.assertTrue(Stream.of(emojiArray).noneMatch("🍋"::equals));
}
As we can see, for each method call above, we have to rewrap the array in a Stream
, and then call a final “bun” method like toList
or findFirst
to achieve some computed result. ArrayIterate
provides the methods directly executing against the array. ArrayAdapter
allows for the adapter to be reused as many times as we want.
Final Thoughts
Java arrays may not be the most useful data structures by themselves, but they have gotten a lot of help from some useful friends in Eclipse Collections and Java 8+. The ArrayIterate
utility, ArrayAdapter
class, and Java Stream
all provide useful higher level methods that work with Java arrays.
I hope you find this blog helpful. Next time you have a Java array you need to do something with, remember you have ArrayIterate
, ArrayAdapter
, and Java Stream
available to help you.
Thanks for reading!
I am the creator of and committer for the Eclipse Collections OSS project, which is managed at the Eclipse Foundation. Eclipse Collections is open for contributions.