使用流收集到TreeSet

2023/06/07

1. 概述

Java 8中的一项重要新功能是Stream API。Stream允许我们方便地处理来自不同来源的元素,例如数组或集合。

此外,使用带有相应CollectorsStream.collect()方法,我们可以将元素重新打包为不同的数据结构,如SetMapList等。

在本教程中,我们将探讨如何将Stream中的元素收集到TreeSet中。

2. 以自然顺序收集成TreeSet

简单地说,TreeSet是一个有序的Set。TreeSet中的元素使用它们的自然顺序或提供的Comparator进行排序。

我们将首先了解如何使用自然顺序收集Stream元素。然后,让我们专注于使用自定义Comparator收集元素。

为简单起见,我们将使用单元测试断言来验证我们是否获得了预期的TreeSet结果。

2.1 将字符串收集到TreeSet中

由于String实现了Comparable接口,让我们首先以String为例,看看如何将它们收集到一个TreeSet中:

String kotlin = "Kotlin";
String java = "Java";
String python = "Python";
String ruby = "Ruby";
TreeSet<String> myTreeSet = Stream.of(ruby, java, kotlin, python).collect(Collectors.toCollection(TreeSet::new));
assertThat(myTreeSet).containsExactly(java, kotlin, python, ruby);

如上面的测试所示,要将Stream元素收集到TreeSet中,我们只需将TreeSet的默认构造函数作为方法引用Lambda表达式传递给Collectors.toCollection()方法

如果我们执行这个测试,它就会通过。

接下来,让我们看一个使用自定义类的类似示例。

2.2 按自然顺序收集Player

首先,让我们看一下我们的Player类:

public class Player implements Comparable<Player> {
    private String name;
    private int age;
    private int numberOfPlayed;
    private int numberOfWins;

    public Player(String name, int age, int numberOfPlayed, int numberOfWins) {
        this.name = name;
        this.age = age;
        this.numberOfPlayed = numberOfPlayed;
        this.numberOfWins = numberOfWins;
    }

    @Override
    public int compareTo(Player o) {
        return Integer.compare(age, o.age);
    }

    // getters are omitted
}

如上面的类所示,我们的Player类实现了Comparable接口。此外,我们在compareTo()方法中定义了它的自然顺序:玩家的年龄

那么接下来,让我们创建一些Player实例:

/*                          name  |  age  | num of played | num of wins
                           --------------------------------------------- */
Player kai = new Player(   "Kai",     26,       28,            7);
Player eric = new Player(  "Eric",    28,       30,           11);
Player saajan = new Player("Saajan",  30,      100,           66);
Player kevin = new Player( "Kevin",   24,       50,           49);

由于我们稍后会用到这四个Player对象进行其他演示,因此我们将代码放在一个类似表格的格式中,以便于查看每个Player的属性值。

现在,让我们按照它们的自然顺序将它们收集到一个TreeSet中,并验证我们是否得到了预期的结果:

TreeSet<Player> myTreeSet = Stream.of(saajan, eric, kai, kevin).collect(Collectors.toCollection(TreeSet::new));
assertThat(myTreeSet).containsExactly(kevin, kai, eric, saajan);

如我们所见,代码与将字符串收集到TreeSet中非常相似。由于Player的compareTo()方法已将“age”属性指定为其自然顺序,因此我们使用按年龄升序排序的Player来验证结果(myTreeSet)。

值得一提的是,我们使用了AssertJ的containsExactly()方法来验证TreeSet是否按顺序精确地包含给定的元素

接下来,我们将了解如何使用自定义的Comparator将这些Player收集到TreeSet中。

3. 使用自定义比较器收集到TreeSet

我们已经看到Collectors.toCollection(TreeSet::new)允许我们按照自然顺序将Stream中的元素收集到TreeSet中。TreeSet提供了另一个接收Comparator对象作为参数的构造函数:

public TreeSet(Comparator<? super E> comparator) { ... }

因此,如果我们希望TreeSet对元素应用不同的排序,我们可以创建一个Comparator对象并将其传递给上面提到的构造函数

接下来,让我们根据获胜次数而不是年龄将这些Player收集到TreeSet中:

TreeSet<Player> myTreeSet = Stream.of(saajan, eric, kai, kevin)
    .collect(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparingInt(Player::getNumberOfWins))
));
assertThat(myTreeSet).containsExactly(kai, eric, kevin, saajan);

这一次,我们使用了Lambda表达式来创建TreeSet实例。此外,我们还使用Comparator.comparingInt()将我们自己的Comparator传递给了TreeSet的构造函数。

Player::getNumberOfWins引用我们需要比较Player的属性值。

当我们运行它时,测试就会通过。

但是,所需的比较逻辑有时并不像示例所示中只是比较属性的值那样简单。例如,我们可能需要比较一些额外计算的结果。

所以最后,让我们再次将这些Player收集到一个TreeSet中。但是这一次,我们希望他们按胜率(胜利次数/游戏次数)排序:

TreeSet<Player> myTreeSet = Stream.of(saajan, eric, kai, kevin)
    .collect(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(player -> BigDecimal.valueOf(player.getNumberOfWins())
        .divide(BigDecimal.valueOf(player.getNumberOfPlayed()), 2, RoundingMode.HALF_UP)))));
assertThat(myTreeSet).containsExactly(kai, eric, saajan, kevin);

如上面的测试所示,我们使用了Comparator.comparing(Function keyExtractor)方法来指定可比较的排序键。在此示例中,keyExtractor函数是一个Lambda表达式,用于计算玩家的获胜率。

此外,如果我们运行测试,它也会通过,因此我们得到了预期的TreeSet。

4. 总结

在本文中,我们通过示例讨论了如何通过自然顺序和自定义比较器将Stream中的元素收集到TreeSet中。

与往常一样,本教程的完整源代码可在GitHub上获得。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章