Turn linked Objects into Stream or Collection

2020-06-07 07:44发布

I want to iterate over a stacktrace. The stacktrace consists of throwables whose getCause() returns the next throwable. The last call to getCause() returns null. (Example: a -> b -> null)

I've tried to use Stream.iterable() which results in a NullPointerException, since the elements in the iterable can't be null. Here is a short demonstration of the problem:

  public void process() {
      Throwable b = new Throwable();
      Throwable a = new Throwable(b);
      Stream.iterate(a, Throwable::getCause).forEach(System.out::println);
  }

I'm currently using a while loop to create a collection manually:

public void process() {
    Throwable b = new Throwable();
    Throwable a = new Throwable(b);

    List<Throwable> list = new ArrayList<>();
    Throwable element = a;
    while (Objects.nonNull(element)) {
      list.add(element);
      element = element.getCause();
    }
    list.stream().forEach(System.out::println);
  }

Is there a better way (shorter, more functional) to achieve this?

6条回答
家丑人穷心不美
2楼-- · 2020-06-07 08:10

The problem is the missing stop condition in Stream.iterate. In Java 9, you could use

Stream.iterate(exception, Objects::nonNull, Throwable::getCause)

which is equivalent to Java 9’s

Stream.iterate(exception, Throwable::getCause)
      .takeWhile(Objects::nonNull)

See Stream.iterate or Stream.takeWhile.

Since this feature does not exist in Java 8, a back-port would be required:

public static <T> Stream<T>
                  iterate​(T seed, Predicate<? super T> hasNext, UnaryOperator<T> next)
{
    Objects.requireNonNull(next);
    Objects.requireNonNull(hasNext);
    return StreamSupport.stream(
        new Spliterators.AbstractSpliterator<T>(Long.MAX_VALUE, Spliterator.ORDERED) {
            T current = seed;
            int state;
            public boolean tryAdvance(Consumer<? super T> action) {
                Objects.requireNonNull(action);
                T value = current;
                if(state > 0) value = next.apply(value);
                else if(state == 0) state = 1;
                else return false;
                if(!hasNext.test(value)) {
                    state = -1;
                    current = null;
                    return false;
                }
                action.accept(current = value);
                return true;
            }
        },
        false);
}

The semantic is the same as with Java 9’s Stream.iterate:

MyStreamFactory.iterate(exception, Objects::nonNull, Throwable::getCause)
               .forEach(System.out::println); // just an example
查看更多
forever°为你锁心
3楼-- · 2020-06-07 08:11

The recursive Stream::concat() approach creates the entire stream in advance in one recursive call. The lazy takeWhile approach is not available until Java 9.

The following is a lazy, Java 8 approach:

class NullTerminated {
    public static <T> Stream<T>  stream(T start, Function<T, T> advance) {
        Iterable<T> iterable = () -> new Iterator<T>() {
            T next = start;

            @Override
            public boolean hasNext() {
                return next != null;
            }

            @Override
            public T next() {
                T current = next;
                next = advance.apply(current);
                return current;
            }           
        };
        return StreamSupport.stream(iterable.spliterator(), false);
    }
}

Usage:

Throwable b = new Throwable();
Throwable a = new Throwable(b);

NullTerminated.stream(a, Throwable::getCause).forEach(System.out::println);

Update: Replacing Iterator/Iterable.spliterator() with direct construction of a Spliterator:

class NullTerminated {
    public static <T> Stream<T>  stream(T start, Function<T, T> advance) {
        Spliterator<T> sp = new AbstractSpliterator<T>(Long.MAX_VALUE, Spliterator.ORDERED | Spliterator.NONNULL) {
            T current = start;
            @Override
            public boolean tryAdvance(Consumer<? super T> action) {
                if (current != null) {
                    action.accept(current);
                    current = advance.apply(current);
                    return true;
                }
                return false;
            }
        };
        return StreamSupport.stream(sp, false);
    }
}

Update 2:

For a one-off, efficient, minimal-code implementation that converts the chain of Throwable objects into a Stream<Throwable> stream, and immediately using said stream:

Stream.Builder<Throwable> builder = Stream.builder();
for(Throwable t = a; t != null; t = t.getCause())
    builder.accept(t);
builder.build().forEach(System.out::println);

This has the disadvantage of being non-lazy (traversing the entire chain at stream construction time), but avoids the inefficiencies of recursion and Stream.concat().

查看更多
疯言疯语
4楼-- · 2020-06-07 08:13

I have another option via a Spliterator:

static Stream<Throwable> process(Throwable t) {

    Spliterator<Throwable> sp = new AbstractSpliterator<Throwable>(100L, Spliterator.ORDERED) {

        Throwable inner = t;

        @Override
        public boolean tryAdvance(Consumer<? super Throwable> action) {
            if (inner != null) {
                action.accept(inner);
                inner = inner.getCause();
                return true;
            }

            return false;
        }
    };

    return StreamSupport.stream(sp, false);
}
查看更多
啃猪蹄的小仙女
5楼-- · 2020-06-07 08:24

What exactly is wrong with this?

while (exception) {
    System.out.println(exception); //or whatever you want to do
    exception = exception.getCause();
}

There is no point in being "more functional." Functional style is just a tool, and it's clearly inappropriate here.

查看更多
在下西门庆
6楼-- · 2020-06-07 08:25

If I understand you correctly, you can create a Stream with seed root (your head Throwable in linkedlist). As UnaryOperator take is the next Throwable. Example:

Stream.iterate(root, Throwable::getNext)
         .takeWhile(node -> node != null)
         .forEach(node -> System.out.println(node.getCause()));
查看更多
甜甜的少女心
7楼-- · 2020-06-07 08:30

I think that you can do a recursive call here:

static Stream<Throwable> process(Throwable t) {
    return t == null ? Stream.empty() : Stream.concat(Stream.of(t), process(t.getCause()));
}
查看更多
登录 后发表回答