如何在搜索重复项时删除流中的第一个外观?

时间:2017-07-04 17:56:50

标签: java java-8 java-stream

我有一个对象流,它根据它们拥有的id定义equals和hashcode。所以我知道使用stream.distinct()我会得到一个没有任何重复的流,但它会保留第一个外观,而不是最后一个。

这在文档中明确说明:

  

对于有序流,不同元素的选择是稳定的        *(对于重复元素,元素在遇到时首先出现        *保留订单。)

那么我怎么能有一个没有重复的流,而是保留最后的外观呢?

例如,如果我有这个项目列表1,2,3,1,4,5

如果我应用distinct(),我会得到1,2,3,4,5,我期待:2,3,1,4,5。我怎样才能做到这一点?

3 个答案:

答案 0 :(得分:8)

具有访问顺序toMap的自定义LinkedHashMap收集器是一个良好的开端,例如:

Stream.of( 1, 2, 3, 1, 4, 5 )
        .collect( Collectors.toMap(
                key -> key,
                value -> value,
                ( oldValue, newValue ) -> newValue,
                () -> new LinkedHashMap<>( 16, 0.75f, true )
        ) )
        .values()
        .stream()
        .forEach( System.out::println );

保留项目的最后一个外观的任务意味着在生成新流的第一个元素之前,您必须完全使用原始流,因此收集和创建新流并不是那么糟糕。如评论中所建议的那样,反转,应用不同和反转将完全消耗原始和“不同”流,因此使用专门的收集器应该更好。

答案 1 :(得分:3)

Oleg's answer绝对正确,在这里,我根据他使用 access-ordered LinkedHashMap的想法,发布了其他做同样事情的方法。

没有溪流:

Map<Integer, Integer> map = new LinkedHashMap<>(16, 0.75f, true); // access order
Arrays.asList(1, 2, 3, 1, 4, 5)
    .forEach(e -> map.put(e, e)); // just put
List<Integer> list1 = new ArrayList<>(map.values()); // discard the map

System.out.println(list1); // [2, 3, 1, 4, 5]

使用流,使用自定义收集器:

List<Integer> list2 = Stream.of(1, 2, 3, 1, 4, 5)
    .collect(Collector.of(
        () -> new LinkedHashMap<Integer, Integer>(16, 0.75f, true),
        (m, e) -> m.put(e, e),
        (m1, m2) -> { m1.putAll(m2); return m1; },
        m -> new ArrayList<>(m.values())));

System.out.println(list2); // [2, 3, 1, 4, 5]

注意:正如Holger在评论中指出的那样,使用Map.values()代替Map.keySet()来创建结果列表非常重要。这是因为当使用m.put(e, e)时,如果地图中已存在具有相同键的条目,则旧值将替换为新值,但键保持不变,即旧键。

答案 2 :(得分:1)

我通过首先反转列表,删除重复项,然后再次反转以获得原始列表来解决它。

public void removeDuplicates(){ 
     Collections.reverse(duplicatesList);
     duplicatesList = duplicatesList.stream().distinct().collect(Collectors.toList());
     Collections.reverse(duplicatesList);
     }