ChatGPT解决这个技术问题 Extra ChatGPT

在 Java 8 中,如何使用 Stream API 通过检查每个对象的属性的独特性来过滤集合?

例如,我有一个 Person 对象列表,我想删除同名的人,

persons.stream().distinct();

将对 Person 对象使用默认相等检查,所以我需要类似的东西,

persons.stream().distinct(p -> p.getName());

不幸的是,distinct() 方法没有这样的重载。如果不修改 Person 类中的相等检查,是否可以简洁地做到这一点?


S
Stuart Marks

distinct 视为一个有状态过滤器。这是一个返回谓词的函数,该谓词维护先前看到的状态,并返回是否第一次看到给定元素:

public static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
    Set<Object> seen = ConcurrentHashMap.newKeySet();
    return t -> seen.add(keyExtractor.apply(t));
}

然后你可以写:

persons.stream().filter(distinctByKey(Person::getName))

请注意,如果流是有序的并且并行运行,这将在重复项中保留一个 任意 元素,而不是像 distinct() 那样保留第一个元素。

(这与此问题的 my answer 基本相同:Java Lambda Stream Distinct() on arbitrary key?


我想为了更好的兼容性,参数应该是 Function<? super T, ?>,而不是 Function<? super T, Object>。还应注意,对于有序并行流,此解决方案不保证将提取哪个对象(与正常 distinct() 不同)。同样对于顺序流,使用 CHM 会产生额外的开销(@nosid 解决方案中不存在)。最后,该解决方案违反了 filter 方法的约定,即谓词必须是无状态的,如 JavaDoc 中所述。尽管如此,还是投了赞成票。
@java_newbie distinctByKey 返回的 Predicate 实例不知道它是否在并行流中使用。如果它被并行使用,它会使用 CHM,尽管这会在顺序情况下增加开销,如上面 Tagir Valeev 所述。
@holandaGo 如果您保存并重用 distinctByKey 返回的 Predicate 实例,它将失败。但如果您每次都调用 distinctByKey,它就会起作用,这样它每次都会创建一个新的 Predicate 实例。
@Chinmay 不,不应该。如果您使用 .filter(distinctByKey(...))。它将执行一次方法并返回谓词。因此,如果您在流中正确使用它,基本上地图已经被重新使用。如果您将地图设为静态,则地图将为所有用途共享。因此,如果您有两个使用此 distinctByKey() 的流,则两者都将使用相同的映射,这不是您想要的。
这太聪明了,而且完全不明显。 通常这是一个有状态的 lambda,底层 CallSite 将链接到 get$Lambda 方法 - 这将始终返回 Predicate 的新实例,但这些实例将共享据我了解,mapfunction 相同。非常好!
l
luiscla27

另一种方法是使用名称作为键将人员放置在地图中:

persons.collect(Collectors.toMap(Person::getName, p -> p, (p, q) -> p)).values();

请注意,如果名称重复,则保留的 Person 将是第一个输入的。


@skiwi:您认为有没有一种方法可以实现 distinct() 而没有这种开销?任何实现如何知道它以前是否见过一个对象而不实际记住它见过的所有不同的值?所以 toMapdistinct 的开销很可能是相同的。
@Holger 我可能错了,因为我没有想到 distinct() 本身产生的开销。
显然它弄乱了列表的原始顺序
@Philipp:可以通过更改为 persons.collect(toMap(Person::getName, p -> p, (p, q) -> p, LinkedHashMap::new)).values(); 来修复
@DanielEarwicker 这个问题是关于“按属性区分”的。这将要求流按相同的属性进行排序,以便能够利用它。首先,OP 从未声明流已排序。其次,流无法检测它们是否按某个属性排序。第三,没有真正的“按属性区分”流操作来执行您的建议。第四,在实践中,只有两种方法可以得到这样的排序流。无论如何已经不同的排序源 (TreeSet) 或在还缓冲所有元素的流上的 sorted
N
Naman

您可以将人员对象包装到另一个类中,该类仅比较人员的姓名。之后,您打开包装的对象以再次获取人员流。流操作可能如下所示:

persons.stream()
    .map(Wrapper::new)
    .distinct()
    .map(Wrapper::unwrap)
    ...;

Wrapper 可能如下所示:

class Wrapper {
    private final Person person;
    public Wrapper(Person person) {
        this.person = person;
    }
    public Person unwrap() {
        return person;
    }
    public boolean equals(Object other) {
        if (other instanceof Wrapper) {
            return ((Wrapper) other).person.getName().equals(person.getName());
        } else {
            return false;
        }
    }
    public int hashCode() {
        return person.getName().hashCode();
    }
}

@StuartCaie 不是真的......没有记忆,重点不是性能,而是适应现有的API。
com.google.common.base.Equivalence.wrap(S) 和 com.google.common.base.Equivalence.Wrapper.get() 也可以提供帮助。
您可以通过密钥提取函数使包装器类通用并参数化。
为了扩展 @bjmi 的建议,下面是一个示例用法:persons.stream().map(Equivalence.equals().onResultOf(Person::getName)::wrap).distinct().map(Equivalence.Wrapper::get)....
S
Santhosh

另一种解决方案,使用 Set。可能不是理想的解决方案,但它有效

Set<String> set = new HashSet<>(persons.size());
persons.stream().filter(p -> set.add(p.getName())).collect(Collectors.toList());

或者如果您可以修改原始列表,您可以使用 removeIf 方法

persons.removeIf(p -> !set.add(p.getName()));

如果您不使用任何第三方库,这是最好的答案!
如果该集合尚未包含指定元素,则使用 Set.add 返回 true 的巧妙想法。 +1
我相信这种方法不适用于并行流处理,因为它不是线程安全的。
@LoBo 可能不会。这只是一个想法,适用于简单的情况。用户可以扩展它以实现线程安全/并行性。
有趣的方法,但看起来有点像修改外部集合(集合)同时过滤另一个集合(人员)上的流的反模式......
j
josketres

使用带有自定义比较器的 TreeSet 有一种更简单的方法。

persons.stream()
    .collect(Collectors.toCollection(
      () -> new TreeSet<Person>((p1, p2) -> p1.getName().compareTo(p2.getName())) 
));

我认为您的回答有助于排序而不是唯一性。然而,它帮助我确定了如何去做。在这里检查:stackoverflow.com/questions/1019854/…
请记住,您将为在这里对元素进行排序付出代价,我们不需要排序来查找重复项甚至删除重复项。
Comparator.comparing(Person::getName)
f
frhack

我们还可以使用 RxJava(非常强大的 reactive extension 库)

Observable.from(persons).distinct(Person::getName)

或者

Observable.from(persons).distinct(p -> p.getName())

Rx 很棒,但这是一个糟糕的答案。 Observable 是基于推送的,而 Stream 是基于拉取的。 stackoverflow.com/questions/30216979/…
该问题要求不一定使用流的 java8 解决方案。我的回答表明 java8 流 api 不如 rx api 强大
使用 reactor,它将是 Flux.fromIterable(persons).distinct(p -> p.getName())
这个问题的字面意思是“使用 Stream API”,而不是“不一定使用流”。也就是说,对于将流过滤为不同值的 XY 问题,这是一个很好的解决方案。
K
Kirby

您可以使用 groupingBy 收集器:

persons.collect(Collectors.groupingBy(p -> p.getName())).values().forEach(t -> System.out.println(t.get(0).getId()));

如果你想有另一个流,你可以使用这个:

persons.collect(Collectors.groupingBy(p -> p.getName())).values().stream().map(l -> (l.get(0)));

C
Craig P. Motlin

您可以使用 Eclipse Collections 中的 distinct(HashingStrategy) 方法。

List<Person> persons = ...;
MutableList<Person> distinct =
    ListIterate.distinct(persons, HashingStrategies.fromFunction(Person::getName));

如果您可以重构 persons 以实现一个 Eclipse Collections 接口,您可以直接调用列表中的方法。

MutableList<Person> persons = ...;
MutableList<Person> distinct =
    persons.distinct(HashingStrategies.fromFunction(Person::getName));

HashingStrategy 只是一个策略接口,可让您定义 equals 和 hashcode 的自定义实现。

public interface HashingStrategy<E>
{
    int computeHashCode(E object);
    boolean equals(E object1, E object2);
}

注意:我是 Eclipse Collections 的提交者。


在 Eclipse Collections 9.0 中添加了 distinctBy 方法,可以进一步简化此解决方案。 medium.com/@donraab/…
K
Kirby

Saeed Zarinfam 使用的类似方法,但更多的是 Java 8 风格:)

