使用不同的标准对树集中的元素进行排序和区分

Sorting and distinguishing elements within a treeset with different criterias

极端Java 新手在这里。我正在做一些简单的练习,以便对语言的基本概念进行一些练习。

其中一个练习要求我实现一个 MusicAlbum class,其中一个实例属性是 class MusicTrack 的实例列表。

由于每个 MusicTrack 必须由其 id 唯一标识,并且考虑到指定所述列表必须是 "sorted"(尽管没有真正的指示),我选择了 TreeSet。

所以我在 MusicTrack class 中实现了 Comparable,这样 MusicAlbum 的集合将按照它包含的 MusicTrack 实例的 ID 进行排序。此外,具有相同 id 的两个 MusicTrack 实例将被视为相同的 MusicTrack 实例,然后在树集中不会有重复项。到目前为止还不错(或者至少我是这么认为的)。

当练习要求使 MusicAlbum class 可迭代 持续时间 的递减顺序(这是 MusicTrack 的另一个属性 class).

我立即想到修改 compareTo 方法,这样树集的排序将改为按持续时间组织,而覆盖 class 对象的 equals 方法仍将保证 id 的唯一性。然而,这并没有奏效,似乎 compareTo 方法的存在使得 equals 方法完全无关紧要。

所以这是我的问题:是否可以使用一个标准对树集进行排序并使用完全不同的标准在同一树集中保持唯一性?

我发现这句话可能暗示这样的事情,即使可能,仍然不推荐:

Note that the ordering maintained by a sorted map (whether or not an explicit comparator is provided) must be consistent with equals if this sorted map is to correctly implement the Map interface. (See Comparable or Comparator for a precise definition of consistent with equals.) This is so because the Map interface is defined in terms of the equals operation, but a map performs all key comparisons using its compareTo (or compare) method, so two keys that are deemed equal by this method are, from the standpoint of the sorted map, equal. The behavior of a sorted map is well-defined even if its ordering is inconsistent with equals; it just fails to obey the general contract of the Map interface.

然而,我发现的有关这方面的信息让我很困惑,所以我要求澄清。

还有什么是解决这个练习的好方法?当然,这是我到目前为止设法做到的:

MusicTrack.java

public class MusicTrack implements Comparable<MusicTrack> {

    private static int nextId = 0;

    private int id;
    private String title;
    private String author;
    private int duration;

    @SuppressWarnings("serial")
    public class NegativeDurationException extends Exception {

        public NegativeDurationException() {

            System.err.println("Duration value must be greater than 0.");
        }
    }

    public MusicTrack(String title, String author, int duration) throws NegativeDurationException {

        if(duration < 1) {

            throw new NegativeDurationException();
        }
        else {

            this.id = nextId++;
            this.title = title;
            this.author = author;
            this.duration = duration;
        }
    }

    public int getId() {

        return this.id;
    }

    public String getTitle() {

        return this.title;
    }

    public void setTitle(String title) {

        this.title = title;
    }

    public String getAuthor() {

        return this.author;
    }

    public void setAuthor(String author) {

        this.author = author;
    }

    public int getDuration() {

        return this.duration;
    }

    public void setDuration(int duration) {

        this.duration = duration;
    }

    public String toString() {

        return "Id: " + this.id  + "\nAuthor: " + this.author + "\nTitle: " + this.title + "\nDuration: " + this.duration + "\n";
    }

    @Override
    public int compareTo(MusicTrack track) {

        return this.id - track.id;
    }
}

MusicAlbum.java

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Iterator;
import java.util.Set;
import java.util.TreeSet;

public class MusicAlbum implements Iterable<MusicTrack> {

    public enum PhysicalMedia {

        VYNIL, CD, USB
    }

    private static int nextId = 0;

    private int id;
    private String title;
    private String author;
    private Date purchaseTime;
    private Set<MusicTrack> tracks;
    private PhysicalMedia physicalMedia;

