1. 概述
Java 8中的一项重要新功能是Stream API。Stream允许我们方便地处理来自不同来源的元素,例如数组或集合。
此外,使用带有相应Collectors的Stream.collect()方法,我们可以将元素重新打包为不同的数据结构,如Set、Map、List等。
在本教程中,我们将探讨如何将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上获得。