我的用例是,我尝试使用
distinct
的 Stream
方法从班级 StudentCourseMapping
的对象列表中删除具有相同卷号的学生。
POJO详情如下:
public class StudentCourseMapping implements Serializable{
private String name;
private String dept;
private Integer roll;
private String course;
下面是 equals 方法:
@Override
public boolean equals(Object obj) {
StudentCourseMapping other = (StudentCourseMapping) obj;
if (roll == null) {
if (other.roll != null)
return false;
} else if (!roll.equals(other.roll))
return false;
return true;
}
以下是实现:
public class RemoveDuplicateUsingStream {
public static void main(String[] args) {
List<StudentCourseMapping> studentCourceList = JacksonJSONReaderObjectMapper.jsonReader();
studentCourceList.stream().distinct().forEach(System.out::println);
StudentCourseMapping s0 = studentCourceList.get(0);
StudentCourseMapping s1 = studentCourceList.get(1);
System.out.println(s0.equals(s1));
Set<Integer> st = new HashSet();
List<StudentCourseMapping>studentCourceList2 = studentCourceList.stream().filter(s -> st.add(s.getRoll()))
.collect(Collectors.toCollection(ArrayList::new));
System.out.println(studentCourceList2.size());
}
}
输出是
StudentCourseMapping [name=Alu, dept=Physics, roll=12, course=Quantum Theory]
StudentCourseMapping [name=Alu, dept=Physics, roll=12, course=English]
StudentCourseMapping [name=Sam, dept=Commerce, roll=16, course=English]
StudentCourseMapping [name=Sam, dept=Commerce, roll=16, course=Accounts]
StudentCourseMapping [name=Joe, dept=Arts, roll=19, course=English]
StudentCourseMapping [name=Joe, dept=Arts, roll=19, course=Hindi]
true
3
JacksonJSONReaderObjectMapper.jsonReader()
是读取以下 JSON 的自定义方法。我可以通过使用过滤器并添加到 HashSet
来实现相同的目的,但我真的想知道我的独特实现有什么问题。
{
"studentCourseMapping": [
{
"name": "Alu",
"dept": "Physics",
"roll": 12,
"course": "Quantum Theory"
},
{
"name": "Alu",
"dept": "Physics",
"roll": 12,
"course": "English"
},
{
"name": "Sam",
"dept": "Commerce",
"roll": 16,
"course": "English"
},
{
"name": "Sam",
"dept": "Commerce",
"roll": 16,
"course": "Accounts"
},
{
"name": "Joe",
"dept": "Arts",
"roll": 19,
"course": "English"
},
{
"name": "Joe",
"dept": "Arts",
"roll": 19,
"course": "Hindi"
}
]
}
当我尝试直接测试 equals 方法时,它工作正常并返回 true,因为
s0
和 s1
的滚动值为 12。
StudentCourseMapping s0 = studentCourceList.get(0);
StudentCourseMapping s1 = studentCourceList.get(1);
System.out.println(s0.equals(s1));
但是当我使用
distinct
时,所有对象都会被打印,并且在尝试在 Eclipse 中调试时,我编写的不同方法不会被调用,但 documentation 说应该调用它。我正在使用 JDK 11:
Stream<T> distinct()
。Object.equals(Object))
正如 @RealSkeptic 和 @Jesper 在评论中已经说过的那样,您必须重写
hashCode
中的 StudentCourseMapping
方法,以便正确比较流中的元素,并且仅根据您的 equals
保留不同的元素
实施。
尽管在 Stream 文档和不同文档中都没有提到这一点。我认为这被认为是隐含的,因为
equals
文档 充分涵盖了在覆盖 equals
方法时遵守通用哈希码合同的义务。
注意,每当重写 hashCode 方法时,通常都需要重写该方法,以维护 hashCode 方法的通用契约,即相等的对象必须具有相等的哈希码。
事实上,一旦
hashCode()
方法被重写,您的代码片段就会产生所需的输出。
public class StudentCourseMapping implements Serializable {
private String name;
private String dept;
private Integer roll;
private String course;
//... your class implementation ...
@Override
public boolean equals(Object obj) {
if (obj == null) return false;
if (obj == this) return true;
if (obj.getClass() != getClass()) return false;
StudentCourseMapping student = (StudentCourseMapping) obj;
return Objects.equals(roll, student.roll);
}
@Override
public int hashCode() {
return Objects.hashCode(roll);
}
@Override
public String toString() {
return String.format("[name = %s, dept = %s, roll = %d, course = %s]", name, dept, roll, course);
}
}
理论上,您现有的实现会起作用,因为不同意味着不相等的值。但是
Stream()
的 distinct()
实现利用 LinkedHashSet
(直接使用对象的 hashCode
方法)来捕获重复项。由于每个类引用都是唯一的,并且 hashCode
的默认实现基于该引用的值,因此不会捕获从 equals
角度来看的重复项。
这是一个使用默认
hashCode
的简单演示。创建了包含 10 个对象的列表。然后,该列表被重复添加到自身并进行打乱,从而生成一个包含 10,240 个对象的列表,总共有 10 个不同的 hashCode。因此,当 distinct()
方法检查这些 hashcodes
时,它将找到重复项并忽略它们。
注意: 这个类使用了一个简单且不完整的
equals
方法,对于这个受控演示来说已经足够了。
class Etest {
int v;
public Etest(int v) {
this.v = v;
}
@Override
public boolean equals(Object ob) {
return ((Etest)ob).v == this.v;
}
@Override
public String toString() {
return v+"";
}
}
List<Etest> list = new ArrayList<>(IntStream.range(1, 11)
.mapToObj(i -> new Etest(i)).toList());
for(int i = 0; i < 10; i++) {
Collections.shuffle(list);
list.addAll(list);
}
System.out.println("Total entries = " + list.size());
list.stream().distinct().forEach(e->System.out.printf("%s ", e));
打印类似的东西
Total entries = 10240
3 4 8 9 2 10 6 5 1 7