将链接的对象转换为流或集合

Ari*_*ion 19 java collections java-8 java-stream

我想迭代堆栈跟踪.stacktrace由throwables组成,其getCause()返回下一个throwable.对getCause()的最后一次调用返回null.(示例:a - > b - > null)

我试图使用Stream.iterable()导致NullPointerException,因为iterable中的元素不能为null.以下是该问题的简短演示:

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

我目前正在使用while循环手动创建集合:

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);
  }
Run Code Online (Sandbox Code Playgroud)

有没有更好的方法(更短,更实用)来实现这一目标?

Hol*_*ger 16

问题是缺少停止条件Stream.iterate.在Java 9中,您可以使用

Stream.iterate(exception, Objects::nonNull, Throwable::getCause)
Run Code Online (Sandbox Code Playgroud)

这相当于Java 9的

Stream.iterate(exception, Throwable::getCause)
      .takeWhile(Objects::nonNull)
Run Code Online (Sandbox Code Playgroud)

Stream.iterateStream.takeWhile.

由于Java 8中不存在此功能,因此需要后端口:

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);
}
Run Code Online (Sandbox Code Playgroud)

语义与Java 9相同Stream.iterate:

MyStreamFactory.iterate(exception, Objects::nonNull, Throwable::getCause)
               .forEach(System.out::println); // just an example
Run Code Online (Sandbox Code Playgroud)


Eug*_*ene 14

我想你可以在这里做一个递归调用:

static Stream<Throwable> process(Throwable t) {
    return t == null ? Stream.empty() : Stream.concat(Stream.of(t), process(t.getCause()));
}
Run Code Online (Sandbox Code Playgroud)

  • 这具有在流构建时已经有效地遍历链的缺点.此外,嵌套的`Stream.concat`的结果可能效率很低(javadoc甚至警告过这个).虽然,原因链通常不应该很长...... (7认同)

AJN*_*eld 9

递归Stream::concat()方法在一次递归调用中预先创建整个流.takeWhile直到Java 9才能使用惰性方法.

以下是一个懒惰的Java 8方法:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

用法:

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

NullTerminated.stream(a, Throwable::getCause).forEach(System.out::println);
Run Code Online (Sandbox Code Playgroud)

更新:更换Iterator/ Iterable.spliterator()直接构建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);
    }
}
Run Code Online (Sandbox Code Playgroud)

更新2:

对于一次性,高效,最小化的代码实现,它将Throwable对象链转换为Stream<Throwable>流,并立即使用所述流:

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

这样做的缺点是非延迟(在流构建时遍历整个链),但避免了递归和Stream.concat()的低效率.

  • 有趣的是,我赞成@Holger的`Stream.iterator()`后端端口......记住所有这些通用实现代码都会被编写一次并隐藏在一个库中,它可以在任何类型中使用和重用. <T>`.@Eugene的递归方法仅针对`Throwable`对象进行硬连接,因此不能直接重用,奇怪的是我发现比基于`Iterator` /`Spliterator`的流更难理解.对于未来的开发人员:有人可能会从该代码中"学习",虽然它可能适用于两级或三级"Throwable"链,但它具有相当低的效率,因此不是最好的例子. (2认同)
  • 该`Stream.iterate`不仅添加到代码库只有一次,它可以切换到Java 9时,也可以再次删除,因为呼叫可以被重定向到标准的API,没有力气再. (2认同)

Eug*_*ene 5

我有另一种选择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);
}
Run Code Online (Sandbox Code Playgroud)