W Javie 9 będzie to dostępne od ręki, ale w ósemce trzeba się trochę nagimnastykować. O co dokładnie chodzi? Przyjrzyjmy się prostemu programowi:

Listing 1. Przykładowy program

public class App {

	public static void main(String[] args) {
		Scanner console = new Scanner(System.in);
		Stream.generate(() -> console.nextLine())
				.limit(10)
		.forEach(System.out::println);
	}
}

Czytamy wartość z konsoli i wypisujemy ją na konsolę. Typowe zadanie z ćwiczeń na wielu uczelniach… ciekawe czy takie rozwiązanie by przeszło 😉 Problem polega na tym, że w tym przypadku liczba wczytanych wartości jest równa 10. W ogólniejszej wersji chcielibyśmy, aby wczytywane było kontynuowane do momentu podania konkretnej wartości w rodzaju exit, czy pustej linii. Inaczej mówiąc:

Listing 2. Przykładowy program II

public class App {

	public static void main(String[] args) {
		Scanner console = new Scanner(System.in);
		Stream.generate(() -> console.nextLine())
				.takeWhile(s-> !s.isEmpty())
		.forEach(System.out::println);
	}
}

Powyższy kod zaskoczy w Javie 9, ale w Javie 8 (bez dodatkowych bibliotek) już nie. Nie za bardzo możemy też bez większego paprania się z AspectJ dodać taką metodę do interfejsu Stream. Pozostaje nam stara dobra klasa narzędziowa/interfejs narzędziowy. To co chcemy uzyskać to kod w postaci:

Listing 3. Program z takeWhile

public class App {

	public static void main(String[] args) {
		Scanner console = new Scanner(System.in);
		takeWhile(
				Stream.generate(() -> console.nextLine())
				, s -> !s.isEmpty()
		)
				.forEach(System.out::println);
	}
}

Czyli mamy metodę takeWhile, która przyjmuje jakiś Stream i predykat, który będzie go ograniczać. Przystąpmy więc do implementacji:

Listing 4. Implementacja takeWhile

class TakeWhile {

	public static <T> Stream<T> takeWhile(Stream<T> in, Predicate<T> condition) {
		Spliterator<T> inSplit = in.spliterator();
		return StreamSupport.stream(
					new Spliterators.AbstractSpliterator(inSplit.estimateSize(), 0) {
						private boolean isGoing = true;

						@Override
						public boolean tryAdvance(Consumer action) {
							if (isGoing) {
								boolean next = inSplit.tryAdvance(e -> {
									if (condition.test(e))
										action.accept(e);
									else
										isGoing = false;
									});
								return next && isGoing;
							}
							return false;
						}
					}
					, false
			);
    }
}

Na początek z naszego wejściowego strumienia bierzemy Spliterator. Na jego podstawie tworzymy nowy strumień. I teraz cała zabawa dzieje się w metodzie tryAdvice. Jeżeli isGoing jest prawdziwa, czyli strumień zawiera kolejny element, to wywołujemy tryAdvice na wejściowym strumieniu (za pośrednictwem spliteratora), przekazując specyficznie przygotowanego Consumera. Najpierw sprawdza on warunek i jeżeli ten pasuje to wywołuje oryginalną akcję. Jeżeli nie, to oznacza iż osiągnęliśmy kres naszego strumienia – warunek nie jest spełniony i ustawiamy isGoing na false. Następnie zwracamy sumę z wywołania tryAdvice na wejściowym strumieniu i isGoing. Suma ta pokrywa przypadek gdy wejściowy strumień nie ma już elementów, ale nasz warunek jest spełniony. W razie czego zwracamy false, ale to nie powinno się zdarzyć i praktycznie jest informacją dla obsługujących strumień, że nie ma w nim już żadnych elementów.

Kod co prawda nie jest piękny, ale spełnia swoje zadanie. Kolejne kroki mogą polegać na wyciągnięciu fragmentów tego algorytmu do mniejszych klas, dodaniu zabezpieczeń na przypadek uruchomienia w wielu wątkach itp. Jednak na chwilę obecną wystarczy.