在Java 8中,如何通过检查每个对象的属性的清晰度来使用Stream
API过滤集合?
例如,我有一个Person
对象列表,我想删除同名的人,
persons.stream().distinct();
将使用Person
对象的默认等式检查,因此我需要类似
persons.stream().distinct(p -> p.getName());
不幸的是distinct()
方法没有这样的重载。如果不修改Person
类中的等式检查,是否可以简洁地执行此操作?
答案 0 :(得分:414)
将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?)
答案 1 :(得分:106)
另一种方法是使用名称作为关键字将人员放在地图中:
persons.collect(toMap(Person::getName, p -> p, (p, q) -> p)).values();
请注意,如果名称重复,保留的人将是第一个被控制的人。
答案 2 :(得分:80)
您可以将person对象包装到另一个类中,该类仅比较人员的名称。然后,您打开包装的对象以再次获取人流。流操作可能如下所示:
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();
}
}
答案 3 :(得分:24)
使用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()));
答案 4 :(得分:24)
使用带有自定义比较器的TreeSet有一种更简单的方法。
persons.stream()
.collect(Collectors.toCollection(
() -> new TreeSet<Person>((p1, p2) -> p1.getName().compareTo(p2.getName()))
));
答案 5 :(得分:23)
我们还可以使用RxJava(非常强大的reactive extension库)
PasswordField.Text
或
Observable.from(persons).distinct(Person::getName)
答案 6 :(得分:10)
您可以使用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的提交者。
答案 7 :(得分:9)
您可以使用StreamEx库:
StreamEx.of(persons)
.distinct(Person::getName)
.toList()
答案 8 :(得分:8)
如果可以的话,我建议使用Vavr。使用此库,您可以执行以下操作:
io.vavr.collection.List.ofAll(persons)
.distinctBy(Person::getName)
.toJavaSet() // or any another Java 8 Collection
答案 9 :(得分:6)
扩展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());
答案 10 :(得分:6)
您可以使用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)));
答案 11 :(得分: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()
);
}
一个例子:
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())
答案 12 :(得分:5)
Saeed Zarinfam使用的类似方法,但更多Java 8风格:)
persons.collect(Collectors.groupingBy(p -> p.getName())).values().stream()
.map(plans -> plans.stream().findFirst().get())
.collect(toList());
答案 13 :(得分:4)
另一个支持此功能的库是jOOλ及其Seq.distinct(Function<T,U>)
方法:
Seq.seq(persons).distinct(Person::getName).toList();
Under the hood,实际上它的作用与accepted answer相同。
答案 14 :(得分:4)
Set<YourPropertyType> set = new HashSet<>();
list
.stream()
.filter(it -> set.add(it.getYourProperty()))
.forEach(it -> ...);
答案 15 :(得分:2)
实现这一点的最简单方法是跳转排序功能,因为它已经提供了一个可选的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
。这取决于你想要对你在问题中没有告诉我们的不同元素流做什么。
答案 16 :(得分:2)
我的处理方法是将所有具有相同属性的对象组合在一起,然后将组切成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());
答案 17 :(得分:1)
我想改进 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));
}
答案 18 :(得分:1)
这个解决方案是什么。
只有当您的密钥实现 Equal 时它才会起作用,大多数基本类型都这样做,但它更简单一些。
person.stream().map(person -> p.getName()).distinct()
答案 19 :(得分:1)
晚了聚会,但有时我会用这种单线的方式:
((Function<Value, Key>) Value::getKey).andThen(new HashSet<>()::add)::apply
表达式是Predicate<Value>
,但是由于映射是内联的,因此它可以用作过滤器。当然,这种方法的可读性较差,但有时可以避免这种方法。
答案 20 :(得分:1)
处理null
的{{3}}的变体形式:
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()));
答案 21 :(得分:1)
虽然最高的答案是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倍,错误得分也降低了。
更高 吞吐量,更好性能
答案 22 :(得分:1)
我遇到了一种情况,当时我想根据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());
答案 23 :(得分:1)
不同的对象列表可以使用:
List distinctPersons = persons.stream()
.collect(Collectors.collectingAndThen(
Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(Person:: getName))),
ArrayList::new));
答案 24 :(得分:1)
在@ 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;
}
}
答案 25 :(得分:0)
我在此清单中的解决方案:
#include <iostream>
#define BOOST_DATE_TIME_NO_LIB
#include <boost/interprocess/managed_mapped_file.hpp>
#include <boost/interprocess/file_mapping.hpp>
#include <boost/interprocess/containers/vector.hpp>
#include <boost/interprocess/allocators/allocator.hpp>
#include <string>
namespace IP = boost::interprocess;
template <typename T> using MyVectorT = IP::vector < T, IP::allocator<T, IP::managed_mapped_file::segment_manager>>;
int main(int )
{
const char *FileName = "file.bin";
const std::size_t FileSize = 1000;
using MyVector = MyVectorT<size_t>;
try
{
IP::remove_file_on_destroy tmp{ FileName };
IP::managed_mapped_file segment(IP::create_only
, FileName //Mapped file name
, FileSize); //Mapped file initial size
MyVector *myvector = segment.construct<MyVector>("MyVector")(segment.get_segment_manager());
for (size_t i = 0; i < 100000; ++i) //Insert data in the vector
{
bool push_failure = true;
do
{
try
{
myvector->push_back(i);
push_failure = false; //success of push_back
}
catch (const IP::bad_alloc &) //memory mapped file is too small for vector
{
const size_t grow_size = std::max<size_t>(FileSize, 2 * (myvector->size() + 1) * sizeof(MyVector::value_type)); //estimate memory for new vector capacity
std::cout << "segment size = " << segment.get_size() << " Vector capacity = " << myvector->capacity() << " grow_size = " << grow_size;
//free memory mapped file
segment.flush();
segment.~basic_managed_mapped_file();
IP::managed_mapped_file::grow(FileName, grow_size);
new (&segment) IP::managed_mapped_file(IP::open_only, FileName);
std::cout << " -> new segment size = " << segment.get_size() << std::endl;
myvector = segment.find<MyVector>("MyVector").first;
push_failure = true; //try push_back again!!!
}
} while (push_failure);
}
std::cout << "Vector size =" << myvector->size() << "\n";
for (size_t i = 0; i < 100000; ++i)
{
if ((*myvector)[i] != i)
{
std::cout << "vector error!!! i = " << i << " vector[i] = " << (*myvector)[i] << std::endl;
}
}
}
catch (const std::exception &e)
{
std::cout << "Error " << e.what() << std::endl;
}
catch (...)
{
std::cout << "Error";
}
return 0;
}
在我的情况下,我想找到不同的值并将其放在列表中。
答案 26 :(得分:0)
就我而言,我需要控制上一个元素是什么。然后,我创建了一个有状态谓词,在该谓词中,我可以控制前一个元素是否不同于当前元素。
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());
}
}
答案 27 :(得分:0)
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]
答案 28 :(得分:0)
也许对某人有用。我还有一点要求。拥有来自第三方的对象列表A
将删除所有具有相同A.b
字段的A.id
字段(列表中具有相同A
的多个A.id
对象)。 Stream partition Tagir Valeev的回答激发了我使用返回Collector
的自定义Map<A.id, List<A>>
。简单的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)); }
答案 29 :(得分:0)
由于每个人都在分享他们自己的想法和实施,我也有一个,它不是一个有效的方法,但它是有效的:
Set<String> personNameList = personList.stream().
map(tempPerson->tempPerson.getName()).collect(Collectors.toSet());
personList.stream().
collect(()->new ArrayList<Person>(),
(l1,p)->{
if(!personNameList.contains(p.getName())) {
l1.add(p);
}
}, ArrayList::addAll);
答案 30 :(得分:-1)
也可以使用以下两种方法找到不同或唯一的列表。
方法1:使用distinct
yourObjectName.stream().map(x->x.yourObjectProperty).distinct.collect(Collectors.toList());
方法2:使用HashSet
Set<E> set = new HashSet<>();
set.addAll(yourObjectName.stream().map(x->x.yourObjectProperty).collect(Collectors.toList()));
答案 31 :(得分:-2)
您可以编写最简单的代码:
persons.stream().map(x-> x.getName()).distinct().collect(Collectors.toList());