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 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
- 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)
是使用该项执行某些操作的方法。