do-while with Java8-Optional

时间:2015-02-26 11:48:42

标签: java loops while-loop java-8 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<Item> 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<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);
    }
}

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

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

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

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

5 个答案:

答案 0 :(得分:10)

您可以这样做:

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

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

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

答案 1 :(得分:6)

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

不,反过来说:可以在帮助编写清洁代码的地方使用可选项。在它没有的地方,只要坚持旧的习语。如果你现有的成语看起来很好的话,不要觉得有任何使用它的压力 - 在我看来它确实如此。举个例子,这可以很好地使用Optional:

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

由于您需要在第一个不存在的可选项中突破循环,这对您来说并不是真的有帮助。

但是,我认为您的真正兴趣更为通用:为您的代码利用Java 8的功能。你应该选择的抽象是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<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);
  }
}

通过这种方式,您可以轻松地无缝并行化计算。由于您的商品流基本上是顺序的,因此我建议您查看关于此主题的blog post

答案 2 :(得分:3)

只需在您的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();
    }
}

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

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

或方法参考

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

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

答案 3 :(得分:0)

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

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

DECLARE @XML as XML
SET @XML = '<Product>
 <field name="IsCustomer" type="System.Boolean, mscorlib">
    <boolean>false</boolean>
  </field>
</Product>'

SELECT EMP.t.value('boolean[1]','varchar(20)') as EmployeeID
FROM   @XML.nodes('/Product/field') as EMP(t)

项目实现NextProvidble接口。

public interface NextProvidble<T> {

    Optional<T> next();
}

这里我使用/...someCondition..../ as nr&lt; 10

Custom Do的新课程如下所示。

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

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

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

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

答案 4 :(得分:0)

Java 9开始,在此处删除另一个替代方案。

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

其中doSomething(Item item)是对项目执行某些操作的方法。