Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Problems using interactive debuggers with Java 8 streams

I love Java 8 streams. They are intuitive, powerful and elegant. But they do have one major drawback IMO: they make debugging much harder (unless you can solve your problem by just debugging lambda expressions, which is answered here).

Consider the following two equivalent fragments:

int smallElementBitCount = intList.stream()
    .filter(n -> n < 50)
    .mapToInt(Integer::bitCount)
    .sum();

and

int smallElementBitCount = 0; 
for (int n: intList) {
    if (n < 50) {
        smallElementBitCount += Integer.bitCount(n);
    }
}

I find the first one much clearer and more succinct. However consider the situation in which the result is not what you were expecting. What do you do?

In the traditional iterative style, you put a breakpoint on the totalBitCount += Integer.bitCount(n); line and step through each value in the list. You can see what the current list element is (watch n), the current total (watch totalBitCount) and, depending on the debugger, what the return value of Integer.bitCount is.

In the new stream style all of this is impossible. You can put a breakpoint on the entire statement and step through to the sum method. But in general this is close to useless. In this situation in my test my call stack was 11 deep of which 10 were java.util methods that I had no interest in. It is impossible to step through the code testing predicates or performing the mapping.

It is noted in the answers to Debugging streams question that iteractive debuggers work fairly well for breaking inside lambda expressions (such as the n < 50 predicate). But in many situations the most appropriate breakpoint is not within a lambda.

Clearly this is a simple piece of code to debug. But once custom reductions and collections are added, or more complex chains of filters and maps, it can become a nightmare to debug.

I have tried this on NetBeans and Eclipse and both seem to have the same issues.

Over the last few months I've got used to debugging using .peek calls to log interim values or moving interim steps into their own named methods or, in extreme cases, refactoring as iteration until any bugs are sorted out. This works but it reminds me a lot of the bad old days before modern IDEs with integrated interactive debuggers when you had to scatter printf statements through code.

Surely there's a better way.

Specifically I would like to know:

  • have others experienced this same issue?
  • are there any 'stream aware' interactive debuggers available?
  • are there better techniques for debugging this style of code?
  • is this a reason to restrict the use of streams to simple cases?

Any techniques that you have found successful would be much appreciated.

like image 466
sprinter Avatar asked Dec 30 '14 03:12

sprinter


People also ask

What is the disadvantage of Java 8 features?

Default Methods are distracting Default methods enable a default implementation of a function in the interface itself. This is definitely one of the coolest new features Java 8 brings to the table but it somewhat interferes with the way we used to do things.

Does Java 8 support streams?

Java 8 offers the possibility to create streams out of three primitive types: int, long and double. As Stream<T> is a generic interface, and there is no way to use primitives as a type parameter with generics, three new special interfaces were created: IntStream, LongStream, DoubleStream.

When we should not use parallel stream?

Similarly, don't use parallel if the stream is ordered and has much more elements than you want to process, e.g. This may run much longer because the parallel threads may work on plenty of number ranges instead of the crucial one 0-100, causing this to take very long time.

Is Java 8 stream faster than for loop?

Yes, streams are sometimes slower than loops, but they can also be equally fast; it depends on the circumstances. The point to take home is that sequential streams are no faster than loops.


1 Answers

I'm not entirely certain there is a viable work around for this problem. By using streams you are effectively delegating iteration (and the associated code) to the VM as far as I understand it, thus shoving the process into a black box that is the stream itself.

At least from what I've read about them. This is sort of what's happened around lambda code for me (if they're complex enough, it's very difficult to track what's happening around them). I'd be very interested in any debugging options out there, but I haven't personally found any.

like image 86
Waldo Terry Avatar answered Sep 30 '22 03:09

Waldo Terry