0

I know that the Java Stream<> interface provides several ways to convert a stream to an array. But the Collection.toArray(T[] array) method is a little different. It has a few clever (at the time) requirements, including:

  • If the array you pass is big enough, the array you pass must be used; otherwise a new one must be created.
  • If the array is bigger than necessary, null must be added after the last element.

So if my Collection<T> implementation retrieves its values from some Stream<FooBar> (with a converter strategy that converts to T, how can I convert from my stream to the array required by Collection.toArray(T[] array)?

Without a lot of thought, it would seem I have to do this:

@Override
public <T> T[] toArray(T[] array) {
  try (final Stream<FooBar> stream = getStream()) {
    T[] result = stream.map(converter::toT).toArray(length ->
        (T[])Array.newInstance(array.getClass(), length));
    if(result.length <= array.length) {
      System.arraycopy(result, 0, array, 0, result.length);
      if(result.length < array.length) {
        array[result.length] = null;
      }
      result = array;
    }
  }
  return result;
}

But is there some more concise way to do this? Is there some way I could transfer the stream directly into the given array if possible? And does the Stream<> API already provide for something like this: creating an array as the Collection<>.toArray(T[] array) API expects?

Response to possible duplicates

I know how to convert a stream to an array. This question is very specific: how to follow the contract of Collection.toArray(T[] array). I don't think this is a duplicate, for two reasons: the other answers do not re-use an existing array if it is big enough, and they don't mark an element with null if the existing array is too big.

8
  • 1
    Here you can find it: stackoverflow.com/questions/23079003/… Commented Nov 13, 2018 at 16:46
  • @nullpointer, this is not a duplicate. Please explain how this is a duplicate. I know how to convert a stream to an array. I'm asking how to most efficiently create stream to an array with the added restrictions that the Collections.toArray(T[] array) provides. Did you read that part of the question? Commented Nov 13, 2018 at 21:49
  • 2
    The easiest way would be for your collection to extend AbstractCollection, and let the default implementation do its job. Commented Nov 13, 2018 at 22:09
  • 2
    If performance is not a major concern, you can defer to the JDK: getStream().collect(Collectors.toList()).toArray(array) Commented Nov 13, 2018 at 22:13
  • OK so may I ask why you need that? Holger already said in his answer what happens in java-11, you can even read the answer from the method creator himself... so why would you need that? Commented Nov 15, 2018 at 8:10

1 Answer 1

2

A very recommended read is the article Arrays of Wisdom of the Ancients.

In short, contrary to intuition, passing a pre-sized array to the Collections.toArray(T[]) method turns out to be less efficient than passing a zero sized array, which only serves to determine the result type but lets the collection allocate the result array.

That’s why Java 11’s new default method <T> T[] toArray​(IntFunction<T[]> generator) does not use the function to allocate an array of the collection’s size but rather to allocate a zero sized array to be passed to <T> T[] toArray​(T[] a).

So it’s worth rethinking whether you really want such a contract for a method or which actual use cases you really want to optimize for (as you can’t serve all at once).

E.g. considering that passing a zero sized array is the most efficient choice anyway, you could optimize for exactly that case

@Override
public <T> T[] toArray(T[] array) {
    T[] template = array.length == 0? array: Arrays.copyOf(array, 0);
    try(Stream<FooBar> stream = getStream()) {
        T[] result = stream.map(converter::toT)
            .toArray(length -> Arrays.copyOf(template, length));
        if(result.length > array.length) return result;
        System.arraycopy(result, 0, array, 0, result.length);
        if(result.length < array.length) array[result.length] = null;
        return array;
    }
}

Note that when you have to implement that method because you’re implementing a Collection, there are plenty of helpful abstract base classes in the JDK which provide an implementation already.

You could even utilize such an implementation when you are not implementing a collection, e.g.

public <T> T[] toArray(T[] array) {
    try(final Stream<FooBar> stream = getStream()) {
        return new AbstractCollection<XYZ>() {
            public Iterator<XYZ> iterator() {
                return stream.map(converter::toT).iterator();
            }
            public int size() { return 0; } // don't know beforehand
        }.toArray(array);
    }
}

You have to replace XYZ with the return type of the converter.toT(FooBar) method.

Which leads to the bigger question, how converter::toT is supposed to convert to the right type without actually knowing what T is.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.