Java 8中使用流和Lambda

8
我想优化以下代码,基于以下逻辑使用现有的ArrayList创建一个新列表。 Java 8中的流/lambda是否使其成为可能?
for (ShipmentTracingDTO tracing : tracings) {
    if (tracing.getDestination()) {
        newTracings.add(tracing);
        break;
    }
    newTracings.add(tracing);
}

可能是 https://dev59.com/YmIi5IYBdhLWcg3w_QmH 的重复问题。 - Jan Gassen
1
@JanGassen 好的,我最初建议使用 takeWhile,但是当元素即使满足条件也被 add 的部分更棘手,因为 takeWhile 会忽略该特定元素。 - Naman
这看起来像是一个XY问题。想要在满足条件的元素之前创建一个不符合某个条件的所有元素列表,然后再添加后者并不是一个典型的场景。你到底是为了什么而这样做? - Klitos Kyriacou
3
当你使用 for (ShipmentTracingDTO tracing : tracings) { newTracings.add(tracing); if(tracing.getDestination()) break; } 这段代码时,你的循环会变得更简单。它的作用是遍历 tracings 集合中的每一个元素,并将其添加到 newTracings 集合中,直到遇到 getDestination() 方法返回 true,此时循环将被打破(break)。 - Holger
2
当你说优化时,是指速度还是代码冗长?流可能比循环慢。因此,如果您想要速度,通常最好使用循环。但是使用流和lambda确实看起来更酷。 - user5063151
3个回答

9
您可以使用IntStream.rangeList.subList来实现,可能如下所示:
List<ShipmentTracingDTO> newTracings = tracings.subList(0, IntStream.range(0, tracings.size())
       .filter(i -> tracings.get(i).getDestination())
       .map(a -> a + 1) // to include the index of occurrence
       .findFirst()
       .orElse(tracings.size())); // else include all until last element

注意: 这只是对tracings的一种视图,因此更改底层列表tracings也会反映在newTracings中的更改。如果您想要解耦这些,可以从解决方案创建一个new ArrayList<>()

编辑: 在Holger的评论中,您还可以使用:

List<ShipmentTracingDTO> newTracings = IntStream.rangeClosed(1, tracings.size())
        .filter(i -> tracings.get(i - 1).getDestination()) // i-1 to include this as well
        .mapToObj(i -> tracings.subList(0, i)) // Stream<List<ShipmentTracingDTO>>
        .findFirst() // Optional<List<ShipmentTracingDTO>>
        .orElse(tracings); // or else the entire list

2
或者 List<ShipmentTracingDTO> newTracings = IntStream.rangeClosed(1, tracings.size()) .filter(i -> tracings.get(i - 1).getDestination()).mapToObj(i -> tracings.subList(0,i)) .findFirst() .orElse(tracings); 如果需要一个新的列表,只需将整个表达式包装在 new ArrayList<>( … ) 中。 - Holger
1
@Holger 谢谢,i-1 部分是我在思考解决这个问题时没有想到的。也许,同样的事情提醒我先写迭代,优化到最佳状态,然后再使用流处理。无论如何,再次感谢。 - Naman
这两种解决方案都比原始的for循环更加复杂和难以阅读。流显然不是解决此问题的合适工具! - Lii
1
@Lii 嗯,我可以说这只是一种看法。因为OP使用这样的代码的用例也没有得到合理的证明。是的,如果必须这样做,非流代码看起来更好。 - Naman

4
如果我需要这样的处理,我会将其分为两个单独的流:
final int lastElemPosition = tracings.size() - 1;
final int firstElemWithGetDestinationTruePosition = IntStream.range(0, lastElemPosition)
    .filter(i -> tracings.get(i).getDestination())
    .findFirst()
    .orElse(lastElemPosition);
final List<ShipmentTracingDTO> desiredElements = IntStream.rangeClosed(0, firstElemWithGetDestinationTruePosition)
    .mapToObj(tracings::get)
    .collect(Collectors.toList());

4

首先,我要指出你想要修改的原始代码没有太大问题。仅仅因为Java有流(streams)并不意味着你必须使用它们。

要使用流(streams),我会将解决方案分为两个阶段:

OptionalInt indexOfMatchingItem = IntStream.range(0, tracings.size())
        .filter(i -> tracings.get(i).getDestination())
        .findFirst();

List<ShipmentTracingDTO> newTracings = new ArrayList<>(
        indexOfMatchingItem
                .map(i -> tracings.subList(0, i + 1))
                .orElse(tracings));

上面的代码可以用一个表达式来写,但是使用一个合适命名的中间变量可以让代码更加易懂。
我创建了一个新的ArrayList,这样它不会受到原始列表tracings的任何后续更改的影响。如果tracings是不可变的,那么可以跳过创建新ArrayList的步骤。
上面的解决方案比问题中的原始代码略微更高效,因为ArrayList构造函数预先分配了一个确切所需大小的数组,从而避免了调整大小和多个数组复制的开销。
另一种替代方案(可能更易读但性能较差)是:
List<ShipmentTracingDTO> newTracings =
        Stream.concat(
            tracings.stream().takeWhile(i -> !tracings.get(i).getDestination()),
            tracings.stream().dropWhile(i -> !tracings.get(i).getDestination()).limit(1)
        ).collect(toList());

1
或者 newTracings = new ArrayList<>(indexOfMatchingItem.isPresent()? tracings.subList(0, 1 + indexOfMatchingItem.get()): tracings)。当源是一个 ArrayList 时,性能不仅受益于预设数组大小,还会有单个 Arrays.copyOf 操作从源列表的数组中进行。 - Holger
谢谢,Holger。我已经加入了你的建议。请注意,在大多数subList实现中,list.subList(0,list.size()) == list(通过身份),因此这两种变体是等效的。isPresent调用通常被认为是“代码异味”,整个解决方案的异味似乎表明它是一个奇怪的要求 - 在原始的for循环版本中不太明显,但当您尝试将其转换为功能性代码时,它变得更加明显。 - Klitos Kyriacou
例如,对于常用的ArrayList,不满足list.subList(0,list.size()) == list。我甚至不确定合同是否允许此优化,因为随后向原始列表添加元素会使子列表无效而不是显示出来。对于那些认为isPresent是代码异味的人,new ArrayList<>(indexOfMatchingItem.map(i -> tracings.subList(0, 1 + i)).orElse(tracings))可以实现相同的功能,但会失去不创建新对象的优势。 - Holger

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接