All Downloads are FREE. Search and download functionalities are using the official Maven repository.

functionalj.stream.StreamPlus Maven / Gradle / Ivy

There is a newer version: 1.0.17
Show newest version
// ============================================================================
// Copyright (c) 2017-2021 Nawapunth Manusitthipol (NawaMan - http://nawaman.net).
// ----------------------------------------------------------------------------
// MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
// ============================================================================
package functionalj.stream;

import static functionalj.function.Func.themAll;
import static functionalj.stream.StreamPlusHelper.terminate;

import java.util.Collection;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.Optional;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.BinaryOperator;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.IntFunction;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.function.ToDoubleFunction;
import java.util.function.ToIntFunction;
import java.util.function.ToLongFunction;
import java.util.stream.Collector;
import java.util.stream.DoubleStream;
import java.util.stream.IntStream;
import java.util.stream.LongStream;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

import functionalj.function.DoubleDoubleFunction;
import functionalj.function.DoubleObjBiFunction;
import functionalj.function.IntIntBiFunction;
import functionalj.function.IntObjBiFunction;
import functionalj.function.LongLongBiFunction;
import functionalj.function.aggregator.Aggregation;
import functionalj.function.aggregator.AggregationToBoolean;
import functionalj.function.aggregator.AggregationToDouble;
import functionalj.function.aggregator.AggregationToInt;
import functionalj.function.aggregator.AggregationToLong;
import functionalj.list.FuncList;
import functionalj.result.NoMoreResultException;
import functionalj.result.Result;
import functionalj.stream.doublestream.DoubleStreamPlus;
import functionalj.stream.intstream.IntStreamPlus;
import functionalj.stream.longstream.LongStreamPlus;
import functionalj.stream.markers.Eager;
import functionalj.stream.markers.Sequential;
import functionalj.stream.markers.Terminal;
import functionalj.tuple.Tuple2;
import lombok.val;


// TODO - Add intersect
// TODO - Add prepare


