是否可以在Java8StreamsAPI中定义可选的流或类似异常的行为?

是否可以在Java8StreamsAPI中定义可选的流或类似异常的行为?,java,java-8,java-stream,Java,Java 8,Java Stream,让我们拥有一系列操作(例如映射、过滤、平面映射等)产生的对象流。现在我想对它们做一个特定的操作,但前提是给定的谓词为true。否则我想马上归还别的东西 一个简单的例子。我有很多不同的食物。如果它们都可以食用,我想对它们进行烹饪操作,并返回熟食清单。但如果其中任何一种都不能食用,我想立即返回一个空列表 我想到的解决方案很少,但我对其中任何一个都不满意 我可以首先使用流上的isEdible谓词执行allMatch操作,但这将导致终止它,我需要再次重复初始操作 在检查可食性之前,我可以保留作为初步操作

让我们拥有一系列操作(例如映射、过滤、平面映射等)产生的对象流。现在我想对它们做一个特定的操作,但前提是给定的谓词为true。否则我想马上归还别的东西

一个简单的例子。我有很多不同的食物。如果它们都可以食用,我想对它们进行烹饪操作,并返回熟食清单。但如果其中任何一种都不能食用,我想立即返回一个空列表

我想到的解决方案很少,但我对其中任何一个都不满意

我可以首先使用流上的
isEdible
谓词执行
allMatch
操作,但这将导致终止它,我需要再次重复初始操作

在检查可食性之前,我可以保留作为初步操作结果的集合,但因此我需要对所有元素执行它们。这是次优的,因为结果可能是,第一个是不可食用的,
allMatch
会更早地返回

或者我可以设计一个hacky
reduce
例程,但它也无法在谓词失败时停止处理元素

我所希望的是下面的代码。使用当前的API是否可能

source.stream()
    // some operations
    .ifAny(food -> !food.isEdible(), new LinkedList<Food>())
    // other operations if previous step not failed
    .peek(food -> food.prepare())
    .collect(Collectors.toList());
source.stream()
//一些操作
.ifAny(food->!food.isEdible(),新链接列表())
//如果上一步未失败,则执行其他操作
.peek(食物->食物.prepare())
.collect(Collectors.toList());

这并不是您想要的,但使用三元运算符将使两步解决方案看起来更干净,它还应具有最佳性能:

return source.stream()
    .allMatch(this::isEdible)
    ? source.stream()
        .map(this::prepare()) // do stuff
        .collect(Collectors.toList())
    : Collections.emptyList();

这里有一个我刚刚发明的方法。它是一个包装
收集器
并检查每个元素是否沿途传递谓词的类。最后,如果任何元素失败,它将返回默认返回值,而不是收集的返回值

不利的一面是,即使第一个元素没有通过谓词,您仍然必须遍历每个元素,但有利的一面是:

  • 每个元素只需迭代一次
  • 简单的内联语法
  • 轻松地与现有流和收集器语法及用例集成
基于这些,我认为这是值得的

public class SatisfyableCollector<T,A,R> implements Collector<T,A,R> {
    private Predicate<T> predicate; //The predicate used to test each element
    private boolean elmHasFailed; //True once an element has failed the predicate
    private Collector<T,A,R> collector; //The collector this wraps
    private R defaultResult; //The result to return at the end if an element failed

    public static <T,A,R> SatisfyableCollector<T,A,R> of(Collector<T,A,R> collector, Predicate<T> predicate, R defaultResult) {
      return new SatisfyableCollector<>(collector, predicate, defaultResult);
    }

    private SatisfyableCollector(Collector<T,A,R> collector, Predicate<T> predicate, R defaultResult) {
      this.predicate = predicate;
      this.collector = collector;
      this.defaultResult = defaultResult;
      elmHasFailed = false;
    }

    @Override
    public Supplier<A> supplier() {
      return collector.supplier();
    }

    @Override
    /** Before accumulating the new element t, check it against the predicate */
    public BiConsumer<A, T> accumulator() {
      return (a, t) -> {
        if (! predicate.test(t)) {
          elmHasFailed = true;
        }
        collector.accumulator().accept(a,t);
      };
    }

    @Override
    public BinaryOperator<A> combiner() {
      return collector.combiner();
    }

