do-while with Java8-Optional

在我的一些项目中,我经常使用do-while-checkNextForNull-getNext循环模式(不知道它是否有正式名称)。 但是在Java8中,使用Optional比在客户端代码中检查空引用更清晰。 但是当在这种循环模式中使用Optional时,代码变得有点冗长和丑陋,但因为Optional有一些方便的方法,我希望必须存在比我在下面提出的更简洁的方法。

例:

鉴于以下课程。

class Item { int nr; Item(nr) { this.nr = nr; // an expensive operation } Item next() { return ...someCondition.... ? new Item(nr + 1) : null; } } 

其中第一个项目始终具有nr == 1并且每个项目确定下一个项目,并且您不希望创建不必要的新项目。

我可以在客户端代码中使用以下循环do-while-checkNextForNull-getNext模式:

 Item item = new Item(1); do { // do something with the item .... } while ((item = item.next()) != null); 

使用Java8-Optional,给定的类变为:

 class Item { .... Optional next() { return ...someCondition.... ? Optional.of(new Item(nr + 1)) : Optional.empty(); } } 

然后do-while-checkNextForNull-getNext循环模式变得有点丑陋和冗长:

 Item item = new Item(1); do { // do something with the item .... } while ((item = item.next().orElse(null)) != null); 

orElse(null)) != null部分感觉不舒服。

我已经寻找其他类型的循环,但没有找到更好的循环。 有更清洁的解决方案吗?

更新:

可以使用for-each循环,同时避免空引用(使用null引用被认为是一种不好的做法)。 该解决方案由Xavier Delamotte提出,不需要Java8-Optional。

使用通用迭代器实现:

 public class Item implements Iterable, Iterator { 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 iterator() { return new CustomIterator(this); } } 

 class CustomIterator<T extends Iterator> implements Iterator { 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; } } } 

然后客户端代码变得非常简单/干净:

 for (Item item : new Item(1)) { // do something with the item .... } 

虽然这可能被视为违反迭代器契约,因为new Item(1)对象包含在循环中,而通常,for循环会立即调用next(),从而跳过第一个对象。 换句话说:对于第一个对象,违反了next(),因为它返回第一个对象本身。

你可以这样做:

 Optional item = Optional.of(new Item(1)); do { Item value = item.get(); // do something with the value .... } while ((item = value.next()).isPresent()); 

或(以避免额外的变量):

 Optional item = Optional.of(new Item(1)); do { // do something with item.get() .... } while ((item = item.get().next()).isPresent()); 

在Java8中,使用Optional被认为是比在客户端代码中检查空引用更清晰的代码

不,它是另一种方式:可以在有助于编写更清晰代码的地方使用可选项 。 如果没有,只要坚持旧的习语。 如果您现有的成语看起来很好,请不要感到任何使用它的压力 – 在我看来它确实如此。 举个例子,这可以很好地使用Optional:

 item.next().map(Object::toString).ifPresent(System.out::println); 

由于你需要在第一个不存在的Optional上跳出循环,这实际上没有用。

但是,我认为您的真正兴趣更为通用:为您的代码利用Java 8的function。 你应该选择的抽象是Stream:

 itemStream(() -> new Item(1)).forEach(item -> { ... all you need ... }); 

而且,当然,您现在可以使用流处理:

 itemStream(() -> new Item(1)).filter(item.nr > 3).mapToInt(Item::nr).sum(); 

这是你构建流的方法:

 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 { private Supplier supplyFirst; private Item lastItem; public ItemSpliterator(Supplier supplyFirst) { super(Long.MAX_VALUE, ORDERED | NONNULL); this.supplyFirst = supplyFirst; } @Override public boolean tryAdvance(Consumer 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 itemStream(Supplier supplyFirst) { return StreamSupport.stream(new ItemSpliterator(supplyFirst), false); } } 

有了这个,您距离无缝并行化计算的能力只有一步之遥。 由于您的项目流基本上是顺序的,我建议查看我关于此主题的博客文章 。

只需将循环支持添加到您的API:

 class Item { int nr; Item(int nr) { this.nr = nr; // an expensive operation } public void forEach(Consumer action) { for(Item i=this; ; i=new Item(i.nr + 1)) { action.accept(i); if(!someCondition) break; } } public Optional next() { return someCondition? Optional.of(new Item(nr+1)): Optional.empty(); } } 

然后你可以简单地通过lambda表达式进行迭代

  i.forEach(item -> {whatever you want to do with the item}); 

或方法参考

  i.forEach(System.out::println); 

如果你想支持比forEach循环更复杂的操作, 支持流是正确的方法。 它的相似之处在于您的实现封装了如何迭代Item的方法。

由于这与某种设计有关,我想出了以下设计。

创建支持提供可选的接口。

 public interface NextProvidble { Optional next(); } 

项目实现NextProvidble接口。

 public class Item implements NextProvidble { int nr; Item(int nr) { this.nr = nr; // an expensive operation } @Override public Optional next() { return /*...someCondition....*/ nr < 10 ? Optional.of(new Item(nr + 1)) : Optional.empty(); } @Override public String toString() { return "NR : " + nr; } } 

在这里我使用/ ... someCondition .... / as nr <10

Custom Do的新课程如下所示。

 public abstract class CustomDoWhile> { public void operate(T t) { doOperation(t); Optional next = t.next(); next.ifPresent( nextT -> operate(nextT)); } protected abstract void doOperation(T t); } 

现在您需要在客户端代码中完成的工作。

  new CustomDoWhile() { @Override protected void doOperation(Item item) { System.out.println(item.toString()); } }.operate(new Item(1)); 

可能很清楚。 请添加您的想法。

从Java 9开始,在这里删除另一个替代方案。

 Stream.iterate(new Item(1), Item::hasNext, Item::next) .forEach(this::doSomething) 

doSomething(Item item)是使用该项执行某些操作的方法。