persons.collect(Collectors.groupingBy(p -> p.getName())).values().stream()
 .map(plans -> plans.stream().findFirst().get())
 .collect(toList());

我会用 flatMap(plans -> plans.stream().findFirst().stream()) 替换地图线它避免使用 get on Optional
也许这也可以: flatMap(plans -> plans.stream().limit(1))
E
Enginer

您可以使用 StreamEx 库:

StreamEx.of(persons)
        .distinct(Person::getName)
        .toList()

不幸的是,这个非常棒的 StreamEx 库的方法设计得很糟糕——它比较对象相等而不是使用相等。由于字符串实习,这可能适用于 String,但也可能不起作用。
M
Mateusz Rasiński

如果可以的话,我建议使用 Vavr。使用此库,您可以执行以下操作:

io.vavr.collection.List.ofAll(persons)
                       .distinctBy(Person::getName)
                       .toJavaSet() // or any another Java 8 Collection

以前称为“javaslang”库。
W
Wojciech Górski

扩展 Stuart Marks 的答案,这可以在没有并发映射的情况下以更短的方式完成(如果您不需要并行流):

public static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
    final Set<Object> seen = new HashSet<>();
    return t -> seen.add(keyExtractor.apply(t));
}

然后调用:

persons.stream().filter(distinctByKey(p -> p.getName());

这个没有考虑到流可能是并行的。
感谢您的评论,我已经更新了我的答案。如果您不需要并行流,则不使用并发映射会给您带来更好的性能。
如果您改为创建 Collections.synchronizedSet(new HashSet<>()),您的代码可能适用于并行集合。但它可能会比使用 ConcurrentHashMap 慢。
K
Kirby

可以使用以下方法找到不同的对象列表:

 List distinctPersons = persons.stream()
                    .collect(Collectors.collectingAndThen(
                            Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(Person:: getName))),
                            ArrayList::new));