    @Override
    /** At the end, check if something failed. If so, return the default. 
      * Otherwise the wrapped collector's finisher.
      */
    public Function<A, R> finisher() {
      return (a) -> elmHasFailed ? defaultResult : collector.finisher().apply(a);
    }

    @Override
    /** Make sure IDENTITY_FINISH isn't present, or finisher() won't be called */
    public Set<Characteristics> characteristics() {
      Set<Characteristics> originalSet = collector.characteristics();
      if (! originalSet.contains(Characteristics.IDENTITY_FINISH)) return originalSet;
      else {
        HashSet<Characteristics> set = new HashSet<>(collector.characteristics()); //Make new set so we can modify it.
        set.remove(Characteristics.IDENTITY_FINISH); //Make sure finisher() is called
        return Collections.unmodifiableSet(set);
      }
    }
  }
公共类SatisfyableCollector实现收集器{
私有谓词;//用于测试每个元素的谓词
private boolean elmHasFailed;//一旦元素使谓词失败,则为True
私有收集器;//此包装的收集器
private R defaultResult;//如果元素失败,则在结尾返回的结果
的公共静态可满足收集器(收集器收集器、谓词谓词、R defaultResult){
返回新的可满足收集器(收集器、谓词、defaultResult);
}
私有可满足收集器(收集器收集器、谓词谓词、R defaultResult){
this.predicate=谓词;
this.collector=收集器;
this.defaultResult=defaultResult;
elmHasFailed=false;
}
@凌驾
公共供应商(){
返回收集器。供应商();
}
@凌驾
/**在累加新元素t之前,对照谓词检查它*/
公共双消费者累加器(){
返回(a,t)->{
if(!谓词测试(t)){
elmHasFailed=true;
}
收集器.累加器().接受(a,t);
};
}
@凌驾
公共二进制运算符组合器(){
返回收集器.combiner();
}
@凌驾
/**最后,检查是否有失败。如果是,则返回默认值。
*否则,包装收集器的修整器。
*/
公共函数完成器(){
return(a)->elmHasFailed?defaultResult:collector.finisher().apply(a);
}
@凌驾
/**确保IDENTITY\u FINISH不存在,否则不会调用finisher()*/
公共集特征(){
Set originalSet=collector.characteristics();
如果(!originalSet.contains(Characteristics.IDENTITY_FINISH))返回originalSet;
否则{
HashSet set=newhashset(collector.characteristics());//创建一个新的集合,以便我们可以修改它。
set.remove(Characteristics.IDENTITY_FINISH);//确保调用finisher()
返回集合。不可修改集合(集合);
}
}
}
可以这样使用:

  public static void main(String[] args) {
    Stream<Integer> stream = Arrays.asList(1,2,3,4,5,6,7,8).stream();

    //Create a list out of the stream *if* every element is even. Return empty otherwise 
    List<Integer> listOrEmpty = stream.collect(SatisfyableCollector.of(Collectors.toList(), (x) -> x%2 == 0, new ArrayList<>())));
    System.out.println(listOrEmpty);
  }
publicstaticvoidmain(字符串[]args){
Stream=数组.asList(1,2,3,4,5,6,7,8).Stream();
//在流*中创建一个列表,如果*每个元素都是偶数。否则返回空
List listOrEmpty=stream.collect(令人满意的collector.of(Collectors.toList(),(x)->x%2==0,new ArrayList());
System.out.println(listOrEmpty);
}

目前,这并不能很好地处理异常抛出,但这应该不太难添加。

这里最大的危险是您希望对所有元素执行操作,因为它们都匹配一个
谓词。为此,需要迭代两次。流不会对每个操作都迭代每个元素,更重要的是它们对单个元素执行尽可能多的操作,然后继续。我认为全场比赛在这里是完全可以接受的。如果表现是你最关心的,我不能说太多。但如果不是这样,我建议保持简单,分两步完成整个过程。如果你试图通过各种花哨的把戏把它强行塞进一条管道,没有人会不理解它,也许一周左右后你自己也会理解。事实上,我倾向于两步解决方案,正是因为这个原因——清晰。尽管如此,我还是想知道,出于知识的考虑,这些选项(事件稍微模糊)是什么。有时,只要有人向你展示了一些技巧,这些技巧就会变得明显和容易理解