当前位置: 首页 > 知识库问答 >
问题:

使用Java8时执行可选操作

杜祺
2023-03-14

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

示例:

考虑到下面的课程。

class Item {
    int nr;

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

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

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

我可以在客户端代码中使用以下循环do,同时检查NextForFull getNext模式:

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

在Java8可选的情况下,给定的类变成:

class Item {
    ....

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

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

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

orElse(null))!=空零件感觉不舒服。

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

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

使用通用迭代器实现:

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

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

共有3个答案

贺轶
2023-03-14

只需将循环支持添加到您的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循环更复杂的操作,那么支持流是正确的选择。它的相似之处在于,您的实现封装了如何迭代s。

宗政坚白
2023-03-14

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

不,情况正好相反:可选可以在有助于编写更简洁代码的地方使用。如果没有,只需坚持旧的习惯用法。如果您现有的习惯用法看起来不错,请不要感到使用它的任何压力——在我看来,确实如此。例如,这将是可选的良好用法:

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

由于您需要在第一个不存在的可选项上打破循环,这并没有真正的帮助。

然而,我认为您的真正兴趣更广泛:在代码中利用Java8的特性。您应该选择的抽象是流:

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

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

董桐
2023-03-14

你可以这样做:

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());
 类似资料:
  • 我有一个方法,它迭代一个映射,对值执行一个操作,并填充一个要返回的映射。 我的问题是,我如何将其转换为Java8(执行不循环的操作)? 代码:

  • 我有一个java。util。流动包含键值对的流,如: 现在,我想合并所有具有相同密钥的条目: 数据已经排序,因此只需合并连续的数据集。 现在,我正在寻找一种方法来转换上述流的内容,而不将所有数据集加载到内存中。 我更喜欢得到一个java.util.stream.Stream,结果是一个不同的对象类型包含一个值列表,而不是一个单独的值。 我唯一的方法是一个自定义迭代器,它执行合并,但是转换为迭代器并

  • 考虑一个和方法。我想将s映射到s并获得第一个。显而易见的解决方案是使用,但要求返回一个流,而没有方法(或者它是或提供一个方法将其转换为,或将其作为)查看。 我能想出的最好的办法是: 但这似乎是一个很普通的案例,但却显得太冗长了。谁有更好的主意?

  • 问题内容: 我有一个http服务器(使用启动),我想做一些操作。 我该怎么做(在Linux上)?在ctrl-C的情况下可以进行那些操作吗? 我不熟悉Unix信号,因此答案可能很简单。 问题答案: 您可以使用信号包订购TERM和INT信号。但是请注意,只有在明确终止进程时才发送这些信号。正常退出(由流程本身启动)不涉及任何信号。我认为,对于正常退出,只需在主例程中执行某些操作即可(该例程应该生成工作

  • 例如,您有一个要转换为JSONObject的pojo列表。你有一个POJO的列表。但是为了转换为JSONObject,您需要使用JSONObject put方法。 如果我只想做一个手术,我就可以做