G
Guillaume Cornet

我做了一个通用版本:

private <T, R> Collector<T, ?, Stream<T>> distinctByKey(Function<T, R> keyExtractor) {
    return Collectors.collectingAndThen(
            toMap(
                    keyExtractor,
                    t -> t,
                    (t1, t2) -> t1
            ),
            (Map<R, T> map) -> map.values().stream()
    );
}

一个例子:

Stream.of(new Person("Jean"), 
          new Person("Jean"),
          new Person("Paul")
)
    .filter(...)
    .collect(distinctByKey(Person::getName)) // return a stream of Person with 2 elements, jean and Paul
    .map(...)
    .collect(toList())

T
Tomasz Linkowski

另一个支持此功能的库是 jOOλ,它的 Seq.distinct(Function<T,U>) 方法:

Seq.seq(persons).distinct(Person::getName).toList();

Under the hood,但它实际上与 accepted answer 做同样的事情。


A
Andrew Novitskyi
Set<YourPropertyType> set = new HashSet<>();
list
        .stream()
        .filter(it -> set.add(it.getYourProperty()))
        .forEach(it -> ...);

一个好的答案有更好的解释How do I write a good answer?
对于那些可能对“...”感到困惑的人,请替换为“it.getYourProperty()”
u
uneq95

我的方法是将具有相同属性的所有对象组合在一起,然后将组缩短为 1 的大小,最后将它们收集为 List

  List<YourPersonClass> listWithDistinctPersons =   persons.stream()
            //operators to remove duplicates based on person name
            .collect(Collectors.groupingBy(p -> p.getName()))
            .values()
            .stream()
            //cut short the groups to size of 1
            .flatMap(group -> group.stream().limit(1))
            //collect distinct users as list
            .collect(Collectors.toList());

这是最好的答案。
A
Abhinav Ganguly

虽然最高支持的答案绝对是 Java 8 的最佳答案,但同时在性能方面绝对是最差的。如果您真的想要一个性能不佳的低性能应用程序,那么请继续使用它。只需“For-Each”和“Set”即可实现提取唯一一组人名的简单要求。如果列表大小超过 10,情况会变得更糟。

假设您有 20 个对象的集合,如下所示:

public static final List<SimpleEvent> testList = Arrays.asList(
            new SimpleEvent("Tom"), new SimpleEvent("Dick"),new SimpleEvent("Harry"),new SimpleEvent("Tom"),
            new SimpleEvent("Dick"),new SimpleEvent("Huckle"),new SimpleEvent("Berry"),new SimpleEvent("Tom"),
            new SimpleEvent("Dick"),new SimpleEvent("Moses"),new SimpleEvent("Chiku"),new SimpleEvent("Cherry"),
            new SimpleEvent("Roses"),new SimpleEvent("Moses"),new SimpleEvent("Chiku"),new SimpleEvent("gotya"),
            new SimpleEvent("Gotye"),new SimpleEvent("Nibble"),new SimpleEvent("Berry"),new SimpleEvent("Jibble"));

