Java tutorial
//revised from //https://github.com/java8/Java8InAction import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Spliterator; import java.util.concurrent.BlockingQueue; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Future; import java.util.concurrent.LinkedBlockingQueue; import java.util.function.Consumer; import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; import java.util.stream.StreamSupport; public class Main { public static void main(String... args) { Stream<Food> menuStream = Food.menu.stream(); StreamForker.Results results = new StreamForker<Food>( menuStream) .fork("shortMenu", s -> s.map( Food::getName).collect( Collectors.joining(", "))) .fork("totalCalories", s -> s.mapToInt(Food::getCalories).sum()) .fork("mostCaloricFood", s -> s.collect(Collectors .reducing((d1, d2) -> d1.getCalories() > d2.getCalories() ? d1 : d2)).get()) .fork("dishesByType", s -> s.collect(Collectors.groupingBy(Food::getType))).getResults(); String shortMeny = results.get("shortMenu"); int totalCalories = results.get("totalCalories"); Food mostCaloricFood = results.get("mostCaloricFood"); Map<Food.Type, List<Food>> dishesByType = results.get("dishesByType"); System.out.println("Short menu: " + shortMeny); System.out.println("Total calories: " + totalCalories); System.out.println("Most caloric dish: " + mostCaloricFood); System.out.println("Foodes by type: " + dishesByType); } } class Food { private final String name; private final boolean vegetarian; private final int calories; private final Type type; public Food(String name, boolean vegetarian, int calories, Type type) { this.name = name; this.vegetarian = vegetarian; this.calories = calories; this.type = type; } public String getName() { return name; } public boolean isVegetarian() { return vegetarian; } public int getCalories() { return calories; } public Type getType() { return type; } public enum Type { MEAT, FISH, OTHER } @Override public String toString() { return name; } public static final List<Food> menu = Arrays.asList(new Food("pork", false, 800, Food.Type.MEAT), new Food("beef", false, 700, Food.Type.MEAT), new Food("chicken", false, 400, Food.Type.MEAT), new Food("french fries", true, 530, Food.Type.OTHER), new Food("rice", true, 350, Food.Type.OTHER), new Food("season fruit", true, 120, Food.Type.OTHER), new Food("pizza", true, 550, Food.Type.OTHER), new Food("prawns", false, 400, Food.Type.FISH), new Food("salmon", false, 450, Food.Type.FISH)); } /** * Adapted from http://mail.openjdk.java.net/pipermail/lambda-dev/2013-November/011516.html */ class StreamForker<T> { private final Stream<T> stream; private final Map<Object, Function<Stream<T>, ?>> forks = new HashMap<>(); public StreamForker(Stream<T> stream) { this.stream = stream; } public StreamForker<T> fork(Object key, Function<Stream<T>, ?> f) { forks.put(key, f); return this; } public Results getResults() { ForkingStreamConsumer<T> consumer = build(); try { stream.sequential().forEach(consumer); } finally { consumer.finish(); } return consumer; } private ForkingStreamConsumer<T> build() { List<BlockingQueue<T>> queues = new ArrayList<>(); Map<Object, Future<?>> actions = forks.entrySet().stream().reduce(new HashMap<Object, Future<?>>(), (map, e) -> { map.put(e.getKey(), getOperationResult(queues, e.getValue())); return map; }, (m1, m2) -> { m1.putAll(m2); return m1; }); return new ForkingStreamConsumer<>(queues, actions); } private Future<?> getOperationResult(List<BlockingQueue<T>> queues, Function<Stream<T>, ?> f) { BlockingQueue<T> queue = new LinkedBlockingQueue<>(); queues.add(queue); Spliterator<T> spliterator = new BlockingQueueSpliterator<>(queue); Stream<T> source = StreamSupport.stream(spliterator, false); return CompletableFuture.supplyAsync(() -> f.apply(source)); } public static interface Results { public <R> R get(Object key); } private static class ForkingStreamConsumer<T> implements Consumer<T>, Results { static final Object END_OF_STREAM = new Object(); private final List<BlockingQueue<T>> queues; private final Map<Object, Future<?>> actions; ForkingStreamConsumer(List<BlockingQueue<T>> queues, Map<Object, Future<?>> actions) { this.queues = queues; this.actions = actions; } @Override public void accept(T t) { queues.forEach(q -> q.add(t)); } @Override public <R> R get(Object key) { try { return ((Future<R>) actions.get(key)).get(); } catch (Exception e) { throw new RuntimeException(e); } } void finish() { accept((T) END_OF_STREAM); } } private static class BlockingQueueSpliterator<T> implements Spliterator<T> { private final BlockingQueue<T> q; BlockingQueueSpliterator(BlockingQueue<T> q) { this.q = q; } @Override public boolean tryAdvance(Consumer<? super T> action) { T t; while (true) { try { t = q.take(); break; } catch (InterruptedException e) { } } if (t != ForkingStreamConsumer.END_OF_STREAM) { action.accept(t); return true; } return false; } @Override public Spliterator<T> trySplit() { return null; } @Override public long estimateSize() { return 0; } @Override public int characteristics() { return 0; } } }