java.util.stream.Stream.distinct()方法如何工作?我可以重写对象流的 equals() 方法吗?

问题描述 投票:0回答:2

我的用例是,我尝试使用

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))

java java-stream distinct-values
2个回答
3
投票

正如 @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);
    }
}

1
投票

理论上,您现有的实现会起作用,因为不同意味着不相等的值。但是

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 
© www.soinside.com 2019 - 2024. All rights reserved.