您反对 SimpleEvent 的位置如下所示:

public class SimpleEvent {

private String name;
private String type;

public SimpleEvent(String name) {
    this.name = name;
    this.type = "type_"+name;
}

public String getName() {
    return name;
}

public void setName(String name) {
    this.name = name;
}

public String getType() {
    return type;
}

public void setType(String type) {
    this.type = type;
}
}

为了测试,你有这样的 JMH 代码,(请注意,我使用了在接受的答案中提到的相同的 distinctByKey 谓词):

@Benchmark
@OutputTimeUnit(TimeUnit.SECONDS)
public void aStreamBasedUniqueSet(Blackhole blackhole) throws Exception{

    Set<String> uniqueNames = testList
            .stream()
            .filter(distinctByKey(SimpleEvent::getName))
            .map(SimpleEvent::getName)
            .collect(Collectors.toSet());
    blackhole.consume(uniqueNames);
}

@Benchmark
@OutputTimeUnit(TimeUnit.SECONDS)
public void aForEachBasedUniqueSet(Blackhole blackhole) throws Exception{
    Set<String> uniqueNames = new HashSet<>();

    for (SimpleEvent event : testList) {
        uniqueNames.add(event.getName());
    }
    blackhole.consume(uniqueNames);
}

public static void main(String[] args) throws RunnerException {
    Options opt = new OptionsBuilder()
            .include(MyBenchmark.class.getSimpleName())
            .forks(1)
            .mode(Mode.Throughput)
            .warmupBatchSize(3)
            .warmupIterations(3)
            .measurementIterations(3)
            .build();

    new Runner(opt).run();
}

然后你会得到这样的基准测试结果:

Benchmark                                  Mode  Samples        Score  Score error  Units
c.s.MyBenchmark.aForEachBasedUniqueSet    thrpt        3  2635199.952  1663320.718  ops/s
c.s.MyBenchmark.aStreamBasedUniqueSet     thrpt        3   729134.695   895825.697  ops/s

如您所见,与 Java 8 Stream 相比,一个简单的 For-Each 的吞吐量提高了 3 倍,并且错误分数更低。

吞吐量越高,性能越好


谢谢,但这个问题在 Stream API 的上下文中非常具体
是的,我同意,我已经提到过“虽然最高投票的答案绝对是 Java 8 的最佳答案”。一个问题可以用 n 种不同的方式解决,我想在这里强调的是,手头的问题可以简单地解决,而不是使用 Java 8 Streams 危险地解决,其中危险是性能下降。 :)
s
saran3h

这就像一个魅力:

按唯一键对数据进行分组以形成地图。从地图的每个值返回第一个对象(可能有多个同名的人)。

persons.stream()
    .collect(groupingBy(Person::getName))
    .values()
    .stream()
    .flatMap(values -> values.stream().limit(1))
    .collect(toList());

H
Holger

实现这一点的最简单方法是使用排序功能,因为它已经提供了一个可选的 Comparator,可以使用元素的属性来创建它。然后,您必须过滤掉重复项,这可以使用 statefull Predicate 来完成,它使用的事实是,对于已排序的流,所有相等的元素都是相邻的:

Comparator<Person> c=Comparator.comparing(Person::getName);
stream.sorted(c).filter(new Predicate<Person>() {
    Person previous;
    public boolean test(Person p) {
      if(previous!=null && c.compare(previous, p)==0)
        return false;
      previous=p;
      return true;
    }
})./* more stream operations here */;

当然,有状态的 Predicate 不是线程安全的,但是如果您需要,您可以将此逻辑移动到 Collector 中,并让流在使用 Collector 时负责线程安全。这取决于您想对您在问题中没有告诉我们的不同元素流做什么。


A
Apar

我想改进 Stuart Marks answer。如果键为空怎么办,它将通过NullPointerException。在这里,我通过将另一项检查添加为 keyExtractor.apply(t)!=null 来忽略空键。

public static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
Set<Object> seen = ConcurrentHashMap.newKeySet();
return t -> keyExtractor.apply(t)!=null && seen.add(keyExtractor.apply(t));

}


S
Sourav Sharma
Here is the example
public class PayRoll {

    private int payRollId;
    private int id;
    private String name;
    private String dept;
    private int salary;


