Ric*_*chK 406 java collections java-8 java-stream
在Java 8中,如何Stream
通过检查每个对象的属性的清晰度来使用API 过滤集合?
例如,我有一个Person
对象列表,我想删除具有相同名称的人,
persons.stream().distinct();
Run Code Online (Sandbox Code Playgroud)
将使用Person
对象的默认相等检查,所以我需要像,
persons.stream().distinct(p -> p.getName());
Run Code Online (Sandbox Code Playgroud)
不幸的是,该distinct()
方法没有这种过载.如果不修改类中的相等性检查,Person
是否可以简洁地执行此操作?
Stu*_*rks 486
考虑distinct
成为一个有状态的过滤器.这是一个函数,它返回一个谓词,该谓词维护前面所看到的状态,并返回给定元素是否第一次被看到:
public static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
Set<Object> seen = ConcurrentHashMap.newKeySet();
return t -> seen.add(keyExtractor.apply(t));
}
Run Code Online (Sandbox Code Playgroud)
然后你可以写:
persons.stream().filter(distinctByKey(Person::getName))
Run Code Online (Sandbox Code Playgroud)
请注意,如果流是有序的并且并行运行,这将保留重复项中的任意元素,而不是第一个元素distinct()
.
(这与我对这个问题的回答基本相同:Java Lambda Stream Distinct()在任意键上?)
小智 122
另一种方法是使用名称作为关键字将人员放在地图中:
persons.collect(toMap(Person::getName, p -> p, (p, q) -> p)).values();
Run Code Online (Sandbox Code Playgroud)
请注意,如果名称重复,则保留的Person将是第一个被限制的人.
nos*_*sid 99
您可以将person对象包装到另一个类中,该类仅比较人员的名称.然后,您打开包装的对象以再次获取人流.流操作可能如下所示:
persons.stream()
.map(Wrapper::new)
.distinct()
.map(Wrapper::unwrap)
...;
Run Code Online (Sandbox Code Playgroud)
该类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();
}
}
Run Code Online (Sandbox Code Playgroud)
San*_*osh 42
使用另一个解决方案Set
.可能不是理想的解决方案,但它确实有效
Set<String> set = new HashSet<>(persons.size());
persons.stream().filter(p -> set.add(p.getName())).collect(Collectors.toList());
Run Code Online (Sandbox Code Playgroud)
或者,如果您可以修改原始列表,则可以使用removeIf方法
persons.removeIf(p -> !set.add(p.getName()));
Run Code Online (Sandbox Code Playgroud)
jos*_*res 27
使用带有自定义比较器的TreeSet有一种更简单的方法.
persons.stream()
.collect(Collectors.toCollection(
() -> new TreeSet<Person>((p1, p2) -> p1.getName().compareTo(p2.getName()))
));
Run Code Online (Sandbox Code Playgroud)
frh*_*ack 24
Observable.from(persons).distinct(Person::getName)
Run Code Online (Sandbox Code Playgroud)
要么
Observable.from(persons).distinct(p -> p.getName())
Run Code Online (Sandbox Code Playgroud)
Cra*_*lin 10
您可以distinct(HashingStrategy)
在Eclipse Collections中使用该方法.
List<Person> persons = ...;
MutableList<Person> distinct =
ListIterate.distinct(persons, HashingStrategies.fromFunction(Person::getName));
Run Code Online (Sandbox Code Playgroud)
如果您可以重构persons
实现Eclipse Collections接口,则可以直接在列表中调用该方法.
MutableList<Person> persons = ...;
MutableList<Person> distinct =
persons.distinct(HashingStrategies.fromFunction(Person::getName));
Run Code Online (Sandbox Code Playgroud)
HashingStrategy只是一个策略接口,允许您定义equals和hashcode的自定义实现.
public interface HashingStrategy<E>
{
int computeHashCode(E object);
boolean equals(E object1, E object2);
}
Run Code Online (Sandbox Code Playgroud)
注意:我是Eclipse Collections的提交者.
Sae*_*fam 10
你可以使用groupingBy
收藏家:
persons.collect(Collectors.groupingBy(p -> p.getName())).values().forEach(t -> System.out.println(t.get(0).getId()));
Run Code Online (Sandbox Code Playgroud)
如果你想要另一个流,你可以使用它:
persons.collect(Collectors.groupingBy(p -> p.getName())).values().stream().map(l -> (l.get(0)));
Run Code Online (Sandbox Code Playgroud)
如果可以,我建议使用Vavr.使用此库,您可以执行以下操作:
io.vavr.collection.List.ofAll(persons)
.distinctBy(Person::getName)
.toJavaSet() // or any another Java 8 Collection
Run Code Online (Sandbox Code Playgroud)
您可以使用StreamEx库:
StreamEx.of(persons)
.distinct(Person::getName)
.toList()
Run Code Online (Sandbox Code Playgroud)
小智 8
虽然获得最高票数的答案绝对是 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"));
Run Code Online (Sandbox Code Playgroud)
你反对的地方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;
}
}
Run Code Online (Sandbox Code Playgroud)
为了测试,您有这样的JMH代码,(请注意,我使用接受的答案中提到的相同的uniqueByKey谓词):
@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();
}
Run Code Online (Sandbox Code Playgroud)
然后你会得到如下的基准测试结果:
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
Run Code Online (Sandbox Code Playgroud)
正如您所看到的,与 Java 8 Stream 相比,简单的For-Each 的吞吐量提高了 3 倍,错误分数降低了 3 倍。
吞吐量越高,性能越好
小智 8
我想改进斯图尔特·马克斯的答案。如果 key 为 null 的话会通过NullPointerException
. 在这里,我通过添加一个检查来忽略 null 键 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));
Run Code Online (Sandbox Code Playgroud)
}
小智 7
可以使用以下方法找到不同的对象列表:
List distinctPersons = persons.stream()
.collect(Collectors.collectingAndThen(
Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(Person:: getName))),
ArrayList::new));
Run Code Online (Sandbox Code Playgroud)
这就像一个魅力:
persons.stream()
.collect(groupingBy(Person::getName))
.values()
.stream()
.flatMap(values -> values.stream().limit(1))
.collect(toList());
Run Code Online (Sandbox Code Playgroud)
扩展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));
}
Run Code Online (Sandbox Code Playgroud)
然后打电话:
persons.stream().filter(distinctByKey(p -> p.getName());
Run Code Online (Sandbox Code Playgroud)
小智 6
我做了一个通用版本:
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()
);
}
Run Code Online (Sandbox Code Playgroud)
一个例子:
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())
Run Code Online (Sandbox Code Playgroud)
另一个支持这个的库是jOO? ,及其Seq.distinct(Function<T,U>)
方法:
Seq.seq(persons).distinct(Person::getName).toList();
Run Code Online (Sandbox Code Playgroud)
小智 6
Set<YourPropertyType> set = new HashSet<>();
list
.stream()
.filter(it -> set.add(it.getYourProperty()))
.forEach(it -> ...);
Run Code Online (Sandbox Code Playgroud)
我的方法是将所有具有相同属性的对象组合在一起,然后将这些组缩短为 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());
Run Code Online (Sandbox Code Playgroud)
Saeed Zarinfam使用了类似的方法,但是使用了更多的Java 8样式:)
persons.collect(Collectors.groupingBy(p -> p.getName())).values().stream()
.map(plans -> plans.stream().findFirst().get())
.collect(toList());
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
243140 次 |
最近记录: |