3

Why generateEmptyArrayBySize method is receiving 0 as input, instead of 3? I was expecting to receive the list size.

public class CollectionToArrayTest {
    public static void main(String[] args) {
        var list = List.of(1, 2, 3);
        var array = list.toArray(CollectionToArrayTest::generateArrayBySize);
        out.println("array: " + Arrays.toString(array)); // array: [1, 2, 3]
    }

    private static Integer[] generateArrayBySize(int arraySize) {
        out.println("arraySize: " + arraySize); // arraySize: 0
        return new Integer[arraySize];
    }
}
zeugor
  • 822
  • 1
  • 8
  • 18
  • Just to point out the real reason for having a toArray method with an T[] or Supplier as param is because in a generic class we can't create an array of the generic type. new T[]{} is not valid. – zeugor Mar 09 '21 at 16:23

2 Answers2

4

If you look at the implementation of the Collection.toArray method you'll see:

return toArray(generator.apply(0));

On the other side, the implementation of the IntFunction that you have looks similar to:

new IntFunction<Integer[]>() {
    @Override
    public Integer[] apply(int arraySize) { // check the parameter here
        return generateArrayBySize(arraySize);
    }
}

So the value passed on to the generateArrayBySize is the same as that passed to the apply method of the IntFunction, i.e. 0. Ofcourse, a straight forward way to transform the list into an array would be:

var array = list.toArray(Integer[]::new);
Naman
  • 27,789
  • 26
  • 218
  • 353
  • [This Q&A](https://stackoverflow.com/questions/4042434/converting-arrayliststring-to-string-in-java) holds certain relevance of how the API has been framed. – Naman Jan 04 '21 at 18:07
  • Reading doc: default T[] toArray​(IntFunction generator): The default implementation calls the generator function with zero and then passes the resulting array to toArray(T[]); and then doc for T[] toArray​(T[] a): a - the array into which the elements of this collection are to be stored, if it is big enough; otherwise, a new array of the same runtime type is allocated for this purpose. In my opinion, quite inefficient, even if we create a fancy IntFunction generator, it is useless and we're going to create always 2 array objects, where 1st array with '0' zero is useless – zeugor Jan 04 '21 at 18:12
  • Interesting point: list.stream().toArray(EntityPermissionService::generateArrayBySize) passes the right array length as input parameter to the array generator. So it might be recommended way – zeugor Jan 04 '21 at 18:14
  • well the trick is, given the resolution of the method reference the code you have shared should fail to execute when using streams, the reason being array containing single element(`0`) is passed on with the underlying implementation which checks for equality of size of the stream and the arrays fixed length determined which I am guessing should be for cases like `new Integer[arraySize]`..besides the correct way to transform should not require you to create any additional stream from the collection. – Naman Jan 04 '21 at 18:46
  • 5
    @zeugor it is [not the recommended way](https://shipilev.net/blog/2016/arrays-wisdom-ancients/). `[0]` _is_. – Eugene Jan 04 '21 at 18:48
  • 4
    @zeugor the method does precisely serve its purpose, i.e. that the developer using it does not need to think about it further: 1) it’s working correctly even when being invoked on a concurrent collection while concurrent modifications are in progress (which trying to pre-size the array won’t) 2) it’s guarding you from the naïve assumption that presizing the array was always more efficient (check Eugene’s link regarding the commonly used HotSpot JVM) 3) at the same time, it can be overridden by a particular collection if it provides a benefit in a particular environment. – Holger Jan 05 '21 at 09:34
0

When you call Collection#toArray method, its internal parameter, i.e. array size, is 0 due to the implementation:

default <T> T[] toArray(IntFunction<T[]> generator) {
    return toArray(generator.apply(0));
}

But you can explicitly set it greater than the actual size:

public static void main(String[] args) {
    var list = List.of(1, 2, 3);
    var array = list.toArray(q -> {
        System.out.println("arraySize: " + q); // arraySize: 0
        return generateArrayBySize(4);
    });
    System.out.println(Arrays.toString(array)); // [1, 2, 3, null]
}
private static Integer[] generateArrayBySize(int arraySize) {
    System.out.println("arraySize: " + arraySize); // arraySize: 4
    return new Integer[arraySize];
}

See also: Move null elements for each row in 2d array to the end of that row