    public PayRoll(int payRollId, int id, String name, String dept, int salary) {
        super();
        this.payRollId = payRollId;
        this.id = id;
        this.name = name;
        this.dept = dept;
        this.salary = salary;
    }
} 

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collector;
import java.util.stream.Collectors;

public class Prac {
    public static void main(String[] args) {

        int salary=70000;
        PayRoll payRoll=new PayRoll(1311, 1, "A", "HR", salary);
        PayRoll payRoll2=new PayRoll(1411, 2    , "B", "Technical", salary);
        PayRoll payRoll3=new PayRoll(1511, 1, "C", "HR", salary);
        PayRoll payRoll4=new PayRoll(1611, 1, "D", "Technical", salary);
        PayRoll payRoll5=new PayRoll(711, 3,"E", "Technical", salary);
        PayRoll payRoll6=new PayRoll(1811, 3, "F", "Technical", salary);
        List<PayRoll>list=new ArrayList<PayRoll>();
        list.add(payRoll);
        list.add(payRoll2);
        list.add(payRoll3);
        list.add(payRoll4);
        list.add(payRoll5);
        list.add(payRoll6);


        Map<Object, Optional<PayRoll>> k = list.stream().collect(Collectors.groupingBy(p->p.getId()+"|"+p.getDept(),Collectors.maxBy(Comparator.comparingInt(PayRoll::getPayRollId))));


        k.entrySet().forEach(p->
        {
            if(p.getValue().isPresent())
            {
                System.out.println(p.getValue().get());
            }
        });



    }
}

Output:

PayRoll [payRollId=1611, id=1, name=D, dept=Technical, salary=70000]
PayRoll [payRollId=1811, id=3, name=F, dept=Technical, salary=70000]
PayRoll [payRollId=1411, id=2, name=B, dept=Technical, salary=70000]
PayRoll [payRollId=1511, id=1, name=C, dept=HR, salary=70000]

R
Rafael Winterhalter

派对迟到了,但我有时会用这个单线作为等价物:

((Function<Value, Key>) Value::getKey).andThen(new HashSet<>()::add)::apply

表达式是 Predicate<Value>,但由于映射是内联的,因此它用作过滤器。这当然可读性较差,但有时避免该方法可能会有所帮助。


看起来令人印象深刻 - 但如何使用它?
R
Ravikumar

有很多方法,这个也有帮助

    List<Employee> employees = new ArrayList<>();

    employees.add(new Employee(11, "Ravi"));
    employees.add(new Employee(12, "Stalin"));
    employees.add(new Employee(23, "Anbu"));
    employees.add(new Employee(24, "Yuvaraj"));
    employees.add(new Employee(35, "Sena"));
    employees.add(new Employee(36, "Antony"));
    employees.add(new Employee(47, "Sena"));
    employees.add(new Employee(48, "Ravi"));

    List<Employee> empList = new ArrayList<>(employees.stream().collect(
                    Collectors.toMap(Employee::getName, obj -> obj,
                    (existingValue, newValue) -> existingValue))
                   .values());

    empList.forEach(System.out::println);


    //  Collectors.toMap(
    //  Employee::getName, - key (the value by which you want to eliminate duplicate)
    //  obj -> obj,  - value (entire employee object)
    //  (existingValue, newValue) -> existingValue) - to avoid illegalstateexception: duplicate key

输出 - toString() 重载

Employee{id=35, name='Sena'}
Employee{id=12, name='Stalin'}
Employee{id=11, name='Ravi'}
Employee{id=24, name='Yuvaraj'}
Employee{id=36, name='Antony'}
Employee{id=23, name='Anbu'}

喜欢你的方法,很好的解释!
G
Garrett Smith

基于@josketres 的回答,我创建了一个通用实用程序方法:

您可以通过创建 Collector 使其对 Java 8 更加友好。

public static <T> Set<T> removeDuplicates(Collection<T> input, Comparator<T> comparer) {
    return input.stream()
            .collect(toCollection(() -> new TreeSet<>(comparer)));
}


@Test
public void removeDuplicatesWithDuplicates() {
    ArrayList<C> input = new ArrayList<>();
    Collections.addAll(input, new C(7), new C(42), new C(42));
    Collection<C> result = removeDuplicates(input, (c1, c2) -> Integer.compare(c1.value, c2.value));
    assertEquals(2, result.size());
    assertTrue(result.stream().anyMatch(c -> c.value == 7));
    assertTrue(result.stream().anyMatch(c -> c.value == 42));
}

