for - hacer-mientras con Java8-Opcional
foreach java 8 (4)
Con frecuencia uso el patrón de bucle do-while-checkNextForNull-getNext (no sé si hay un nombre oficial para él) en algunos de mis proyectos. Pero en Java8, el uso de Opcional se considera como un código más limpio que la comprobación de referencias nulas en el código del cliente. Pero cuando se usa Opcional en este patrón de bucle, el código se vuelve un poco detallado y feo, pero como Opcional tiene algunos métodos útiles, yo esperaría que exista una forma más limpia que la que se me ocurrió a continuación.
Ejemplo:
Dada la siguiente clase.
class Item {
int nr;
Item(nr) {
this.nr = nr;
// an expensive operation
}
Item next() {
return ...someCondition....
? new Item(nr + 1)
: null;
}
}
En el que el primer elemento siempre tiene nr == 1 y cada elemento determina el siguiente elemento, y no desea crear nuevos elementos innecesarios.
Puedo usar el siguiente patrón de bucle do-while-checkNextForNull-getNext en el código del cliente:
Item item = new Item(1);
do {
// do something with the item ....
} while ((item = item.next()) != null);
Con Java8-Optional, la clase dada se convierte en:
class Item {
....
Optional<Item> next() {
return ...someCondition....
? Optional.of(new Item(nr + 1))
: Optional.empty();
}
}
Y luego el patrón de bucle do-while-checkNextForNull-getNext se vuelve un poco feo y detallado:
Item item = new Item(1);
do {
// do something with the item ....
} while ((item = item.next().orElse(null)) != null);
La parte orElse(null)) != null
siente incómoda.
He buscado otro tipo de bucles, pero no he encontrado uno mejor. ¿Hay una solución más limpia?
Actualizar:
Es posible usar un bucle for-each mientras que al mismo tiempo se evitan las referencias nulas (el uso de referencias nulas se considera una mala práctica). Esta solución ha sido propuesta por Xavier Delamotte y no necesita Java8-Optional.
Implementación con un iterador genérico:
public class Item implements Iterable<Item>, Iterator<Item> {
int nr;
Item(int nr) {
this.nr = nr;
// an expensive operation
}
public Item next() {
return new Item(nr + 1);
}
public boolean hasNext() {
return ....someCondition.....;
}
@Override
public Iterator<Item> iterator() {
return new CustomIterator(this);
}
}
y
class CustomIterator<T extends Iterator<T>> implements Iterator<T> {
T currentItem;
boolean nextCalled;
public CustomIterator(T firstItem) {
this.currentItem = firstItem;
}
@Override
public boolean hasNext() {
return currentItem.hasNext();
}
@Override
public T next() {
if (! nextCalled) {
nextCalled = true;
return currentItem;
} else {
currentItem = currentItem.next();
return currentItem;
}
}
}
Entonces el código del cliente se vuelve muy simple / limpio:
for (Item item : new Item(1)) {
// do something with the item ....
}
Aunque esto puede verse como una violación del contrato de Iterator porque el new Item(1)
está incluido en el bucle, mientras que normalmente, el bucle for llama inmediatamente a next () y omite así el primer objeto. En otras palabras: para el primer objeto, next () se infringe porque devuelve el primer objeto en sí.
en Java8, el uso de Opcional se considera como un código más limpio que la comprobación de referencias nulas en el código de cliente
No, es al revés: Opcional puede usarse donde ayuda a escribir código limpio. Donde no es así, solo adhiérase a la vieja expresión idiomática. No sienta ninguna presión para usarlo si su idioma actual se ve bien, y lo hace, en mi opinión. Como ejemplo, este sería un buen uso del Opcional:
item.next().map(Object::toString).ifPresent(System.out::println);
Como necesita salir del lazo en el primer Opcional no presente, esto realmente no ayuda.
Sin embargo, asumo que su verdadero interés es más general: aprovechar las características de Java 8 para su código. La abstracción que debes elegir es Stream:
itemStream(() -> new Item(1)).forEach(item -> { ... all you need ... });
Y, naturalmente, ahora puedes volverte loco con el procesamiento de flujo:
itemStream(() -> new Item(1)).filter(item.nr > 3).mapToInt(Item::nr).sum();
Así es como construirías la secuencia:
import java.util.Spliterators;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
public class ItemSpliterator extends Spliterators.AbstractSpliterator<Item>
{
private Supplier<Item> supplyFirst;
private Item lastItem;
public ItemSpliterator(Supplier<Item> supplyFirst) {
super(Long.MAX_VALUE, ORDERED | NONNULL);
this.supplyFirst = supplyFirst;
}
@Override public boolean tryAdvance(Consumer<? super Item> action) {
Item item;
if ((item = lastItem) != null)
item = lastItem = item.next();
else if (supplyFirst != null) {
item = lastItem = supplyFirst.get();
supplyFirst = null;
}
else return false;
if (item != null) {
action.accept(item);
return true;
}
return false;
}
public static Stream<Item> itemStream(Supplier<Item> supplyFirst) {
return StreamSupport.stream(new ItemSpliterator(supplyFirst), false);
}
}
Con esto, está a un paso de la capacidad de paralelizar de forma transparente su cálculo. Debido a que la secuencia de elementos es fundamentalmente secuencial, sugiero que busque en mi publicación de blog sobre este tema.
Puedes hacer algo como esto:
Optional<Item> item = Optional.of(new Item(1));
do {
Item value = item.get();
// do something with the value ....
} while ((item = value.next()).isPresent());
o (para evitar la variable adicional):
Optional<Item> item = Optional.of(new Item(1));
do {
// do something with item.get() ....
} while ((item = item.get().next()).isPresent());
Simplemente agregue el soporte de bucle a su API:
class Item {
int nr;
Item(int nr) {
this.nr = nr;
// an expensive operation
}
public void forEach(Consumer<Item> action) {
for(Item i=this; ; i=new Item(i.nr + 1)) {
action.accept(i);
if(!someCondition) break;
}
}
public Optional<Item> next() {
return someCondition? Optional.of(new Item(nr+1)): Optional.empty();
}
}
Entonces simplemente puede iterar a través de la expresión lambda
i.forEach(item -> {whatever you want to do with the item});
o referencias de métodos
i.forEach(System.out::println);
Si desea admitir operaciones más sofisticadas que solo para cada bucle, las transmisiones compatibles son el camino correcto a seguir. Es similar en que su implementación encapsula cómo iterar sobre los Item
.
Como esto está relacionado con algún tipo de diseño, se me ocurre un diseño a continuación.
Crear una interfaz que soporte para proporcionar el próximo opcional.
public interface NextProvidble<T> {
Optional<T> next();
}
El artículo implementa la interfaz NextProvidble.
public class Item implements NextProvidble<Item> {
int nr;
Item(int nr) {
this.nr = nr;
// an expensive operation
}
@Override
public Optional<Item> next() {
return /*...someCondition....*/ nr < 10 ? Optional.of(new Item(nr + 1)) : Optional.empty();
}
@Override
public String toString() {
return "NR : " + nr;
}
}
Aquí uso / ... someCondition .... / as nr <10
Y nueva clase para Custom Do While, como a continuación.
public abstract class CustomDoWhile<T extends NextProvidble<T>> {
public void operate(T t) {
doOperation(t);
Optional<T> next = t.next();
next.ifPresent( nextT -> operate(nextT));
}
protected abstract void doOperation(T t);
}
Ahora lo que tienes que hacer en tu código de cliente.
new CustomDoWhile<Item>() {
@Override
protected void doOperation(Item item) {
System.out.println(item.toString());
}
}.operate(new Item(1));
Puede ser muy claro. Por favor agregue sus pensamientos