7

Looking for how to use Java lambda functions so Consumer can process all objects provided by a Supplier, and get rid of the explicit while loop and null checks.

I have a Supplier of String keys for a database and I want to use a Consumer to process each of those keys.

Supplier<String> keyGen = new SimpleKeySupplier(keyPrefix, numKeys);
Consumer<String> consumer = (String key) -> System.out.println("key="+key);

I want consumer to process each key supplied by keyGen and tried the following. It works but I am sure that there must be a more concise way of using lambda functions to make this simpler.

    // Test that the correct keys have been populated.
    Supplier<String> keyGen = new SimpleKeySupplier(keyPrefix, NumKeys);
    String k = keyGen.get();
    while(k != null) {
        consumer.accept(k);
        k = keyGen.get();
    }

SimpleKeySupplier works, and a simplified version is presented below:

import java.util.function.Supplier;

public class SimpleKeySupplier implements Supplier<String> {
    private final String keyPrefix;
    private final int numToGenerate;
    private       int numGenerated;

    public SimpleKeySupplier(String keyPrefix, int numRecs) {
        this.keyPrefix = keyPrefix;
        numToGenerate  = numRecs;
        numGenerated   = 0;
    }
    @Override
    public String get() {
        if (numGenerated >= numToGenerate) 
            return null; 
        else   
            return (keyPrefix + numGenerated++);
    }
}

The Consumer in this example is greatly simplified for posting on StackOverflow.

ScottK
  • 1,526
  • 1
  • 16
  • 23
  • You are using the wrong tool for the job. Use `IntStream.range(0, numKeys).mapToObj(numGenerated-> keyPrefix + numGenerated) …` – Holger Dec 20 '18 at 09:48

3 Answers3

6

You may do it like so, with the new capabilities added in Java9.

Stream.generate(keyGen).takeWhile(Objects::nonNull).forEach(consumer);
Ravindra Ranwala
  • 20,744
  • 6
  • 45
  • 63
  • 2
    Interestingly `Stream.generate` produces an *unordered stream* which helps further in using `takeWhile` efficiently. – Naman Dec 19 '18 at 05:39
  • 2
    @nullpointer actually, it breaks the operation. Since the stream is *unordered*, there is no constraint about how many elements it will actually process, when there is a `null` element, as it is allowed to behave as-if `null` was at an arbitrary position (*unordered* implies the absence of a meaningful position), including the first position. It may even try to consume elements “after” the `null` element, as “before” and “after” have no meaning in an unordered stream. You are right in that this will help optimizing `takeWhile` internally, but not in the way the OP wants… – Holger Dec 20 '18 at 09:42
  • @Holger It looks like I should use filter(s -> s != null) rather than takeWhile(Objects::nonNull) to ensure I process them all except the null. In my case, order does not matter but if it did then what would you suggest I use as the right tool rather than generate(keyGen)? Thanks. – ScottK Dec 20 '18 at 13:54
  • 2
    @ScottK “process them all” is not a good idea for an infinite stream. You need an ordered stream to correctly terminate when encountering `null`, e.g. `Stream.iterate(supplier.get(), Objects::nonNull, prev -> supplier.get()). …`, but as you can see, a `Supplier` is not a good starting point for that. I already provided the better alternative in [this comment](https://stackoverflow.com/questions/53844760/using-lambda-functions-to-consume-all-objects-provided-by-a-supplier/53845097?noredirect=1#comment94579576_53844760). – Holger Dec 20 '18 at 18:10
  • @Holger Maybe I misunderstand you, but I use the Supplier class as that is the design pattern that I am given. In reality, the key Supplier and verification steps are much more complex than I have shown, and the two are decoupled. I would not be able to replace the Supplier Class with something of my own choosing. – ScottK Dec 20 '18 at 18:29
  • @ScottK well, if you can’t replace the supplier, the `Stream.iterate` based solution of my previous comment may help. – Holger Dec 20 '18 at 18:32
5

You can use a Stream:

Stream.generate(keyGen).limit(NumKeys).forEach(consumer);

Stream.generate will create a stream that keeps calling get on the supplier provided.

limit makes the stream finite, limiting it to x elements. In this case, I can see that keyGen will only generate NumKeys keys, so I "cut off" the stream from that point.

Sweeper
  • 213,210
  • 22
  • 193
  • 313
  • This was an excellent solution, except in my case I needed to add a filter for the one Null record: filter(s -> s !=null). Now it works well. Thanks. Final Solution was: "Stream.generate(keyGen).limit(MaxRecs+1).filter(s -> s !=null).limit(MaxRecs).forEach(consumer);". Adding two filters was overkiil, and there are robustness problems since the line will hang if Supplier fails to deliver MaxRecs+1 keys. – ScottK Dec 21 '18 at 01:25
5

Try this

Supplier<String> keyGen = new SimpleKeySupplier(keyPrefix, numKeys);
Consumer<String> consumer = (String key) -> System.out.println("key="+key);
Stream.generate(keyGen).filter(s -> s !=null).limit(NumKeys).forEach(consumer);
TongChen
  • 1,414
  • 1
  • 11
  • 21