我对 Java 8 流有一个简单的需求。
我目前正在通过对列表进行排序,然后按索引设置排名属性来解决此问题。 有没有更好的方法通过使用 Java 8 流来解决这个问题?
public class Student {
private String name;
private int score;
private int rank;
public Student(String name, int score) {
this.name = name;
this.score = score;
}
}
public class Main {
public static void main(String[] args) {
Student s1 = new Student("jack", 98);
Student s2 = new Student("jason", 86);
Student s3 = new Student("john", 80);
List<Student> studentList = Arrays.asList(s1, s2, s3);
// how to set rank property by score in each student with stream?
}
}
流元素索引不可用,这是有道理的,因为流是为了在没有其他元素上下文的情况下处理元素而构建的。
但是,你可以做到:
List<Student> studentList = Arrays.asList(s1, s2, s3);
AtomicInteger index = new AtomicInteger();
studentList.stream()
.sorted(Comparator.comparing(s -> -s.score)) // sort highest first
.forEachOrdered(s -> s.rank = index.getAndIncrement());
这个排名从零到n-1。要从 1 到 n 排名,请将
getAndIncrement
更改为 incrementAndGet
。
流是对列表进行排序的合理(尽管不是唯一)选择。 然而,基于索引的变异操作(例如根据索引为每个元素设置属性)目前并不是流可以轻松且惯用地处理的。
因此,我建议不要使用流来解决该部分问题,而应使用现有的非流方法。
ListIterator
设置每个学生的排名(假设 Student
中字段的标准 getter 和 setter):
List<Student> sorted = studentList.stream()
.sorted(Comparator.comparing(Student::getScore).reversed())
.toList();
for (ListIterator<Student> i = sorted.listIterator(); i.hasNext(); ) {
Student next = i.next();
next.setRank(i.previousIndex());
}
基于索引的变异操作(例如根据索引为每个元素设置属性)目前不是 Java 可以使用当前永久(非预览)Stream API 轻松且惯用地处理的操作。
也就是说,JEP 461:流收集器 Java 22 预览语言功能可用于将元素流转换为包含这些元素及其索引的流。 然后可以对其进行迭代以设置每个项目的排名:
record RankedStudent(int rank, Student student) {
}
class CurrentIndex {
int index = 0;
}
Gatherer<Student, CurrentIndex, RankedStudent> gatherer = Gatherer.ofSequential(
CurrentIndex::new,
Gatherer.Integrator.ofGreedy((state, element, downstream) ->
downstream.push(new RankedStudent(state.index++, element)))
);
studentList.stream()
.sorted(Comparator.comparing(Student::getScore).reversed())
.gather(gatherer)
.forEachOrdered(s -> s.student().setRank(s.rank()));
自定义收集器跟踪当前索引,并将每个元素映射到包含元素和索引的自定义
RankedStudent
类型。
这首先按分数(降序)对流进行排序,使用自定义收集器将每个元素映射到包含该元素及其索引的
IndexedElement
,然后使用 forEachOrdered
对每个项目进行操作。
Gatherer
:
将输入元素流转换为输出元素流的中间操作,可以选择在到达上游末尾时应用最终操作。 […]
[…]
聚集操作的例子有很多,包括但不限于:将元素分组(窗口函数);对连续相似的元素进行去重;增量累加功能(前缀扫描);增量重新排序功能等。类
提供了常见收集操作的实现。Gatherers
API说明:
A
由四个函数指定,这些函数协同工作来处理输入元素,可选择使用中间状态,并可选择在输入结束时执行最终操作。他们是:Gatherer
- 创建一个新的、可能可变的状态 (
)initializer()
- 集成新的输入元素 (
)integrator()
- 将两种状态合并为一种 (
)combiner()
- 执行可选的最终操作 (
)finisher()
Stream.gather(Gatherer<? super T,?,R> gatherer)
:
返回一个流,其中包含将给定收集器应用于该流的元素的结果。
Gatherer.ofSequential(initializer, integrator)
返回由给定
和Gatherer
描述的新的连续initializer
。integrator