0

I have a class called Data which has only one method:

public boolean isValid()

I have a Listof Dataand I want to loop through them via a Java 8 stream. I need to count how many valid Data objects there are in this List and print out only the valid entries.

Below is how far I've gotten but I don't understand how.

List<Data> ar = new ArrayList<>();
...
// ar is now full of Data objects.
...

int count = ar.stream()
            .filter(Data::isValid)
            .forEach(System.out::println)
            .count(); // Compiler error, forEach() does not return type stream.

My second attempt: (horrible code)

List<Data> ar = new ArrayList<>();
...
// Must be final or compiler error will happen via inner class.
final AtomicInteger counter = new AtomicInteger();
ar.stream()
    .filter(Data:isValid)
    .forEach(d -> 
    {
        System.out.println(d);
        counter.incrementAndGet();
    };
System.out.printf("There are %d/%d valid Data objects.%n", counter.get(), ar.size());
Holger
  • 285,553
  • 42
  • 434
  • 765
Hatefiend
  • 3,416
  • 6
  • 33
  • 74

2 Answers2

5

If you don’t need the original ArrayList, containing a mixture of valid and invalid objects, later-on, you might simply perform a Collection operation instead of the Stream operation:

ar.removeIf(d -> !d.isValid());
ar.forEach(System.out::println);
int count = ar.size();

Otherwise, you can implement it like

List<Data> valid = ar.stream().filter(Data::isValid).collect(Collectors.toList());
valid.forEach(System.out::println);
int count = valid.size();

Having a storage for something you need multiple times is not so bad. If the list is really large, you can reduce the storage memory by (typically) factor 32, using

BitSet valid = IntStream.range(0, ar.size())
    .filter(index -> ar.get(index).isValid())
    .collect(BitSet::new, BitSet::set, BitSet::or);
valid.stream().mapToObj(ar::get).forEach(System.out::println);
int count = valid.cardinality();

Though, of course, you can also use

int count = 0;
for(Data d: ar) {
    if(d.isValid()) {
        System.out.println(d);
        count++;
    }
}
Holger
  • 285,553
  • 42
  • 434
  • 765
  • neat solution (as usual from u). one question still. where did the 32 appear here? it's not like we really know the Data internals and how much that class weights. – Eugene Nov 23 '16 at 07:58
  • @Eugene: the `BitSet` uses exactly one bit per entry, whereas an `ArrayList` stores a reference per entry, which is typically 32 bit (either matching the architecture or due to the use of compressed OOPs). It’s not about the per-object overhead, but how these data structures *scale* with large sizes. Of course, we should be careful with assumptions about internals, but certain implementation classes bear their fundamental nature even in its name (“bit” or “array”) to allow the developer to make an appropriate choice. The factor may be even 64 instead of 32, but that’s why I wrote “typically”… – Holger Nov 23 '16 at 09:03
  • so 32 comes from diff between the reference storage and one single bit in BitSet. Would it be appropriate to call this factor the *shallow* difference, as opposed to *deep* difference? – Eugene Nov 23 '16 at 09:33
  • @Eugene: well, here it is the *only* difference… – Holger Nov 23 '16 at 10:05
4

Peek is similar to foreach, except that it lets you continue the stream.

ar.stream().filter(Data::isValid)
            .peek(System.out::println)
            .count();
Kishore Bandi
  • 5,537
  • 2
  • 31
  • 52
  • Why would I ever use `forEach` then? – Hatefiend Nov 22 '16 at 16:59
  • @Hatefiend you use foreach when you want to iterate over a list, perform some operations on each of its objects and leave. Say you want to compute the total interest for a list of customers. You could just use foreach and on each object call `calculateInterest()`. The result of this is anyways saved on the Original Customer Object, so you don't have to collect it either. – Kishore Bandi Nov 22 '16 at 17:09
  • But by this logic I could just do `list.peek(x -> x.calculateInterest(); };` No? How does that differ from forEach() – Hatefiend Nov 22 '16 at 17:16
  • Also I see that `peek` is a suggest debug only method. See the following [api link](https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html#peek-java.util.function.Consumer-) – Hatefiend Nov 22 '16 at 17:18
  • 4
    @Hatefiend See also http://stackoverflow.com/questions/33635717/in-java-streams-is-peek-really-only-for-debugging – bradimus Nov 22 '16 at 17:19