即使抛出异常,如何迭代流?

时间:2015-05-22 12:58:27

标签: java java-8 java-stream

stream.map(obj -> doMap(obj)).collect(Collectors.toList());

private String doMap(Object obj) {
    if (objectIsInvalid) {
    throw new ParseException("Object could not be parsed");
    }
}

问题:如何抛出异常并使流迭代知道它不应该破坏整个迭代,而是继续使用下一个元素(并最终记录失败的对象)?

5 个答案:

答案 0 :(得分:4)

这是一个可以用来改善异常处理的奇怪技巧。

让我们说你的映射器函数是这样的:

String doMap(Object obj) {
    if (isInvalid(obj)) {
        throw new IllegalArgumentException("info about obj");
    } else {
        return obj.toString();
    }
}

如果对象有效,则返回结果,但如果对象无效则抛出异常。不幸的是,如果您将此直接粘贴到管道中,任何错误都将停止管道执行。你想要的是像"或者"可以包含值或错误指示符的类型(在Java中这将是一个例外)。

事实证明CompletableFuture可以包含值或异常。虽然它是用于异步处理的 - 这里没有发生 - 但我们只需要稍微扭曲它就可以用于我们的目的。

首先,给定要处理的stream个对象,我们将调用映射函数包含在对supplyAsync的调用中:

 CompletableFuture<String>[] cfArray = 
        stream.map(obj -> CompletableFuture.supplyAsync(() -> doMap(obj), Runnable::run))
              .toArray(n -> (CompletableFuture<String>[])new CompletableFuture<?>[n]);

(不幸的是,通用数组创建提供了一个未经检查的警告,必须将其抑制。)

奇怪的构造

 CompletableFuture.supplyAsync(supplier, Runnable::run)

以异步方式运行供应商&#34;在提供的Executor Runnable::run上,它只是在此线程中立即执行任务。换句话说,它同步运行供应商。

诀窍在于,此调用返回的CompletableFuture实例包含供应商的值,如果它正常返回,或者包含异常,如果供应商扔了一个。 (我在这里无视取消。)然后我们将CompletableFuture个实例收集到一个数组中。为什么一个阵列?它是下一部分的设置:

CompletableFuture.allOf(cfArray).join();

这通常会等待CF数组完成。由于它们已经同步运行,它们应该已经完成​​了。对于这种情况,重要的是join()如果阵列中的任何任何异常完成,则会抛出CompletionException。如果连接正常完成,我们可以简单地收集返回值。如果连接抛出异常,我们可以传播它,或者我们可以捕获它并处理存储在数组中的CF中的异常。例如,

try {
    CompletableFuture.allOf(cfArray).join();
    // no errors
    return Arrays.stream(cfArray)
                 .map(CompletableFuture::join)
                 .collect(toList());
} catch (CompletionException ce) {
    long errcount =
        Arrays.stream(cfArray)
              .filter(CompletableFuture::isCompletedExceptionally)
              .count();
    System.out.println("errcount = " + errcount);
    return Collections.emptyList();
}

如果全部成功,则返回值列表。如果有任何异常,则计算异常数并返回空列表。当然,您可以轻松地执行其他操作,例如记录异常的详细信息,过滤掉异常并返回有效值列表等。

答案 1 :(得分:2)

如果没有例外,您可以使用Optionals:

stream.map(obj -> doMap(obj))
      .filter(obj -> obj.isPresent())
      .collect(Collectors.toList());

private Optional<String> doMap(Object obj) {
   if (objectIsInvalid) {
    return Optional.empty();
   }
}

答案 2 :(得分:2)

如果你想要一种方法来静静地忽略那些在映射时抛出异常的元素,你可以定义一个帮助方法,它返回成功或不返回任何内容,作为0或1个元素的流,然后使用该方法对你的流进行flatMap方法

import static java.util.stream.Collectors.toList;

import java.util.List;
import java.util.function.Supplier;
import java.util.stream.Stream;

public class Fish {
    private int weight;
    public Fish(int weight) { this.weight = weight; }
    public String size() {
        if (weight < 10) {
            return "small";
        } else if (weight < 50) {
            return "medium";
        } else if (weight < 100) {
            return "large";
        } else {
            throw new RuntimeException("I don't believe you");
        }   
    }

    public static <T> Stream<T> successOrNothing(Supplier<T> supplier) {
      try {
        return Stream.of(supplier.get());
      } catch (Exception e) {
        return Stream.empty();
      }
    }

    public static void main(String[] args) {
        Stream<Fish> fishes = Stream.of(new Fish(1000), new Fish(2));
        List<String> sizes = fishes
          .flatMap(fish -> successOrNothing(fish::size))
          .collect(toList());
        System.out.println(sizes);
    }
}

流中的每个元素都映射到1个元素(如果调用成功返回)或0(如果没有)。这可能更简单或更简单,然后更改方法返回null或Optional.empty()而不是抛出&#34;模式,特别是在处理已经定义合同并且您不想改变的方法时。

警告:这个解决方案仍然默默地忽略异常;这在许多情况下是不合适的。

答案 3 :(得分:0)

我会跟着......

stream.map(doMapLenient())
      .filter(Objects::nonNull)
      .collect(Collectors.toList());

private String doMap(Object obj) {
    if (objectIsInvalid(obj)) {
    throw new ParseException("Object could not be parsed");
    }
    return "Streams are cool";
}

private Function<Object, String> doMapLenient() {
  return obj -> {
     try {
       return doMap(obj);   
   } catch (ParseExcepion ex) {
       return null; 
   }
}

在这里你可以很好地添加一些日志记录在catch部分

答案 4 :(得分:0)

您可以使用迭代器显式迭代流元素:

Stream<Object> stream = ... // throws unchecked exceptions;
Iterator<Object> iterator = stream.iterator();
while (iterator.hasNext()) {
    try {
        Object obj = it.next();
        // process
    } catch (ParseException ex) {
        // handle
    }
}