/**
 * This interface allows extension to Java Stream object.
 *
 * 
    *
  1. It ensures stream is called and onClose is called.
  2. *
  3. Add many convenience methods
  4. *
* * Use this class if the source of data is a one-time non-repeatable data source. * Otherwise, use {@link FuncList} as some operation may benefit from having repeatable streams. * * Unless stated otherwise all methods in this class is: *
    *
  1. Lazy.
  2. *
  3. Works for parallel
  4. *
  5. Run in order - if sequence
  6. *
  7. Terminal method must close the stream
  8. *
* * If any of the rules are not observed, it is a bug. * See annotations in {@link functionalj.stream.markers} package for more information. * * @param the data type. * * @author NawaMan -- [email protected] */ @FunctionalInterface public interface StreamPlus extends Stream, AsStreamPlus, StreamPlusWithCombine, StreamPlusWithFillNull, StreamPlusWithFilter, StreamPlusWithFlatMap, StreamPlusWithLimit, StreamPlusWithMap, StreamPlusWithMapFirst, StreamPlusWithMapFlat, StreamPlusWithMapGroup, StreamPlusWithMapMulti, StreamPlusWithMapThen, StreamPlusWithMapToMap, StreamPlusWithMapToTuple, StreamPlusWithMapWithIndex, StreamPlusWithModify, StreamPlusWithPeek, StreamPlusWithPipe, StreamPlusWithSegment, StreamPlusWithSort, StreamPlusWithSplit { /** Throw a no more element exception. This is used for generator. */ public static TARGET noMoreElement() throws NoMoreResultException { return SupplierBackedIterator.noMoreElement(); } //== Constructor == /** Returns an empty StreamPlus. */ public static StreamPlus empty() { return StreamPlus .from(Stream.empty()); } /** Returns an empty StreamPlus. */ public static StreamPlus emptyStream() { return empty(); } /** Create a StreamPlus from the given data. */ @SafeVarargs public static StreamPlus of(TARGET ... data) { return ArrayBackedStreamPlus.from(data); } /** Create a StreamPlus from the given data */ @SafeVarargs public static StreamPlus streamOf(TARGET ... data) { return StreamPlus.of(data); } //-- from other type -- /** Create a StreamPlus from the given data. */ public static StreamPlus from(TARGET[] data, int start, int length) { return ArrayBackedStreamPlus.from(data, start, length); } /** Create a StreamPlus from the given stream. */ @SuppressWarnings({ "unchecked", "rawtypes" }) public static StreamPlus from(Stream stream) { if (stream == null) return StreamPlus.empty(); return (stream instanceof StreamPlus) ? (StreamPlus)stream : (StreamPlus)(()->stream); } /** Create a StreamPlus from the given iterator. */ public static StreamPlus from(Iterator iterator) { return IteratorPlus.from(iterator) .stream(); } /** Create a StreamPlus from the given iterator. */ public static StreamPlus from(Iterable iterable) { if (iterable instanceof FuncList) { return ((FuncList)iterable).streamPlus(); } return IteratorPlus.from(iterable.iterator()) .stream(); } /** Create a StreamPlus from the given enumeration. */ public static StreamPlus from(Enumeration enumeration) { val iterable = (Iterable)() -> new EnumerationBackedIterator(enumeration); return StreamPlus.from(StreamSupport.stream(iterable.spliterator(), false)); } /** @return the streams containing null value. */ public static StreamPlus nulls() { return generateWith(() -> null); } /** @return the streams containing null value. */ public static StreamPlus nulls(Class dataClass) { return generateWith(() -> null); } /** Create a list that is the repeat of the given array of data. */ @SuppressWarnings("unchecked") public static StreamPlus repeat(TARGET ... data) { return cycle(data); } /** Create a list that is the repeat of the given list of data. */ public static StreamPlus repeat(FuncList data) { return cycle(data); } /** Create a FuncList that is the repeat of the given array of data. */ @SafeVarargs public static StreamPlus cycle(TARGET ... data) { val size = data.length; return IntStreamPlus .wholeNumbers() .mapToObj(i -> data[i % size]); } /** Create a FuncList that is the repeat of the given list of data. */ public static StreamPlus cycle(Collection collection) { val list = FuncList.from(collection); val size = list.size(); return IntStreamPlus .wholeNumbers() .mapToObj(i -> list.get(i % size)); } /** Create a FuncList that for an infinite loop - the value is null */ public static StreamPlus loop() { return nulls(); } /** Create a FuncList that for a loop with the number of time given - the value is the index of the loop. */ public static StreamPlus loop(int times) { return nulls((Class)null).limit(times); } /** Create a FuncList that for an infinite loop - the value is the index of the loop. */ public static StreamPlus infiniteInt() { return IntStreamPlus .wholeNumbers() .boxed(); } /** Concatenate all the given streams. */ @SafeVarargs public static StreamPlus concat(Stream ... streams) { return streamOf (streams) .flatMap(themAll()); } /** Concatenate all streams supplied by the given supplied. */ @SafeVarargs public static StreamPlus concat(Supplier> ... streams) { return streamOf (streams) .map (Supplier::get) .flatMap(themAll()); } /** Concatenate all the given streams. */ @SafeVarargs public static StreamPlus combine(Stream ... streams) { return streamOf (streams) .flatMap(themAll()); } /** Concatenate all streams supplied by the given supplied. */ @SafeVarargs public static StreamPlus combine(Supplier> ... streams) { return streamOf (streams) .map (Supplier::get) .flatMap(themAll()); } /** * Create a StreamPlus from the supplier. * The supplier will be repeatedly asked for value until NoMoreResultException is thrown. **/ public static StreamPlus generate(Supplier supplier) { return generateWith(supplier); } /** * Create a StreamPlus from the supplier. * The supplier will be repeatedly asked for value until NoMoreResultException is thrown. **/ public static StreamPlus generateWith(Supplier supplier) { val iterable = (Iterable)() -> new SupplierBackedIterator(supplier); return StreamPlus.from(StreamSupport.stream(iterable.spliterator(), false)); } //== Iterate + Compound == /** * Create a StreamPlus by apply the compounder to the seed over and over. * * For example: let say seed = 1 and f(x) = x*2. * The result stream will be: * 1 <- seed, * 2 <- (1*2), * 4 <- ((1*2)*2), * 8 <- (((1*2)*2)*2), * 16 <- ((((1*2)*2)*2)*2) * ... * * Note: this is an alias of compound() **/ public static StreamPlus iterate( TARGET seed, Function compounder) { return StreamPlus.from(Stream.iterate(seed, compounder::apply)); } public static StreamPlus iterate( TARGET seed, Aggregation aggregation) { val compounder = aggregation.newAggregator(); return StreamPlus.from(Stream.iterate(seed, compounder::apply)); } /** * Create a StreamPlus by apply the compounder to the seed over and over. * * For example: let say seed = 1 and f(x) = x*2. * The result stream will be: * 1 <- seed, * 2 <- (1*2), * 4 <- ((1*2)*2), * 8 <- (((1*2)*2)*2), * 16 <- ((((1*2)*2)*2)*2) * ... * * Note: this is an alias of iterate() **/ public static StreamPlus compound( TARGET seed, Function compounder) { return iterate(seed, compounder); } public static StreamPlus compound( TARGET seed, Aggregation aggregation) { return iterate(seed, aggregation); } /** * Create a StreamPlus by apply the compounder to the seeds over and over. * * For example: let say seed1 = 1, seed2 = 1 and f(a,b) = a+b. * The result stream will be: * 1 <- seed1, * 1 <- seed2, * 2 <- (1+1), * 3 <- (1+2), * 5 <- (2+3), * 8 <- (5+8) * ... * * Note: this is an alias of compound() **/ @Sequential public static StreamPlus iterate( TARGET seed1, TARGET seed2, BiFunction compounder) { return StreamPlus.from(StreamSupport.stream(new Spliterators.AbstractSpliterator(Long.MAX_VALUE, 0) { private final AtomicReference first = new AtomicReference<>(seed1); private final AtomicReference second = new AtomicReference<>(seed2); private volatile AtomicBoolean isInOrder = null; @Override public boolean tryAdvance(Consumer action) { if (isInOrder == null) { action.accept(seed1); action.accept(seed2); isInOrder = new AtomicBoolean(true); } boolean inOrder = isInOrder.get(); if (inOrder) { TARGET next = compounder.apply(first.get(), second.get()); action.accept(next); first.set(next); } else { TARGET next = compounder.apply(second.get(), first.get()); action.accept(next); second.set(next); } isInOrder.set(!inOrder); return true; } }, false)); } /** * Create a StreamPlus by apply the compounder to the seeds over and over. * * For example: let say seed1 = 1, seed2 = 1 and f(a,b) = a+b. * The result stream will be: * 1 <- seed1, * 1 <- seed2, * 2 <- (1+1), * 3 <- (1+2), * 5 <- (2+3), * 8 <- (5+8) * ... * * Note: this is an alias of iterate() **/ public static StreamPlus compound( TARGET seed1, TARGET seed2, BiFunction compounder) { return iterate(seed1, seed2, compounder); } //== ZipOf == /** * Create a StreamPlus by combining elements together into a StreamPlus of tuples. * Only elements with pair will be combined. If this is not desirable, use stream1.zip(stream2). * * For example: * stream1 = [A, B, C, D, E] * stream2 = [1, 2, 3, 4] * * The result stream = [(A,1), (B,2), (C,3), (D,4)]. **/ public static StreamPlus> zipOf( Stream stream1, Stream stream2) { return StreamPlus.from(stream1) .zipWith(StreamPlus.from(stream2), ZipWithOption.RequireBoth); } /** * Create a StreamPlus by combining elements together using the merger function and collected into the result stream. * Only elements with pair will be combined. If this is not desirable, use stream1.zip(stream2). * * For example: * stream1 = [A, B, C, D, E] * stream2 = [1, 2, 3, 4] * merger = a + "+" + b * * The result stream = ["A+1", "B+2", "C+3", "D+4"]. **/ public static StreamPlus zipOf( Stream stream1, Stream stream2, BiFunction merger) { return StreamPlus.from(stream1) .zipWith(StreamPlus.from(stream2), ZipWithOption.RequireBoth, merger); } /** * Zip integers from two IntStreams and combine it into another object. * The result stream has the size of the shortest stream. */ public static StreamPlus zipOf( IntStream stream1, IntStream stream2, IntIntBiFunction merger) { return IntStreamPlus.from(stream1) .zipToObjWith(stream2, merger); } /** * Zip integers from an int stream and another object stream and combine it into another object. * The result stream has the size of the shortest stream. */ public static StreamPlus zipOf( IntStream stream1, Stream stream2, IntObjBiFunction merger) { return IntStreamPlus.from(stream1) .zipWith(stream2, merger); } /** * Zip integers from two IntStreams and combine it into another object. * The result stream has the size of the shortest stream. */ public static StreamPlus zipOf( LongStream stream1, LongStream stream2, LongLongBiFunction merger) { return LongStreamPlus.from(stream1) .zipToObjWith(stream2, merger); } /** * Zip integers from two IntStreams and combine it into another object. * The result stream has the size of the shortest stream. */ public static StreamPlus zipOf( DoubleStream stream1, DoubleStream stream2, DoubleDoubleFunction merger) { return DoubleStreamPlus.from(stream1) .zipToObjWith(stream2, merger); } /** * Zip integers from an int stream and another object stream and combine it into another object. * The result stream has the size of the shortest stream. */ public static StreamPlus zipOf( DoubleStream stream1, Stream stream2, DoubleObjBiFunction merger) { return DoubleStreamPlus.from(stream1) .zipWith(stream2, merger); } //== Core == /** Return the stream of data behind this StreamPlus. */ public Stream stream(); /** Return this StreamPlus. */ public default StreamPlus streamPlus() { return this; } //-- Derive -- public default StreamPlus derive( Function, Stream> action) { return StreamPlus .from(action.apply(this)); } public default IntStreamPlus deriveToInt( Function, IntStream> action) { return IntStreamPlus .from(action.apply(this)); } public default DoubleStreamPlus deriveToDouble( Function, DoubleStream> action) { return DoubleStreamPlus .from(action.apply(this)); } public default StreamPlus deriveToObj( Function, Stream> action) { return StreamPlus .from(action.apply(this)); } //-- Characteristics -- /** * Returns an equivalent stream that is sequential. May return * itself, either because the stream was already sequential, or because * the underlying stream state was modified to be sequential. * *

This is an intermediate * operation. * * @return a sequential stream */ @Override public default StreamPlus sequential() { return StreamPlus .from(stream().sequential()); } /** * Returns an equivalent stream that is parallel. May return * itself, either because the stream was already parallel, or because * the underlying stream state was modified to be parallel. * *

This is an intermediate * operation. * * @return a parallel stream */ @Override public default StreamPlus parallel() { return StreamPlus .from(stream().parallel()); } /** * Returns an equivalent stream that is * unordered. May return * itself, either because the stream was already unordered, or because * the underlying stream state was modified to be unordered. * *

This is an intermediate * operation. * * @return an unordered stream */ @Override public default StreamPlus unordered() { return StreamPlus .from(stream().unordered()); } /** * Returns whether this stream, if a terminal operation were to be executed, * would execute in parallel. Calling this method after invoking an * terminal stream operation method may yield unpredictable results. * * @return {@code true} if this stream would execute in parallel if executed */ @Override public default boolean isParallel() { return stream() .isParallel(); } //-- Close -- @Terminal @Override public default void close() { stream() .close(); } @Override public default StreamPlus onClose(Runnable closeHandler) { return StreamPlus.from(stream().onClose(closeHandler)); } //-- Iterator -- /** @return a iterator of this FuncList. */ @Override public default IteratorPlus iterator() { return IteratorPlus.from(stream().iterator()); } /** @return a spliterator of this FuncList. */ @Override public default Spliterator spliterator() { val iterator = iterator(); return Spliterators.spliteratorUnknownSize(iterator, 0); } //== Functionalities == //-- Map -- @Override public default StreamPlus map(Function mapper) { return StreamPlus.from(stream().map(mapper)); } @Override public default IntStreamPlus mapToInt(ToIntFunction mapper) { return IntStreamPlus.from(stream().mapToInt(mapper)); } @Override public default LongStreamPlus mapToLong(ToLongFunction mapper) { return LongStreamPlus.from(stream().mapToLong(mapper)); } @Override public default DoubleStreamPlus mapToDouble(ToDoubleFunction mapper) { return DoubleStreamPlus.from(stream().mapToDouble(mapper)); } @Override public default StreamPlus mapToObj(Function mapper) { return StreamPlus.from(stream().map(mapper)); } public default StreamPlus map(Aggregation aggregation) { val mapper = aggregation.newAggregator(); return StreamPlus.from(stream().map(mapper)); } public default IntStreamPlus mapToInt(AggregationToInt aggregation) { val mapper = aggregation.newAggregator(); return IntStreamPlus.from(stream().mapToInt(mapper)); } public default LongStreamPlus mapToLong(AggregationToLong aggregation) { val mapper = aggregation.newAggregator(); return LongStreamPlus.from(stream().mapToLong(mapper)); } public default DoubleStreamPlus mapToDouble(AggregationToDouble aggregation) { val mapper = aggregation.newAggregator(); return DoubleStreamPlus.from(stream().mapToDouble(mapper)); } public default StreamPlus mapToObj(Aggregation aggregation) { val mapper = aggregation.newAggregator(); return StreamPlus.from(stream().map(mapper)); } //-- FlatMap -- @Override public default StreamPlus flatMap(Function> mapper) { return StreamPlus.from(stream().flatMap(mapper)); } @Override public default IntStreamPlus flatMapToInt(Function mapper) { return IntStreamPlus.from(stream().flatMapToInt(mapper)); } @Override public default LongStreamPlus flatMapToLong(Function mapper) { return LongStreamPlus.from(stream().flatMapToLong(mapper)); } @Override public default DoubleStreamPlus flatMapToDouble(Function mapper) { return DoubleStreamPlus.from(stream().flatMapToDouble(mapper)); } public default StreamPlus flatMapToObj(Function> mapper) { return StreamPlus.from(stream().flatMap(mapper)); } public default StreamPlus flatMap(Aggregation> aggregation) { val mapper = aggregation.newAggregator(); return StreamPlus.from(stream().flatMap(mapper)); } public default IntStreamPlus flatMapToInt(Aggregation aggregation) { val mapper = aggregation.newAggregator(); return IntStreamPlus.from(stream().flatMapToInt(mapper)); } public default LongStreamPlus flatMapToLong(Aggregation aggregation) { val mapper = aggregation.newAggregator(); return LongStreamPlus.from(stream().flatMapToLong(mapper)); } public default DoubleStreamPlus flatMapToDouble(Aggregation aggregation) { val mapper = aggregation.newAggregator(); return DoubleStreamPlus.from(stream().flatMapToDouble(mapper)); } public default StreamPlus flatMapToObj(Aggregation> aggregation) { val mapper = aggregation.newAggregator(); return StreamPlus.from(stream().flatMap(mapper)); } //-- Filter -- @Override public default StreamPlus filter(Predicate predicate) { return StreamPlus.from(stream().filter(predicate)); } public default StreamPlus filter(AggregationToBoolean aggregation) { val predicate = aggregation.newAggregator(); return StreamPlus .from(stream() .filter(each -> predicate.test(each))); } //-- Peek -- @Override public default StreamPlus peek(Consumer action) { return StreamPlus.from(stream().peek(action)); } //-- Limit/Skip -- @Override public default StreamPlus limit(long maxSize) { return StreamPlus.from(stream().limit(maxSize)); } @Override public default StreamPlus skip(long offset) { return StreamPlus.from(stream().skip(offset)); } //-- Distinct -- @Override public default StreamPlus distinct() { return StreamPlus.from(stream().distinct()); } //-- Sorted -- @Eager @Override public default StreamPlus sorted() { return StreamPlus.from(stream().sorted()); } @Eager @Override public default StreamPlus sorted(Comparator comparator) { return StreamPlus.from(stream().sorted(comparator)); } //-- Terminate -- @Eager @Terminal @Override public default void forEach(Consumer action) { terminate(this, stream -> { stream .forEach(action); }); } @Eager @Terminal @Sequential @Override public default void forEachOrdered(Consumer action) { terminate(this, stream -> { stream .sequential () .forEachOrdered(action); }); } @Eager @Terminal @Override public default DATA reduce(DATA identity, BinaryOperator reducer) { return terminate(this, stream -> { return stream .reduce(identity, reducer); }); } @Eager @Terminal @Override public default Optional reduce(BinaryOperator reducer) { return terminate(this, stream -> { return stream .reduce(reducer); }); } @Eager @Terminal @Override public default U reduce( U identity, BiFunction accumulator, BinaryOperator combiner) { return terminate(this, stream -> { return stream .reduce(identity, accumulator, combiner); }); } @Eager @Terminal @Override public default R collect( Supplier supplier, BiConsumer accumulator, BiConsumer combiner) { return terminate(this, stream -> { return stream .collect(supplier, accumulator, combiner); }); } @Eager @Terminal @Override public default R collect(Collector collector) { return terminate(this, stream -> { return stream .collect(collector); }); } @Eager @Terminal public default R collect(Aggregation aggregation) { val collector = aggregation.collectorPlus(); return terminate(this, stream -> { return stream .collect(collector); }); } //-- statistics -- @Eager @Terminal @Override public default Optional min(Comparator comparator) { return terminate(this, stream -> { return stream.min(comparator); }); } @Eager @Terminal @Override public default Optional max(Comparator comparator) { return terminate(this, stream -> { return stream.max(comparator); }); } @Eager @Terminal @Override public default long count() { return terminate(this, stream -> { return stream .count(); }); } //-- Match -- @Terminal @Override public default boolean anyMatch(Predicate predicate) { return terminate(this, stream -> { return stream .anyMatch(predicate); }); } @Eager @Terminal @Override public default boolean allMatch(Predicate predicate) { return terminate(this, stream -> { return stream .allMatch(predicate); }); } @Eager @Terminal @Override public default boolean noneMatch(Predicate predicate) { return terminate(this, stream -> { return stream .noneMatch(predicate); }); } @Terminal public default boolean anyMatch(AggregationToBoolean aggregation) { val aggregator = aggregation.newAggregator(); return terminate(this, stream -> { return stream .anyMatch(each -> aggregator.test(each)); }); } @Eager @Terminal public default boolean allMatch(AggregationToBoolean aggregation) { val aggregator = aggregation.newAggregator(); return terminate(this, stream -> { return stream .allMatch(each -> aggregator.test(each)); }); } @Eager @Terminal public default boolean noneMatch(AggregationToBoolean aggregation) { val aggregator = aggregation.newAggregator(); return terminate(this, stream -> { return stream .noneMatch(each -> aggregator.test(each)); }); } @Terminal @Override public default Optional findFirst() { return terminate(this, stream -> { return stream .findFirst(); }); } @Terminal @Override public default Optional findAny() { return terminate(this, stream -> { return stream .findAny(); }); } @SuppressWarnings("unchecked") @Sequential @Terminal public default Optional findLast() { return terminate(this, stream -> { Object dummy = new Object(); AtomicReference dataRef = new AtomicReference<>(dummy); stream.forEach(dataRef::set); Object last = dataRef.get(); return (dataRef.get() == dummy) ? Optional.empty() : Optional.ofNullable((DATA)last); }); } @SuppressWarnings("unchecked") @Sequential @Terminal public default Result firstResult() { return terminate(this, stream -> { Object dummy = new Object(); AtomicReference dataRef = new AtomicReference<>(dummy); stream.limit(1).forEach(dataRef::set); Object last = dataRef.get(); return (dataRef.get() == dummy) ? Result.ofNotExist() : Result.valueOf((DATA)last); }); } @SuppressWarnings("unchecked") @Sequential @Terminal public default Result lastResult() { return terminate(this, stream -> { Object dummy = new Object(); AtomicReference dataRef = new AtomicReference<>(dummy); stream.forEach(dataRef::set); Object last = dataRef.get(); return (dataRef.get() == dummy) ? Result.ofNotExist() : Result.valueOf((DATA)last); }); } //== Conversion == @Eager @Terminal @Override public default Object[] toArray() { return terminate(this, stream -> { return stream .toArray(); }); } @Eager @Terminal @Override public default A[] toArray(IntFunction generator) { return terminate(this, stream -> { return stream .toArray(generator); }); } }