    public MusicAlbum(String title, String author, String purchaseTime, PhysicalMedia physicalMedia) {

        try {

            this.purchaseTime = new SimpleDateFormat("dd/mm/yyyy").parse(purchaseTime);
        } 
        catch (ParseException e) {

            e.printStackTrace();
        }

        this.id = nextId++;
        this.title = title;
        this.author = author;
        this.physicalMedia = physicalMedia;
        this.tracks = new TreeSet<MusicTrack>();
    }

    public void addMusicTracks(MusicTrack ... tracks) {

        for(MusicTrack track: tracks) {

            this.tracks.add(track);
        }
    }

    public boolean contains(MusicTrack track) {

        return this.tracks.contains(track);
    }

    public int getTotalDuration() {

        Iterator<MusicTrack> i = this.tracks.iterator();
        int totalDuration = 0;

        while(i.hasNext()) {

            totalDuration += i.next().getDuration();
        }

        return totalDuration;
    }

    public String toString() {

        return "Id: " + this.id + "\nDate: " + this.purchaseTime.toString() + "\nTotal duration: " + this.getTotalDuration();
    }


    @Override
    public Iterator<MusicTrack> iterator() {

        return this.tracks.iterator();
    }

}
  1. 写一个持续时间比较器。
class DurationComparator implements Comparator<MusicTrack> {

    @Override
    public int compare(MusicTrack o1, MusicTrack o2) {
        int d1 = o1 == null ? 0 : o1.getDuration();
        int d2 = o2 == null ? 0 : o2.getDuration();
        return d2 - d1;
    }
}
  1. 在classMusicAlbum
  2. 中更改方法iterator()
public Iterator<MusicTrack> iterator() {
    TreeSet<MusicTrack> temp = new TreeSet<MusicTrack>(new DurationComparator());
    temp.addAll(tracks);
    return temp.iterator();
}

现在迭代器按持续时间递减的顺序列出曲目,而简单地列出曲目则按 ID 的顺序显示它们。

演示代码。
(请注意,我将方法 getTracks() 添加到 class MusicAlbum returns tracks 成员。)

public static void main(String[] args) throws NegativeDurationException {
    MusicAlbum album = new MusicAlbum("title", "author", "03/10/2003", PhysicalMedia.CD);
    MusicTrack track1 = new MusicTrack("title_1", "author_1", 30);
    MusicTrack track2 = new MusicTrack("title_2", "author_2", 40);
    MusicTrack track3 = new MusicTrack("title_3", "author_3", 10);
    MusicTrack track4 = new MusicTrack("title_4", "author_4", 20);
    album.addMusicTracks(track1, track2, track3, track4);
    Iterator<MusicTrack> iter = album.iterator();
    while (iter.hasNext()) {
        System.out.println(iter.next());
    }
    System.out.println("====================================================================");
    album.getTracks().forEach(System.out::println);
}

以上main()方法的输出:

Id: 1
Author: author_2
Title: title_2
Duration: 40

Id: 0
Author: author_1
Title: title_1
Duration: 30

Id: 3
Author: author_4
Title: title_4
Duration: 20

Id: 2
Author: author_3
Title: title_3
Duration: 10

====================================================================
Id: 0
Author: author_1
Title: title_1
Duration: 30

Id: 1
Author: author_2
Title: title_2
Duration: 40

Id: 2
Author: author_3
Title: title_3
Duration: 10

Id: 3
Author: author_4
Title: title_4
Duration: 20

编辑

由于你的评论,@Gian,我意识到你需要一个 list 迭代器而不是 set 因为可能有两个或更多 MusicTrack 具有相同的持续时间。因此 class MusicAlbum 中的方法 iterator() 变为:

public Iterator<MusicTrack> iterator() {
    List<MusicTrack> temp = new ArrayList<MusicTrack>();
    temp.addAll(tracks);
    Collections.sort(temp, new DurationComparator());
    return temp.iterator();
}

现在看看当您列出专辑的曲目时会发生什么,其中两个或多个曲目具有相同的持续时间。