Java-Stream distinct 去重
程序员文章站
2022-05-01 19:30:31
写在前面distinct()返回由该流的不同元素组成的流。distinct()是Stream接口的方法。distinct()使用hashCode() 和equals()方法来获取不同的元素。因此,我们的类必须实现hashCode()和equals()方法。对象去重代码实例 未重写hashCode()&equals()public class Student { private String name; private int age; private int type...
写在前面
distinct()
返回由该流的不同元素组成的流。distinct()
是Stream接口的方法。distinct()
使用hashCode()
和equals()
方法来获取不同的元素。因此,我们的类必须实现hashCode()
和equals()
方法。
对象去重
代码实例 未重写hashCode()
&equals()
public class Student {
private String name;
private int age;
private int type;
public Student() {
}
public Student(String name, int age, int type) {
this.name = name;
this.age = age;
this.type = type;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", type=" + type +
'}';
}
public static void main(String[] args) {
List<Student> students = Arrays.asList(
new Student("张三", 18, 1),
new Student("李四", 20, 1),
new Student("小明", 22, 3),
new Student("Jack", 22, 3),
new Student("Jack", 22, 3)
);
// 未重写 hashCode() & equals()
students.stream().distinct().forEach(System.out::println);
/** 输出 两个Jack一模一样
* Student{name='张三', age=18, type=1}
* Student{name='李四', age=20, type=1}
* Student{name='小明', age=22, type=3}
* Student{name='Jack', age=22, type=3}
* Student{name='Jack', age=22, type=3}
*/
}
}
重写hashCode()
&equals()
public class Student {
private String name;
private int age;
private int type;
public Student() {
}
public Student(String name, int age, int type) {
this.name = name;
this.age = age;
this.type = type;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", type=" + type +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Student)) return false;
Student student = (Student) o;
return age == student.age &&
type == student.type &&
Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age, type);
}
public static void main(String[] args) {
List<Student> students = Arrays.asList(
new Student("张三", 18, 1),
new Student("李四", 20, 1),
new Student("小明", 22, 3),
new Student("Jack", 22, 3),
new Student("Jack", 22, 3)
);
// 重写 hashCode() & equals()
students.stream().distinct().forEach(System.out::println);
/** 输出 只有一个Jack
* Student{name='张三', age=18, type=1}
* Student{name='李四', age=20, type=1}
* Student{name='小明', age=22, type=3}
* Student{name='Jack', age=22, type=3}
*/
}
}
结论:distinct()
要想进行对象去重,必须实现对象的hashCode()
&equals()
方法
对象属性去重
1.利用filter
筛选原理,不需要重写hashCode()
&equals()
方法
distinct()
不提供按照属性对对象列表进行去重的直接实现。它是基于hashCode()
和equals()
工作的。如果我们想要按照对象的属性,对对象列表进行去重,我们可以通过其它方法来实现。如下代码段所示:
public static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor){
Map<Object,Boolean> seen = new ConcurrentHashMap<>();
return t -> seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
}
代码实例
public class Student {
private String name;
private int age;
private int type;
public Student() {
}
public Student(String name, int age, int type) {
this.name = name;
this.age = age;
this.type = type;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", type=" + type +
'}';
}
// putIfAbsent() key存在时不添加到map并返回value,不存在时则添加返回null
public static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor){
Map<Object,Boolean> seen = new ConcurrentHashMap<>();
return t -> seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
}
public static void main(String[] args) {
List<Student> students = Arrays.asList(
new Student("张三", 18, 1),
new Student("李四", 20, 1),
new Student("小明", 22, 3),
new Student("Jack", 22, 3),
new Student("Jack", 22, 3)
);
// 单属性去重 按照age去重
students.stream().filter(distinctByKey(Student::getAge)).forEach(System.out::println);
/** 输出 age不同,有三个
* Student{name='张三', age=18, type=1}
* Student{name='李四', age=20, type=1}
* Student{name='小明', age=22, type=3}
*/
System.out.println("---------cut line------------");
// 多属性去重 按照name+age+type去重
students.stream().filter(distinctByKey(Student::getName)).filter(distinctByKey(Student::getAge))
.filter(distinctByKey(Student::getType)).forEach(System.out::println);
/** 输出 name+age+type不同,有2个
* Student{name='张三', age=18, type=1}
* Student{name='小明', age=22, type=3}
*/
}
}
2.利用TreeSet
特性
代码实例
利用TreeSet
不可重复的特性,不需要重写hashCode()
&equals()
方法
public class Student {
private String name;
private int age;
private int type;
public Student() {
}
public Student(String name, int age, int type) {
this.name = name;
this.age = age;
this.type = type;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", type=" + type +
'}';
}
//基于 TreeSet 和 collectingAndThen(先获取数据,然后再对数据进行操作),不需要重写 hashcode() 和 equals()
// 注意:打破了原来的添加顺序
public static void main(String[] args) {
List<Student> students = Arrays.asList(
new Student("张三", 18, 1),
new Student("李四", 20, 1),
new Student("小明", 22, 3),
new Student("Jack", 22, 3),
new Student("Jack", 22, 3)
);
// --------单属性去重 按照age去重---------
TreeSet<Student> treeSet = students.stream().collect(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(Student::getAge))));
treeSet.forEach(System.out::println);
/** 输出 age不同的,有三个
* Student{name='张三', age=18, type=1}
* Student{name='李四', age=20, type=1}
* Student{name='小明', age=22, type=3}
*/
System.out.println("-----------cut line--------");
// 想把结果再转化成List,可以使用 collectingAndThen()方法 该方法先获取数据,然后再对数据进行操作
List<Student> unique = students.stream().collect(
Collectors.collectingAndThen(
Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(Student::getAge))), ArrayList::new)
);
unique.forEach(System.out::println);
/** 输出 age不同的,有三个
* Student{name='张三', age=18, type=1}
* Student{name='李四', age=20, type=1}
* Student{name='小明', age=22, type=3}
*/
System.out.println("-----------cut line--------");
// --------多属性去重 按照name+age+type去重--------
List<Student> unique1 = students.stream().collect(
Collectors.collectingAndThen(
Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(o -> o.getName() + "-" + o.getAge() + "-" + o.getType()))), ArrayList::new)
);
unique1.forEach(System.out::println);
/** 输出 name+age+type都不同的,有四个
* Student{name='Jack', age=22, type=3}
* Student{name='小明', age=22, type=3}
* Student{name='张三', age=18, type=1}
* Student{name='李四', age=20, type=1}
*/
}
}
补充知识 Comparator
比较器
TreeSet
有一个构造方法参数是 new TreeSet(Comparator,<? super E> comparator)
,Comparator
接口中有一个静态方法comparing()
,返回值类型就是Comparator
,所以在Stream流中使用这个方法创建比较器,可以达到去重目的。
@Test
public void test(){
// 第一种
Comparator<Book> comparator = (o1, o2) -> o1.getPrice() - o2.getPrice();
int compare = comparator.compare(new Book("Java", 20.0), new Book("C++", 20.0));
// 第二种
Comparator<Book> comparing = Comparator.comparing(book -> book.getPrice());
int compare2 = comparing.compare(new Book("Java", 20.0), new Book("C++", 20.0));
// 输出是结果一样的
System.out.println("compare = " + compare);
System.out.println("compara2 = " + compare2);
}
为了在Stream中书写简单方便,故使用第二种写法
@Test
public void test(){
List<Book> books = Arrays.asList(
new Book("Java", 20.0),
new Book("Java", 30.0),
new Book("C++", 20.0)
);
Comparator<Book> comparing = Comparator.comparing(book -> book.getPrice());
TreeSet<Book> treeSet = books.stream().collect(
Collectors.toCollection(
() -> new TreeSet<>(comparing)
));
// 合并两行
TreeSet<Book> treeSet = books.stream().collect(
Collectors.toCollection(
() -> new TreeSet<>(Comparator.comparing(book -> book.getPrice()))
));
treeSet.forEach(System.out::println);
//输出 (根据price去重)
//Book{name='Java', price=20.0}
//Book{name='C++', price=20.0}
}
本文地址:https://blog.csdn.net/qiuwen_521/article/details/109629204