usar ejemplo codigo java collections java-8 java-stream

java - ejemplo - private jlabel



Convertir objetos vinculados en secuencia o colección (6)

Quiero iterar sobre un stacktrace. El seguimiento de pila consiste en lanzamientos cuyo getCause () devuelve el siguiente lanzamiento. La última llamada a getCause () devuelve un valor nulo. (Ejemplo: a -> b -> null)

He intentado usar Stream.iterable () que resulta en una excepción NullPointerException, ya que los elementos en el iterable no pueden ser nulos. Aquí hay una breve demostración del problema:

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

Actualmente estoy usando un bucle while para crear una colección manualmente:

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); }

¿Hay una mejor manera (más corta, más funcional) para lograr esto?


¿Qué es exactamente lo que está mal con esto?

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

No tiene sentido ser "más funcional". El estilo funcional es solo una herramienta, y es claramente inapropiado aquí.


Creo que puedes hacer una llamada recursiva aquí:

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


El enfoque recursivo de Stream::concat() crea la secuencia completa por adelantado en una llamada recursiva. El enfoque perezoso de takeWhile no está disponible hasta Java 9.

El siguiente es un enfoque perezoso de 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); } }

Uso:

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

Actualización: Reemplazando Iterator / Iterable.spliterator() con la construcción directa de un 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); } }

Actualización 2:

Para una implementación única, eficiente, de código mínimo que convierte la cadena de objetos Throwable en una Stream<Throwable> , e inmediatamente utilizando dicha secuencia:

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

Esto tiene la desventaja de no ser perezoso (atravesar toda la cadena en el momento de la construcción del flujo), pero evita las ineficiencias de la recursión y Stream.concat ().


El problema es la condición de parada que falta en Stream.iterate . En Java 9, podrías usar

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

que es equivalente a la de Java 9

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

Ver Stream.iterate o Stream.takeWhile .

Dado que esta característica no existe en Java 8, se requeriría un puerto posterior:

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); }

La semántica es la misma que con Stream.iterate Java 9:

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


Si te comprendo correctamente, puedes crear un Stream con una root de inicio (tu cabeza Throwable en la lista enlazada). Como toma UnaryOperator es el siguiente Throwable . Ejemplo:

Stream.iterate(root, Throwable::getNext) .takeWhile(node -> node != null) .forEach(node -> System.out.println(node.getCause()));


Tengo otra opción a través de un 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); }