If you really want/need a stream api for retrieving pages, you could create your own streams by implementing a Spliterator to retrieve each page in its tryAdvance() method.
It would look something like this
public class PageSpliterator implements Spliterator<Page> {
private static final Integer PAGE_SIZE = 200;
int offset;
ApiService apiService;
int selector;
Builder builder;
Page page;
public PageSpliterator(ApiService apiService) {
// initialize Builder?
}
@Override
public boolean tryAdvance(Consumer<? super Page> action) {
if (page == null || offset < page.getTotalNumEntries()) {
Objects.requireNonNull(action);
page = apiService.get(selector);
action.accept(page);
offset += PAGE_SIZE;
selector = builder.increaseOffsetBy(PAGE_SIZE).build();
return true;
} else {
// Maybe close/cleanup apiService?
return false;
}
}
@Override
public Spliterator<Page> trySplit() {
return null; // can't split
}
@Override
public long estimateSize() {
return Long.MAX_VALUE; // don't know in advance
}
@Override
public int characteristics() {
return IMMUTABLE; // return appropriate
}
}
Then you could use the it like this:
StreamSupport.stream(new PageSpliterator(apiService), false)
.flatMap(page -> page.getEntries()
.stream())
.forEach(item -> System.out.printf("Item with name '%s' and ID %d was found.%n", item.getName(), item.getId()));
Answer from rogerkl on Stack OverflowIf you really want/need a stream api for retrieving pages, you could create your own streams by implementing a Spliterator to retrieve each page in its tryAdvance() method.
It would look something like this
public class PageSpliterator implements Spliterator<Page> {
private static final Integer PAGE_SIZE = 200;
int offset;
ApiService apiService;
int selector;
Builder builder;
Page page;
public PageSpliterator(ApiService apiService) {
// initialize Builder?
}
@Override
public boolean tryAdvance(Consumer<? super Page> action) {
if (page == null || offset < page.getTotalNumEntries()) {
Objects.requireNonNull(action);
page = apiService.get(selector);
action.accept(page);
offset += PAGE_SIZE;
selector = builder.increaseOffsetBy(PAGE_SIZE).build();
return true;
} else {
// Maybe close/cleanup apiService?
return false;
}
}
@Override
public Spliterator<Page> trySplit() {
return null; // can't split
}
@Override
public long estimateSize() {
return Long.MAX_VALUE; // don't know in advance
}
@Override
public int characteristics() {
return IMMUTABLE; // return appropriate
}
}
Then you could use the it like this:
StreamSupport.stream(new PageSpliterator(apiService), false)
.flatMap(page -> page.getEntries()
.stream())
.forEach(item -> System.out.printf("Item with name '%s' and ID %d was found.%n", item.getName(), item.getId()));
In my opinion there are not many scenarios where a do...while loop would be the best choice. This however is such a scenario.
Just because there is new stuff in Java8, does not mean you have to use it. If you still want to implement it with a foreach loop, for whatever reason, then I would go for the option you mentioned. Do the API call at the beginning and then start the foreach.
java - Convert an loop (while and for) to stream - Stack Overflow
java - Limit a stream by a predicate - Stack Overflow
Transforming a while loop to a stream in Java 8 - Stack Overflow
In Java, what are the advantages of streams over loops? - Stack Overflow
As was stated above, using streams here doesn't really add value since it makes the code harder to read/understand. I get that you're doing it more as a learning exercise. That being said, doing something like this is a slightly more functional-style approach as it doesn't have a side effect of adding to the list from within the stream itself:
list = oldList.stream().flatMap(line->
map.entrySet().stream()
.filter(e->line.startsWith(e.getKey()))
.map(filteredEntry->line.replace(filteredEntry.getKey(),filteredEntry.getValue()))
).collect(Collectors.toList());
I don't see why you would want to use streams here, but it is possible.
Create some test input:
List<String> oldList = Arrays.asList("adda","bddb","cddc");
Map<String,String> map = new HashMap<>();
map.put("a", "x");
map.put("b", "y");
map.put("c", "z");
List<String> list = new ArrayList<>();
The actual code:
oldList.stream()
.forEach(line -> map.entrySet().stream()
.filter(entry -> line.startsWith(entry.getKey()))
.forEach(entry -> list.add(line.replace(entry.getKey(),entry.getValue()))));
Print the outcome:
list.forEach(System.out::println);
Which is:
xddx
yddy
zddz
Operations takeWhile and dropWhile have been added to JDK 9. Your example code
IntStream
.iterate(1, n -> n + 1)
.takeWhile(n -> n < 10)
.forEach(System.out::println);
will behave exactly as you expect it to when compiled and run under JDK 9.
JDK 9 has been released. It is available for download here: JDK 9 Releases.
Such an operation ought to be possible with a Java 8 Stream, but it can't necessarily be done efficiently -- for example, you can't necessarily parallelize such an operation, as you have to look at elements in order.
The API doesn't provide an easy way to do it, but what's probably the simplest way is to take Stream.iterator(), wrap the Iterator to have a "take-while" implementation, and then go back to a Spliterator and then a Stream. Or -- maybe -- wrap the Spliterator, though it can't really be split anymore in this implementation.
Here's an untested implementation of takeWhile on a Spliterator:
static <T> Spliterator<T> takeWhile(
Spliterator<T> splitr, Predicate<? super T> predicate) {
return new Spliterators.AbstractSpliterator<T>(splitr.estimateSize(), 0) {
boolean stillGoing = true;
@Override public boolean tryAdvance(Consumer<? super T> consumer) {
if (stillGoing) {
boolean hadNext = splitr.tryAdvance(elem -> {
if (predicate.test(elem)) {
consumer.accept(elem);
} else {
stillGoing = false;
}
});
return hadNext && stillGoing;
}
return false;
}
};
}
static <T> Stream<T> takeWhile(Stream<T> stream, Predicate<? super T> predicate) {
return StreamSupport.stream(takeWhile(stream.spliterator(), predicate), false);
}
I see no reason to use a Stream for this other than to take advantage of parallelism (if the primes happen to be very far apart, but this won't be true within int bounds, so there's essentially no benefit).
You can iterate over an IntStream of ascending integers (starting from number + 1) and filter only the prime numbers. When one is inevitably found, you can return the first.
private static int nextPrime(final int number) {
return IntStream.iterate(number + 1, i -> i + 1)
.filter(Test::isPrime)
.findFirst()
.getAsInt();
}
Note: The class that I used to test this is called Test, as seen by the method reference. You should change that to your class name.
Based on this answer, for an object like an Enumeration<T> for example, where you can only call .hasMoreElements() & .nextElement(), you can use this kind of code :
public static <T> Stream<T> enumerationAsStream(Enumeration<T> e) {
return StreamSupport.stream(
new Spliterators.AbstractSpliterator<T>(Long.MAX_VALUE, Spliterator.ORDERED) {
public boolean tryAdvance(Consumer<? super T> action) {
if(e.hasMoreElements()) {
action.accept(e.nextElement());
return true;
}
return false;
}
public void forEachRemaining(Consumer<? super T> action) {
while(e.hasMoreElements()) action.accept(e.nextElement());
}
}, false);
}
Interesting that the interview question asks about the advantages, without asking about disadvantages, for there are are both.
Streams are a more declarative style. Or a more expressive style. It may be considered better to declare your intent in code, than to describe how it's done:
return people
.filter( p -> p.age() < 19)
.collect(toList());
... says quite clearly that you're filtering matching elements from a list, whereas:
List<Person> filtered = new ArrayList<>();
for(Person p : people) {
if(p.age() < 19) {
filtered.add(p);
}
}
return filtered;
Says "I'm doing a loop". The purpose of the loop is buried deeper in the logic.
Streams are often terser. The same example shows this. Terser isn't always better, but if you can be terse and expressive at the same time, so much the better.
Streams have a strong affinity with functions. Java 8 introduces lambdas and functional interfaces, which opens a whole toybox of powerful techniques. Streams provide the most convenient and natural way to apply functions to sequences of objects.
Streams encourage less mutability. This is sort of related to the functional programming aspect -- the kind of programs you write using streams tend to be the kind of programs where you don't modify objects.
Streams encourage looser coupling. Your stream-handling code doesn't need to know the source of the stream, or its eventual terminating method.
Streams can succinctly express quite sophisticated behaviour. For example:
stream.filter(myfilter).findFirst();
Might look at first glance as if it filters the whole stream, then returns the first element. But in fact findFirst() drives the whole operation, so it efficiently stops after finding one item.
Streams provide scope for future efficiency gains. Some people have benchmarked and found that single-threaded streams from in-memory Lists or arrays can be slower than the equivalent loop. This is plausible because there are more objects and overheads in play.
But streams scale. As well as Java's built-in support for parallel stream operations, there are a few libraries for distributed map-reduce using Streams as the API, because the model fits.
Disadvantages?
Performance: A for loop through an array is extremely lightweight both in terms of heap and CPU usage. If raw speed and memory thriftiness is a priority, using a stream is worse.
Familiarity.The world is full of experienced procedural programmers, from many language backgrounds, for whom loops are familiar and streams are novel. In some environments, you want to write code that's familiar to that kind of person.
Cognitive overhead. Because of its declarative nature, and increased abstraction from what's happening underneath, you may need to build a new mental model of how code relates to execution. Actually you only need to do this when things go wrong, or if you need to deeply analyse performance or subtle bugs. When it "just works", it just works.
Debuggers are improving, but even now, when you're stepping through stream code in a debugger, it can be harder work than the equivalent loop, because a simple loop is very close to the variables and code locations that a traditional debugger works with.
Syntactic fun aside, Streams are designed to work with potentially infinitely large data sets, whereas arrays, Collections, and nearly every Java SE class which implements Iterable are entirely in memory.
A disadvantage of a Stream is that filters, mappings, etc., cannot throw checked exceptions. This makes a Stream a poor choice for, say, intermediate I/O operations.
This is horrible code, there's no reason ever to use this. Streams are an additional tool to regular loops, they're not a replacement for for and especially while loops.
// If you use this code seriously somewhere, I will find you
IntStream.generate(() -> 0)
.peek(i -> {
// Any custom logic
System.out.println(i);
})
.noneMatch(i -> isTrue());
The code generates zeroes infinitely, peeks in the stream to perform custom logic, then stops when noneMatch evaluates to true.
The above is equivalent to the code in the question, which can be written far more succinctly as
do {
// custom logic
} while(!isTrue());
You cannot replace or rewrite while loop with Stream API. Stream API reads values from Stream. while (true) is not reading data from any stream. It is simply a infinite loop.