假设有这样的不可变类:
public class Foo {
private final Long id;
private final String name;
private final LocalDate date;
public Foo(Long id, String name, LocalDate date) {
this.id = id;
this.name = name;
this.date = date;
}
public Long getId() {
return id;
}
public String getName() {
return name;
}
public LocalDate getDate() {
return date;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Foo foo = (Foo) o;
return Objects.equals(getId(), foo.getId()) &&
Objects.equals(getName(), foo.getName()) &&
Objects.equals(getDate(), foo.getDate());
}
@Override
public int hashCode() {
return Objects.hash(getId(), getName(), getDate());
}
}
这个类有一组对象。在某些情况下,需要仅通过名称和某些情况下的名称和日期进行区分。
因此,将集合传递给java.util.Set<Foo>
或创建java Stream<Foo>
调用.distinct()
方法对于这种情况不起作用。
我知道可以使用TreeSet
和Comparator
进行区分。它看起来像这样:
private Set<Foo> distinct(List<Foo> foos, Comparator<Foo> comparator) {
TreeSet<Foo> treeSet = new TreeSet<>(comparator);
treeSet.addAll(foos);
return treeSet;
}
用法:
distinct(foos, Comparator.comparing(Foo::getName)); // distinct by name
distinct(foos, Comparator.comparing(Foo::getName).thenComparing(Foo::getDate)); // distinct by name and date
但我认为这不是一个好方法。 什么是解决这个问题最优雅的方法?
答案 0 :(得分:3)
首先,让我们考虑一下您当前的方法,然后我会展示一个更好的选择。
您当前的方法很简洁,但只需TreeSet
即可使用TreeMap
。如果您对O(nlogn)
的红/黑树结构强加的TreeMap
复杂性感到满意,我只会将您当前的代码更改为:
public static <T> Set<T> distinct(
Collection<? extends T> list,
Comparator<? super T> comparator) {
Set<T> set = new TreeSet<>(comparator);
set.addAll(list);
return set;
}
请注意,我已经使您的方法具有通用性和静态性,因此无论其元素的类型如何,它都可以以通用方式用于任何集合。我还将第一个参数更改为Collection
,以便它可以与更多数据结构一起使用。
此外,TreeSet
仍有O(nlogn)
时间复杂度,因为它使用TreeMap
作为其支持结构。
TreeSet
的使用有三个缺点:首先,它根据传递的Comparator
对你的元素进行排序(也许你不需要这个);第二,时间复杂度为O(nlogn)
(如果您需要的是具有不同的元素,则可能太多了);第三,它返回Set
(可能不是调用者需要的集合类型)。
所以,这是另一种返回Stream
的方法,然后您可以将其收集到所需的数据结构中:
public static <T> Stream<T> distinctBy(
Collection<? extends T> list,
Function<? super T, ?>... extractors) {
Map<List<Object>, T> map = new LinkedHashMap<>(); // preserves insertion order
list.forEach(e -> {
List<Object> key = new ArrayList<>();
Arrays.asList(extractors)
.forEach(f -> key.add(f.apply(e))); // builds key
map.merge(key, e, (oldVal, newVal) -> oldVal); // keeps old value
});
return map.values().stream();
}
根据作为varargs参数传递的提取器函数,将传递的集合的每个元素转换为对象列表。
然后,使用此键将每个元素放入LinkedHashMap
并通过保留最初放置的值进行合并(根据您的需要进行更改)。
最后,从地图的值返回一个流,以便调用者可以用它做任何事情。
注意:这种方法要求提取器函数返回的所有对象一致地实现equals
和hashCode
方法,以便由它们形成的列表可以安全地用作地图的关键。
用法:
List<Foo> result1 = distinctBy(foos, Foo::getName)
.collect(Collectors.toList());
Set<Foo> result2 = distinctBy(foos, Foo::getName, Foo::getDate)
.collect(Collectors.toSet());