0
String[] stringArray = streamString.toArray(size -> new String[size]);

How it takes the size as stream's size automatically?

Stuart Marks
  • 127,867
  • 37
  • 205
  • 259
l a s
  • 3,836
  • 10
  • 42
  • 61
  • Could you please elaborate? What didn't you understand [about it](https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html#toArray-java.util.function.IntFunction-)? – lifus Feb 11 '15 at 20:40
  • 1
    You can replace `size -> new String[size]` with `String[]::new`, btw. – Holger Feb 11 '15 at 20:54

2 Answers2

6

The Stream API is formed around Spliterator which is an advanced form of iterators. These can report certain characteristics, allowing optimizations of the operations a Stream will apply. They may also report the expected number of elements, either estimated or exact. A Spliterator will report a SIZED characteristic if it knows the number of elements in advance.

You can test which knowledge about its elements a Stream has, given the encapsulated operations, using the following method:

public static <T> Stream<T> printProperties(String op, Stream<T> s) {
    System.out.print("characteristics after "+op+": ");
    Spliterator<T> sp=s.spliterator();
    int characteristics=sp.characteristics();
    if(characteristics==0) System.out.println("0");
    else {
        String str;
        for(;;) {
            int flag=Integer.highestOneBit(characteristics);
            switch(flag) {
                case ORDERED: str="ORDERED"; break;
                case DISTINCT: str="DISTINCT"; break;
                case SORTED: str="SORTED"; break;
                case SIZED: str="SIZED"; break;
                case NONNULL: str="NONNULL"; break;
                case IMMUTABLE: str="IMMUTABLE"; break;
                case CONCURRENT: str="CONCURRENT"; break;
                case SUBSIZED: str="SUBSIZED"; break;
                default: str=String.format("0x%X", flag);
            }
            characteristics-=flag;
            if(characteristics==0) break;
            System.out.append(str).append('|');
        }
        System.out.println(str);
    }
    return StreamSupport.stream(sp, s.isParallel());
}

You can use it to learn how certain operations influence the knowledge about the elements. E.g., when you use this method with the following test program:

Stream<Object> stream;
stream=printProperties("received from TreeSet", new TreeSet<>().stream() );
stream=printProperties("applying map", stream.map(x->x) );
stream=printProperties("applying distinct", stream.distinct() );
stream=printProperties("filtering", stream.filter(x->true) );
stream=printProperties("applying sort", stream.sorted() );
stream=printProperties("requesting unordered", stream.unordered() );

System.out.println();

stream=printProperties("received from varargs array", Stream.of("foo", "bar") );
stream=printProperties("applying sort", stream.sorted() );
stream=printProperties("applying map", stream.map(x->x) );
stream=printProperties("applying distinct", stream.distinct() );
stream=printProperties("requesting unordered", stream.unordered() );

System.out.println();

printProperties("ConcurrentHashMap.keySet().stream()",
    new ConcurrentHashMap<>().keySet().stream() );

it will print:

characteristics after received from TreeSet: SIZED|ORDERED|SORTED|DISTINCT
characteristics after applying map: SIZED|ORDERED
characteristics after applying distinct: ORDERED|DISTINCT
characteristics after filtering: ORDERED|DISTINCT
characteristics after applying sort: ORDERED|SORTED|DISTINCT
characteristics after requesting unordered: SORTED|DISTINCT

characteristics after received from varargs array: SUBSIZED|IMMUTABLE|SIZED|ORDERED
characteristics after applying sort: SUBSIZED|SIZED|ORDERED|SORTED
characteristics after applying map: SUBSIZED|SIZED|ORDERED
characteristics after applying distinct: ORDERED|DISTINCT
characteristics after requesting unordered: DISTINCT

characteristics after ConcurrentHashMap.keySet().stream(): CONCURRENT|NONNULL|DISTINCT

As JB Nizet explained, if a stream does not know the size in advance, it has to use a strategy for collecting the elements which might include reallocating arrays. As the documentation says:

… using the provided generator function to allocate the returned array, as well as any additional arrays that might be required for a partitioned execution or for resizing.

Community
  • 1
  • 1
Holger
  • 285,553
  • 42
  • 434
  • 765
3
size -> new String[size]

is a lambda, which is an instance of IntFunction<A[]> generator, as the signature of the method is

<A> A[] toArray(IntFunction<A[]> generator)

So this line creates an instance of IntFunction, and passes it as argument to the stream. The stream is the one which calls the function (i.e. invokes the method apply(int)), and the stream is thus the one passing the size as argument. And the stream knows its own size.

JB Nizet
  • 678,734
  • 91
  • 1,224
  • 1,255
  • I understand that its a lambda expression, input is an int and output is an array. I am not sure how the size gets passed automatically as argument though i can call it size or l or k or whatever. streamString.toArray(whatever-> new String[whatever]); – l a s Feb 11 '15 at 20:46
  • 2
    That's the syntax of lambda expressions. You choose the name of the variable. Just like when you define a method `public int[] apply(int size)`, you may choose to name the argument `size` the way you like. – JB Nizet Feb 11 '15 at 20:47
  • Does it mean the stream always passes its size as the argument? – l a s Feb 11 '15 at 20:48
  • 1
    It depends on the stream. As the javadoc says, *using the provided generator function to allocate the returned array, as well as any additional arrays that might be required for a partitioned execution or for resizing*. What's sure is that it's the stream which calls this function, as many times as it needs to produce the final array. – JB Nizet Feb 11 '15 at 20:52
  • I can't get my head around how its sending the size? Am not sure if there is any other stream - generator combination that does similar but different argument. – l a s Feb 11 '15 at 20:55
  • 1
    Imagine the stream is a list of Strings, and the list transforms itself to an array. The list would do `String[] result = intFunction.apply(this.size);`. The stream does the same thing. The difference with a stream is that it doesn't necessarily know the size in advance. So it could create a too large array, and resize it after it has produced all its elements. Or it could generate N small arrays and combine them as necessary. – JB Nizet Feb 11 '15 at 21:01
  • 1
    @l a s: it’s still not clear what your question is. Is it about how lambda expressions work or about the `Stream` implementation? Almost *every* function you pass to one of the `Stream`’s methods will receive values for its parameters. – Holger Feb 11 '15 at 21:12
  • I want to know how stream knows what needs to be passed as arguments. – l a s Feb 11 '15 at 21:43
  • 3
    It depends on several factors: is the stream parallel, does the stream have a known size (which would be the case for example if the stream starts from a collection and only has map operations). From the source code, it seems that, in the sequential case where the size is unknown, it accumulates the elements in intermediate chunks, then flattens these chunks into a final array. – JB Nizet Feb 12 '15 at 06:59