英文:
Exception handling in java stream reduce lambda
问题
I am trying to handle exceptions inside the reduce(item, aggregator)
function for a stream in java.
这部分不需要翻译。
This is what my original code looks like:
这是我的原始代码:
List<ReportRow> totalList = newList.stream()
.collect(Collectors.groupingBy(a -> a.getEngagementCode()))
.entrySet().stream()
.map(engagement -> engagement.getValue().stream()
.reduce((item, aggregator) ->
new ReportRow(item.getEnvironment(), item.getApplicationName(), item.getEngagementCode(), item.getTotalHits() + aggregator.getTotalHits(), item.getServiceLine(), Float.toString(Float.parseFloat(item.getTotalCost().replace(",", "")) + Float.parseFloat(aggregator.getTotalCost().replace(",", ""))), item.getPrimaryOwnerEmail(), item.getDeploymentId()))
.get())
.collect(Collectors.toList());
and this is roughly what I expect it to look like (doesn't work though).
这大致是我期望它看起来像的(但不起作用)。
List<ReportRow> totalList = newList.stream()
.collect(Collectors.groupingBy(a -> a.getEngagementCode()))
.entrySet().stream()
.map(engagement -> engagement.getValue().stream()
.reduce((item, aggregator) -> {
try {
new ReportRow(item.getEnvironment(), item.getApplicationName(), item.getEngagementCode(), item.getTotalHits() + aggregator.getTotalHits(), item.getServiceLine(), Float.toString(Float.parseFloat(item.getTotalCost().replace(",", "")) + Float.parseFloat(aggregator.getTotalCost().replace(",", ""))), item.getPrimaryOwnerEmail(), item.getDeploymentId()))
} catch (Exception e) {
throw new ImproperDataException("No Deployment Id present in row: "+ item.toString());
}
}
.get())
.collect(Collectors.toList());
The exception happens when I am trying to call the ReportRow()
constructor.
异常发生在我尝试调用ReportRow()
构造函数时。
What would be the way to handle this exception while still making using of collect, stream and map as in the original method?
在仍然使用collect、stream和map的情况下,如何处理这个异常呢?
英文:
I am trying to handle exceptions inside the reduce(item,aggregator)
function for a stream in java.
This is what my original code looks like:
List<ReportRow> totalList = newList.stream()
.collect(Collectors.groupingBy(a -> a.getEngagementCode()))
.entrySet().stream()
.map(engagement -> engagement.getValue().stream()
.reduce((item, aggregator) ->
new ReportRow(item.getEnvironment(), item.getApplicationName(), item.getEngagementCode(), item.getTotalHits() + aggregator.getTotalHits(), item.getServiceLine(), Float.toString(Float.parseFloat(item.getTotalCost().replace(",", "")) + Float.parseFloat(aggregator.getTotalCost().replace(",", ""))), item.getPrimaryOwnerEmail(), item.getDeploymentId()))
.get())
.collect(Collectors.toList());
and this is roughly what I expect it to look like (doesn't work though).
List<ReportRow> totalList = newList.stream()
.collect(Collectors.groupingBy(a -> a.getEngagementCode()))
.entrySet().stream()
.map(engagement -> engagement.getValue().stream()
.reduce((item, aggregator) -> {
try {
new ReportRow(item.getEnvironment(), item.getApplicationName(), item.getEngagementCode(), item.getTotalHits() + aggregator.getTotalHits(), item.getServiceLine(), Float.toString(Float.parseFloat(item.getTotalCost().replace(",", "")) + Float.parseFloat(aggregator.getTotalCost().replace(",", ""))), item.getPrimaryOwnerEmail(), item.getDeploymentId()))
} catch (Exception e) {
throw new ImproperDataException("No Deployment Id present in row: "+ item.toString());
}
}
.get())
.collect(Collectors.toList());
The exception happens when I am trying to call the ReportRow()
constructor.
What would be the way to handle this exception while still making using of collect, stream and map as in the original method ?
答案1
得分: 1
如果发生未经检查的异常,将终止流处理。这在遇到无法恢复的意外问题时很有用。为了实现这一点,ImproperDataException 应该扩展 RuntimeException。出于可读性的原因,我会将 try-catch 和抛出逻辑移到一个方法内。
对于已检查的异常,您应该决定需要发生什么。要么继续处理(跳过该项或使用默认值),要么通过将其包装在未经检查的异常中终止。您不能从流表达式传播已检查的异常。
这是一个简化的、稍微虚构的示例。当出现 ArithmeticException 时,在报告中跳过了 costPerHit 计算。
static class Value {
int hits;
int cost;
Value(int hits, int cost) {
this.hits = hits;
this.cost = cost;
}
}
static class Report {
int values;
int hits;
int cost;
double costPerHit;
Report accumulate(Value value) {
values++;
hits += value.hits;
cost += value.cost;
updateCostPerHit();
return this;
}
Report combine(Report report) {
values += report.values;
hits += report.hits;
cost += report.cost;
updateCostPerHit();
return this;
}
private void updateCostPerHit() {
try {
costPerHit = cost / hits;
} catch (ArithmeticException e) {
e.printStackTrace();
}
}
@Override
public String toString() {
return String.format("Report{values=%d, hits=%d, cost=%d, costPerHit=%.2f}", values, hits, cost, costPerHit);
}
}
public static void main(String[] args) {
List<Value> values = List.of(
new Value(0, 3), // 导致 ArithmeticException
new Value(5, 1),
new Value(1, 2),
new Value(3, 8)
);
Report output = values.stream().reduce(new Report(), Report::accumulate, Report::combine);
System.out.println(output);
}
输出:
java.lang.ArithmeticException: / by zero
at ErrorHandling$Report.updateCostPerHit(ErrorHandling.java:42)
at ErrorHandling$Report.accumulate(ErrorHandling.java:28)
at java.base/java.util.stream.ReduceOps$1ReducingSink.accept(ReduceOps.java:80)
at java.base/java.util.AbstractList$RandomAccessSpliterator.forEachRemaining(AbstractList.java:720)
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484)
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)
at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:913)
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.base/java.util.stream.ReferencePipeline.reduce(ReferencePipeline.java:563)
at interview.streams.ErrorHandling.main(ErrorHandling.java:63)
Report{values=4, hits=9, cost=14, costPerHit=1.00}
实际上,由于我使用了单个 ReportRow 作为可变容器,可能更好使用 collect
而不是 reduce
:
Report output = values.stream().collect(Report::new, Report::accumulate, Report::combine);
我认为这也可能更适合您的实际代码。
英文:
If an unchecked exception occurs it will terminate the stream processing. This is useful in case of unexpected problems that you cannot recover from. To do this, the ImproperDataException should extend RuntimeException. For readability reasons I would move the try-catch and throw logic inside a method.
For checked exceptions you should decide what needs to happen. Either continue the process (by skipping the item or using a default value) or terminate by wrapping it in an unchecked exception. You cannot propagate checked exceptions from a stream expression.
Here is a simplified and slightly contrived example. When the ArithmeticException occurs, the costPerHit calculation is skipped in the report.
static class Value {
int hits;
int cost;
Value(int hits, int cost) {
this.hits = hits;
this.cost = cost;
}
}
static class Report {
int values;
int hits;
int cost;
double costPerHit;
Report accumulate(Value value) {
values++;
hits += value.hits;
cost += value.cost;
updateCostPerHit();
return this;
}
Report combine(Report report) {
values += report.values;
hits += report.hits;
cost += report.cost;
updateCostPerHit();
return this;
}
private void updateCostPerHit() {
try {
costPerHit = cost / hits;
} catch (ArithmeticException e) {
e.printStackTrace();
}
}
@Override
public String toString() {
return String.format("Report{values=%d, hits=%d, cost=%d, costPerHit=%.2f}", values, hits, cost, costPerHit);
}
}
public static void main(String[] args) {
List<Value> values = List.of(
new Value(0, 3), // causes ArithmeticException
new Value(5, 1),
new Value(1, 2),
new Value(3, 8)
);
Report output = values.stream().reduce(new Report(), Report::accumulate, Report::combine);
System.out.println(output);
}
Output:
java.lang.ArithmeticException: / by zero
at ErrorHandling$Report.updateCostPerHit(ErrorHandling.java:42)
at ErrorHandling$Report.accumulate(ErrorHandling.java:28)
at java.base/java.util.stream.ReduceOps$1ReducingSink.accept(ReduceOps.java:80)
at java.base/java.util.AbstractList$RandomAccessSpliterator.forEachRemaining(AbstractList.java:720)
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484)
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)
at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:913)
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.base/java.util.stream.ReferencePipeline.reduce(ReferencePipeline.java:563)
at interview.streams.ErrorHandling.main(ErrorHandling.java:63)
Report{values=4, hits=9, cost=14, costPerHit=1.00}
Actually since I am using a single ReportRow as a mutable container of sorts, it's probably better to use collect
instead of reduce
:
Report output = values.stream().collect(Report::new, Report::accumulate, Report::combine);
I think this might also be better for your actual code
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论