6

I have upgraded Eclipse Photon 4.8 (http://download.eclipse.org/eclipse/downloads/drops4/S-4.9M2-201808012000/) to support JDK 11 (https://marketplace.eclipse.org/content/java-11-support-eclipse-photon-49). It seems to be working fine (Version: 4.9 Build id: I20180801-2000).

In JDK 11 there is a new override of method toArray() in Java.util.Collection:

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

It is a default method, but it is not overriden. All it does is pass the value returned by the supplied generator function (using a hard-coded argument of zero) to another override of toArray() which then returns the content of the Collection as an array.

As described in the Javadoc for that method, it can be called like this:

String[] y = x.toArray(String[]::new);

That works fine, and an array of String of the appropriate length, corresponding to the Collection<String>, is returned.

The Javadoc also states that "the default implementation calls the generator function with zero and then passes the resulting array to toArray(T[])".

If I provide my own generator function it does get called (as shown by the println() console output), but the return value of its apply() method seems to be ignored. It's as though I had called toArray(String[]::new) regardless of the content of the array returned by my generator function.

Here's the MCVE:

package pkg;

import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.function.IntFunction;

public class App {
    public static void main(String[] args) {

        IntFunction<String[]> intFunc = (int sz) -> {
            System.out.println("intFunc: sz: " + sz);
            if (sz == 0) {
                sz = 3;
            }
            String[] array = new String[sz];
            for (int i = 0; i < sz; i++) {
                array[i] = Character.toString('A' + i);

            }
            System.out.println("intFunc: array to be returned: " + Arrays.toString(array));
            return array;
        };

        Collection<String> coll = List.of("This", "is", "a", "list", "of", "strings");

        // Correctly returns the collection as an array, as described in JDK11 Javadoc.
        String[] array1 = coll.toArray(String[]::new);
        System.out.println("array1: " + Arrays.toString(array1) + '\n');

        // Use generator function to return a different collection as an array - doesn't work.      
        String[] array2 = coll.toArray(intFunc);
        System.out.println("array2: " + Arrays.toString(array2) + '\n');

        // Use generator function to return a different collection as an array - doesn't work.
        String[] array3 = coll.toArray(intFunc.apply(coll.size()-2));
        System.out.println("array3: " + Arrays.toString(array3));
    }
}

Here's the console output produced by running the MCVE:

array1: [This, is, a, list, of, strings]

intFunc: sz: 0

intFunc: array to be returned: [A, B, C]

array2: [This, is, a, list, of, strings]

intFunc: sz: 4

intFunc: array to be returned: [A, B, C, D]

array3: [This, is, a, list, of, strings]

The output shows that it doesn't matter what my generator function does - the array it returns is not used.

My question is how do I get this new implementation of toArray() to use the array returned by my generator function, or am I attempting something that is not possible?


Update based on comments and the answer from Nicolai:

The problem with my sample code was not with the generator, but with my test cases. They happened to cause the generator to return an array with fewer elements than the collection, so a new array was allocated instead, to hold exactly the number of elements in the collection.

A test case that returns an array larger than the collection works as expected. For example this code:

    String[] array4 = coll.toArray(intFunc.apply(coll.size() + 3));
    System.out.println("array4: " + Arrays.toString(array4));

gives the following console output:

intFunc: sz: 9

intFunc: array to be returned: [A, B, C, D, E, F, G, H, I]

array4: [This, is, a, list, of, strings, null, H, I]

The SO question Collections emptyList/singleton/singletonList/List/Set toArray explains why there is a null value within the returned array.

skomisa
  • 16,436
  • 7
  • 61
  • 102
  • 1
    Note: Eclipse Photon is version 4.8. Eclipse 4.9 has no special name – greg-449 Aug 28 '18 at 07:34
  • 2
    read the answer [From the creator of those methods himself](https://stackoverflow.com/questions/51902362/collections-emptylist-singleton-singletonlist-list-set-toarray) – Eugene Aug 28 '18 at 11:32
  • @greg-449 You are correct, and the closest thing I can find to a name for 4.9 is "2018-09", which is what I see now when I start Eclipse. The Marketplace link in my opening sentence confusingly refers to "Eclipse Photon (4.9)". I tweaked my opening sentence accordingly. – skomisa Aug 28 '18 at 16:58
  • @Eugene Thanks. That answer was especially helpful regarding concurrency, which I hadn't even considered. – skomisa Aug 28 '18 at 16:58
  • The core code of the next release of Eclipse is version 4.9 it will be part of the 2018-09 Simultaneous Release with many other components. – greg-449 Aug 28 '18 at 18:13

2 Answers2

4

As you pointed out, toArray(IntFunction<T[]>) is a default method that simply forwards to toArray(T[]) (after creating an array with the given function). If you take a closer look at that method, you will find the answer to your question - from the JDK 10 Javadoc (emphasis mine):

Returns an array containing all of the elements in this collection; the runtime type of the returned array is that of the specified array. If the collection fits in the specified array, it is returned therein. Otherwise, a new array is allocated with the runtime type of the specified array and the size of this collection.

For the array you create to be used, it must be long enough to hold the collection's elements, e.g.:

public static void main(String[] args) {
    var createdArray = new AtomicReference<String[]>();
    var usedArray = List.of("A", "B", "C").toArray(__ -> {
        createdArray.set(new String[5]);
        return createdArray.get();
    });

    var message = String.format(
            "%s (length: %d; identical with created array: %s)",
            Arrays.toString(usedArray), usedArray.length, usedArray == createdArray.get());
    System.out.println(message);
}
Nicolai Parlog
  • 47,972
  • 24
  • 125
  • 255
  • 3
    just recently Stuart Marks answered my question about this, with extensive explanations... https://stackoverflow.com/questions/51902362/collections-emptylist-singleton-singletonlist-list-set-toarray – Eugene Aug 28 '18 at 11:33
  • @Nicolai You identified the crucial point that I was missing (i.e. a new array is allocated if the array returned by the generator is too small), and I have added an update to my question accordingly. – skomisa Aug 28 '18 at 16:59
2

The array returned by the generator function isn't being ignored. The component type of the returned array is used. Suppose you have a collection of strings, as in your example:

jshell> Collection<String> coll = List.of("This", "is", "a", "list", "of", "strings")
coll ==> [This, is, a, list, of, strings]

And your generator function is this:

jshell> IntFunction<CharSequence[]> g = x -> {
   ...>   var a = new CharSequence[x];
   ...>   System.out.println(a);
   ...>   return a;
   ...> }
g ==> $Lambda$28/0x00000008000d8040@17d677df

(Note that this prints out the default representation of the array, which includes its identity hash code, not the contents of the array. The array's contents are as expected.)

Then you can copy the strings from the collection into an array of CharSequence:

jshell> System.out.println(coll.toArray(g))
[Ljava.lang.CharSequence;@7d70d1b1
[Ljava.lang.CharSequence;@2a742aa2

Now suppose the source is an empty collection:

jshell> System.out.println(List.of().toArray(g))
[Ljava.lang.CharSequence;@3dfc5fb8
[Ljava.lang.CharSequence;@3dfc5fb8

You can see that the zero-size array returned by the generator is of sufficient size to contain zero elements, so it's simply returned.

Stuart Marks
  • 127,867
  • 37
  • 205
  • 259
  • You and your fancy jshell. – Nicolai Parlog Aug 29 '18 at 06:55
  • Got it. My mistake was that I was attempting to use the generator function to effect a transformation, by creating elements in the returned array which differed from their corresponding elements in the collection. That can't be done (though additional new elements can be added at the end of an oversized array). – skomisa Aug 29 '18 at 17:44