Java 8 how stream to array size works?

2019-01-25 01:49发布

问题:

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

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

回答1:

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.



回答2:

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.