Java - TreeSet 允许重复元素

7

我在使用TreeSet时遇到了一些问题:为什么它会接受重复元素?我以为TreeSet通过比较器检测并自动删除重复元素。请帮帮我,我对Java和StackOverflow都还很陌生。

import java.util.Comparator;
import java.util.Set;
import java.util.TreeSet;

public class SortedSongs
{   
    private Set songs;
    public SortedSongs()
    {
         Comparator<Song> comp = (Song c1, Song c2)-> c1.toString().compareTo(c2.toString());
         songs = new TreeSet<>(comp);
    }
}

编辑:以下是我实现hashCode和equals的方法:

@Override
public int hashCode()
{
    return Objects.hash(name, author);
}

@Override
public boolean equals(Object o)
{
    return o == null ? false : o.getClass() != getClass() ? false
        : o.hashCode() == hashCode();
}

编辑2:

这是更新后的Song类equals方法、toString和compareTo方法:

@Override
public boolean equals(Object o)
{
    if (this==o) return true;
    if (getClass()!=o.getClass()) return false;
    return name.equals(((Song) o).name) && author.equals(((Song) o).author);
}
@Override
public String toString() {return name + " - " + author;}

public int compareTo(Song other)
{
    if (name.equals(other.name))
        return author.equals(other.author) ? 0 : author.compareTo(other.author);
    return name.compareTo(other.name);
}

所以现在在SortedSongs中的Comparator
Comparator<Song> comp = (Song c1, Song c2)-> c1.compareTo(c2);

尽管如此,仍然不起作用,我觉得我可能漏掉了一些显而易见的东西。

编辑3: 问题已解决,实际上是我在测试类中犯了一个错误。很尴尬。抱歉,我没有想浪费你的时间,希望这对某人有所帮助。


4
问题可能有点微妙:您正在比较歌曲的字符串表示形式。您应该向我们展示toString是如何实现的,并最好还要显示哪些“Song”对象最终成为重复项。通常,比较器必须与“equals”一致,以遵守“Set”接口的契约,但即使没有适当的“equals”实现,您也不应该看到重复项。 - Marco13
3
附带说明:你现在的 equals 实现方式 不是 有效的。通常规则是:如果两个对象根据 equals 方法相等,那么它们必须具有相同的 hashCode。但是如果它们具有相同的 hashCode,则它们不一定根据 equals 方法相等。这只是一个附带说明,因为它对你正在观察的问题 应该 不相关。 - Marco13
你的 equalsComparator<Song> 还不太正确。先尝试修复它们,使它们可以逐个字段比较 Song - lexicore
1
equals方法是绝对错误的。Joshua Bloch在《Effective Java》的第3章中告诉您如何正确覆盖equals和hashCode。 - duffymo
Song的字段包括两个字符串,名称和作者。 - M. P.
显示剩余3条评论
2个回答

5

TreeSet 在Java中是用平衡二叉树(实际上是红黑树)实现的。因此,它不使用equals方法,而是使用Comparator

现在你的实现问题在于你的comparator。你的比较器基于toString方法。默认情况下,Java返回对象的类名加上其哈希码。因此,默认情况下,如果两个对象指向相同的内存引用,则toString的输出将是相同的。你需要确保你已经重写了你的类中的toString方法,因为你的比较器是基于它的。

要解决这个问题,你需要定义一个反映你程序比较逻辑的比较器。


1
问题显然与 toString 相关,但只要我们不知道 toString 是否被覆盖,或者如何被覆盖,就很难在这里给出明确的答案。根据名称(SortedSongs),我相当确定有一个 toString 实现,并且意图是按字母顺序排序歌曲 - 但直到现在这也只是一个猜测。 - Marco13
1
永远不要使用toString()。让Comparator自己完成工作,不要从toString()中获取任何信息,直接查看组成Song的任何数据--标题、作家、歌手、编曲者等等--这样无论toString()是否正确或被覆盖,都不会有影响。 - Kevin Anderson

0

TreeSet在底层使用TreeMap实现,并使用虚拟值进行委托。 因此,与使用复杂的比较器允许重复项相比,更简单且更节省内存的方法是切换到使用值来保存重复计数的TreeMap。

这里有一个例子:如何在Java中使用允许重复项的TreeSet?


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