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