java 使用 Java8-Optional 做时

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/28741673/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-02 14:06:02  来源:igfitidea点击:

do-while with Java8-Optional

javaloopswhile-loopjava-8optional

提问by Devabc

I'm frequently using the do-while-checkNextForNull-getNext looping pattern (don't know if there is an official name for it) in some of my projects. But in Java8, the use of Optional is considered as cleaner code than checking for null references in client-code. But when using Optional in this looping pattern, the code gets a bit verbose and ugly, but because Optional has some handy methodS, I would expect that there must exist a cleaner way than the one I came up with below.

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

Example:

例子:

Given the following class.

鉴于以下课程。

class Item {
    int nr;

    Item(nr) {
        this.nr = nr;
        // an expensive operation
    }

    Item next() {
        return ...someCondition....
            ? new Item(nr + 1)
            : null;
    }
}

In which the first item always has nr==1 and each item determines the next item, and you don't want to create unnecessary new items.

其中第一个项目总是有 nr==1 并且每个项目决定下一个项目,并且您不想创建不必要的新项目。

I can use the following looping do-while-checkNextForNull-getNext pattern in client-code:

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

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

With Java8-Optional, the given class becomes:

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

class Item {
    ....

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

And then the do-while-checkNextForNull-getNext looping pattern becomes a bit ugly and verbose:

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

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

The orElse(null)) != nullpart feels uncomfortable.

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

I have looked for other kind of loops, but haven't found a better one. Is there a cleaner solution?

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

Update:

更新:

It is possible to use a for-each loop while at the same time avoiding null-references (the use of null-references is considered as a bad practice). This solution has been proposed by Xavier Delamotte, and doesn't need Java8-Optional.

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

Implementation with a generic iterator:

使用通用迭代器实现:

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

and

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

Then client code becomes very simple/clean:

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

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

Although this may be seen as a violation of the Iterator contract because the new Item(1)object is included in the loop, whereas normally, the for loop would immediately call next() and thus skipping the first object. In other words: for the first object, next() is violated because it returnS the first object itself.

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

回答by Eran

You can do something like this :

你可以这样做:

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

or (to avoid the extra variable) :

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

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

回答by Marko Topolnik

in Java8, the use of Optional is considered as cleaner code than checking for null references in client-code

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

No, it is the other way around: Optional can be used where it helpswrite cleaner code. Where it doesn't, just stick to the old idiom. Do not feel any pressure to use it if your existing idiom looks fine—and it does, in my opinion. As an example, this would be good usage of the Optional:

不,恰恰相反:Optional 可用于帮助编写更清晰代码的地方。如果没有,只需坚持旧习语。如果您现有的习语看起来不错,请不要感到使用它的任何压力 - 在我看来确实如此。例如,这将是 Optional 的良好用法:

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

Since you need to break out of the loop on the first non-present Optional, this doesn't really help.

由于您需要在第一个不存在的 Optional 上跳出循环,因此这并没有真正的帮助。

However, I assume your true interest is more general: leveraging the features of Java 8 for your code. The abstraction you should pick is the Stream:

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

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

And, naturally, you can now go wild with stream processing:

而且,自然而然地,您现在可以进行流处理:

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

This is how you would construct the stream:

这是您构造流的方式:

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

With this you are a tiny step away from the ability to seamlessly parallelize your computation. Since your item stream is fundamentally sequential, I suggest looking into my blog poston this subject.

有了这个,您离无缝并行计算的能力还有一小步。由于您的项目流基本上是连续的,我建议查看我关于此主题的博客文章

回答by Holger

Just add the loop support to your API:

只需将循环支持添加到您的 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();
    }
}

Then you can simply iterate via lambda expression

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

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

or method references

或方法参考

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

If you want to support more sophisticated operations than just forEach loops, supporting streamsis the right way to go. It's similar in that your implementation encapsulates how to iterate over the Items.

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

回答by Chamly Idunil

Since this is related to some kind of design i come up with below design.

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

Create interface which support to provide optional next.

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

public interface NextProvidble<T> {

    Optional<T> next();
}

Item implement NextProvidble interface.

Item 实现 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;
    }
}

Here i use /...someCondition..../ as nr < 10

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

And new class for Custom Do While as below.

以及自定义 Do While 的新类,如下所示。

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

Now what you have to done in your client code.

现在您必须在客户端代码中完成什么。

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

It may very clear. Please add your thoughts.

它可能很清楚。请补充您的想法。

回答by jbx

Dropping another alternative here that is available since Java 9.

在此处删除自Java 9以来可用的另一种替代方法。

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

Where doSomething(Item item)is the method that does something with the item.

doSomething(Item item)对项目执行某些操作的方法在哪里。