Java 8中的Stream distinct方法无法正常工作

10
这是我正在做的事情:
List scores = Stream.concat(oldEntries.stream(), newEntries.stream())
                    .sorted()
                    .distinct()
                    .limit(maxSize)
                    .collect(Collectors.toList());

我希望得到一个去重的有序列表,但有时列表中会出现重复项。

我已经重写了hashCode和equals方法,并观察到这些方法每次都返回正确的值。有人能看出我的流代码有什么问题吗?

这是我的equals()和hashCode()方法,它们是由IDEA自动生成的:

..
private int userId;
private int levelId;
private int score;

@Override
public boolean equals(Object o) {

    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;

    Score score = (Score) o;

    if (userId != score.userId) return false;
    return levelId == score.levelId;

}

@Override
public int hashCode() {
    int result = userId;
    result = 31 * result + levelId;
    return result;
}

public int compareTo(Score other) {

    if (other == null) {
        return 1;
    } else {
        return Integer.compare(other.score, this.score);
    }
}

 ..

6
你的 equals/hashCode 可能没有正确实现。请在问题中包含它们的代码。 - Kayaman
1
userIdlevelIdint类型的吗? - ByeBye
2
compareTo 方法中的代码怎么样? - Kayaman
1
@MoienGK 这就是问题所在,你的compareTohashCode/equals不一致。 - Eugene
2
@Eugene 我不明白为什么那是个问题。CompareTo 用于排序,而 hashCode 和 equals() 用于去重。distinct 不使用 compareTo。难道它使用吗? - MoienGK
显示剩余10条评论
2个回答

10

首先根据compareTo,即使用score进行排序。

然后使用equals()来“去重”,即使用userIdlevelId。根据javadoc:

对于有序流,选择不同的元素是稳定的(对于重复的元素,保留遇到的第一个元素)。 对于无序流,不提供稳定性保证。

例如:

score 1, user 2, level 3
score 3, user 2, level 3
score 1, user 3, level 1

排序后...

score 1, user 2, level 3
score 1, user 3, level 1
score 3, user 2, level 3

Distinct方法现在不起作用,因为元素根据用户/级别而言不相等。这可能导致"重复"元素的出现,因为您正在基于一件事情进行排序,但完全由不同的事情确定相等性。


3
由于流是已排序的,因此假定相等的元素会跟在一起。在所示例子中并非如此,因为分数为3的行与它之前的行不相等,所以 distinct 操作未能移除该行。 - Kayaman
3
@ByeBye,你是什么意思?这已经描述得非常清楚了。 - Kayaman
1
@MoienGK 先使用 distinct,然后再进行排序。 - ByeBye
1
@Kayaman 对我来说,“对于重复元素,始终保留首个出现的元素。”这意味着始终保留第一个元素,而不是与其相邻元素进行相等性检查,当在无序流中时,它可能是随机的。 - ByeBye
2
优化的有效性在this answer中得到了证明,但请注意,即使没有进行这种优化,具有与equals不一致的自然顺序的问题也可能会发生,例如在使用distinct()而没有使用sort()时,因为在幕后使用的HashMap使用自然顺序来解决哈希冲突(见此处)... - Holger
显示剩余5条评论

4

这是个bug。

Stream.distinct() 的文档只是简单地说:

返回由该流的不同元素(根据 Object.equals(Object))组成的流。

对于有序流,选择不同的元素是稳定的(对于重复的元素,保留首次出现在遇到顺序中的元素)。 对于无序流,不作稳定性保证。

对于有序流,文档并没有要求相等的对象应该紧接着出现在一起(连续)。 然而,实现似乎假设它们确实如此。 文档的意思是,应该保留第一次出现的 user 2, level 3,丢弃第二次出现的。

据Java bug数据库显示,该bug存在于Java 13之前的版本,并仍未解决。

链接


2
不错的发现。这是一个进退两难的情况,因为作为实现细节,无法通过记录hashCode/equals/compareTo必须一致来真正解决它。 - Kayaman

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接