@Test
public void removeDuplicatesWithoutDuplicates() {
    ArrayList<C> input = new ArrayList<>();
    Collections.addAll(input, new C(1), new C(2), new C(3));
    Collection<C> result = removeDuplicates(input, (t1, t2) -> Integer.compare(t1.value, t2.value));
    assertEquals(3, result.size());
    assertTrue(result.stream().anyMatch(c -> c.value == 1));
    assertTrue(result.stream().anyMatch(c -> c.value == 2));
    assertTrue(result.stream().anyMatch(c -> c.value == 3));
}

private class C {
    public final int value;

    private C(int value) {
        this.value = value;
    }
}

A
Aliaksei Yatsau

也许对某人有用。我还有一点要求。拥有来自第 3 方的对象列表 A 会删除所有具有相同 A.b 字段的所有对象(列表中具有相同 A.id 的多个 A 对象)。 Tagir ValeevStream partition 回答启发我使用返回 Map<A.id, List<A>> 的自定义 Collector。简单的 flatMap 将完成剩下的工作。

 public static <T, K, K2> Collector<T, ?, Map<K, List<T>>> groupingDistinctBy(Function<T, K> keyFunction, Function<T, K2> distinctFunction) {
    return groupingBy(keyFunction, Collector.of((Supplier<Map<K2, T>>) HashMap::new,
            (map, error) -> map.putIfAbsent(distinctFunction.apply(error), error),
            (left, right) -> {
                left.putAll(right);
                return left;
            }, map -> new ArrayList<>(map.values()),
            Collector.Characteristics.UNORDERED)); }

D
Dharman

我有一种情况,我想根据 2 个键从列表中获取不同的元素。如果您想基于两个键或复合键来区分,试试这个

class Person{
    int rollno;
    String name;
}
List<Person> personList;


Function<Person, List<Object>> compositeKey = personList->
        Arrays.<Object>asList(personList.getName(), personList.getRollno());

Map<Object, List<Person>> map = personList.stream().collect(Collectors.groupingBy(compositeKey, Collectors.toList()));

List<Object> duplicateEntrys = map.entrySet().stream()`enter code here`
        .filter(settingMap ->
                settingMap.getValue().size() > 1)
        .collect(Collectors.toList());

K
Kache

处理 nullthe top answer 变体:

    public static <T, K> Predicate<T> distinctBy(final Function<? super T, K> getKey) {
        val seen = ConcurrentHashMap.<Optional<K>>newKeySet();
        return obj -> seen.add(Optional.ofNullable(getKey.apply(obj)));
    }

在我的测试中:

        assertEquals(
                asList("a", "bb"),
                Stream.of("a", "b", "bb", "aa").filter(distinctBy(String::length)).collect(toList()));

        assertEquals(
                asList(5, null, 2, 3),
                Stream.of(5, null, 2, null, 3, 3, 2).filter(distinctBy(x -> x)).collect(toList()));

        val maps = asList(
                hashMapWith(0, 2),
                hashMapWith(1, 2),
                hashMapWith(2, null),
                hashMapWith(3, 1),
                hashMapWith(4, null),
                hashMapWith(5, 2));

        assertEquals(
                asList(0, 2, 3),
                maps.stream()
                        .filter(distinctBy(m -> m.get("val")))
                        .map(m -> m.get("i"))
                        .collect(toList()));

a
aboger

就我而言,我需要控制前一个元素是什么。然后我创建了一个有状态的 Predicate 来控制前一个元素是否与当前元素不同,在这种情况下我保留它。

public List<Log> fetchLogById(Long id) {
    return this.findLogById(id).stream()
        .filter(new LogPredicate())
        .collect(Collectors.toList());
}

public class LogPredicate implements Predicate<Log> {

    private Log previous;

    public boolean test(Log atual) {
        boolean isDifferent = previouws == null || verifyIfDifferentLog(current, previous);

        if (isDifferent) {
            previous = current;
        }
        return isDifferent;
    }

    private boolean verifyIfDifferentLog(Log current, Log previous) {
        return !current.getId().equals(previous.getId());
    }

}

Е
Евгений Трахимович

我在此清单中的解决方案:

List<HolderEntry> result ....

List<HolderEntry> dto3s = new ArrayList<>(result.stream().collect(toMap(
            HolderEntry::getId,
            holder -> holder,  //or Function.identity() if you want
            (holder1, holder2) -> holder1 
    )).values());

在我的情况下,我想找到不同的值并将它们放入列表中。