开篇案例:
使用map加reduce操作符实现,double类型自增
List<String> list=Arrays.asList(new String[]{"1.0","3","2","5.2","4"});
Double total=list.stream().map(BigDecimal::new).reduce((sum,item)->sum.add(item)).map(BigDecimal::doubleValue).orElse(0d);
System.out.println(total);
使用Stream API
Stream API提供了很多开发者可以用来从集合中查询数据的操作。Stream操作分为两类——中间操作和结束操作。
中间操作是从已有的Stream产生另一个Stream的函数,有filter
、map
、sorted
等。
结束操作是从Stream来产生一个不是Stream的结果的函数,有collect(toList())
、forEach
、count
等。
中间操作允许你来构建管道流,它们会在你调用结束操作时被执行。
在Stream的接口中,有很多像of
、generate
和iterate
一样的静态工厂方法,它们可以用来创建Stream的实例。上面展示的generate
方法以Supplier
为参数。Supplier
是一个函数式接口,用来描述一个不需要参数并返回一个值的函数。我们传递给generate
方法一个供应者,那么当调用的时候,就能产生一个唯一标识码。
Supplier<String> uuids = () -> UUID.randomUUID().toString()
如果我们运行上面的代码,那么什么都不会发生,因为Stream是懒惰的,它没有被使用之前,什么计算都不会进行。如果我们将代码更新成下面这样,我们将会看到在控制台上输出UUID。该程序将会不断运行。
public static void main(String[] args) {
Stream<String> uuidStream = Stream.generate(() -> UUID.randomUUID().toString());
uuidStream.forEach(System.out::println);
}
在Java8之前的数据处理
public class Example1_Java7 {
public static void main(String[] args) {
List<Task> tasks = getTasks();
List<Task> readingTasks = new ArrayList<>();
for (Task task : tasks) {
if (task.getType() == TaskType.READING) {
readingTasks.add(task);
}
}
Collections.sort(readingTasks, new Comparator<Task>() {
@Override
public int compare(Task t1, Task t2) {
return t1.getTitle().length() - t2.getTitle().length();
}
});
for (Task readingTask : readingTasks) {
System.out.println(readingTask.getTitle());
}
}
}
上面的代码将阅读任务按照它们标题的长度进行排序后输出。Java7的开发者成天要写这类的代码。为了写一个这样简单的程序,我们编写了15行代码。上面提到的代码的最大问题不是开发者要编写的代码的数量,而是它丢失了开发者的意图,也就是过滤阅读任务,根据标题长度排序,和转换成字符串列表。
Java8中的数据处理
如下所示,上述的代码可以通过Java8的Stream
API简化。
public class Example1_Stream {
public static void main(String[] args) {
List<Task> tasks = getTasks();
List<String> readingTasks = tasks.stream()
.filter(task -> task.getType() == TaskType.READING)
.sorted((t1, t2) -> t1.getTitle().length() - t2.getTitle().length())
.map(Task::getTitle)
.collect(Collectors.toList());
readingTasks.forEach(System.out::println);
}
}
- 上面的代码构建了一个由许多流式操作组成的管道流,下面对其一一讲解。
- stream(): 通过在一个原始的集合上调用
stream
方法来创建一个流式管道流,而tasks
就是List<Task>
类型的。 - filter(Predicate): 这个操作从流中抽取符合断言的判定条件的元素。一旦你有了一个数据流,你可以在其上不调用或者多次调用中间操作。lambda表达式
task -> task.getType() == TaskType.READING
定义了一个断言来过滤所有的阅读任务。该lambda表达式的类型为java.util.function.Predicate<Task>
。 - sorted(Comparator):这个操作返回一个根据由lambda表达式定义的比较器进行排序后的元素组成的数据流。在上面的例子中,这个比较器是
(t1, t2) -> t1.getTitle().length() - t2.getTitle().length()
。 - map(Function
reduce
- reduce 操作可以实现从Stream中生成一个值,其生成的值不是随意的,而是根据指定的计算模型。reduce方法有三个override的方法:
- Optional<T> reduce(BinaryOperator<T> accumulator);
- T reduce(T identity, BinaryOperator<T> accumulator);
- <U> U reduce(U identity,BiFunction<U, ? super T, U> accumulator,BinaryOperator<U> combiner);
接受一个函数接口BinaryOperator<T>,而这个接口又继承于BiFunction<T, T, T>.在BinaryOperator接口中,又定义了两个静态方法minBy和maxBy。这里我们先不管这两个静态方法,先了解reduce的操作。
@FunctionalInterface public interface BinaryOperator<T> extends BiFunction<T,T,T> { public static <T> BinaryOperator<T> minBy(Comparator<? super T> comparator) { Objects.requireNonNull(comparator); return (a, b) -> comparator.compare(a, b) <= 0 ? a : b; } public static <T> BinaryOperator<T> maxBy(Comparator<? super T> comparator) { Objects.requireNonNull(comparator); return (a, b) -> comparator.compare(a, b) >= 0 ? a : b; } }
- 在使用时,我们可以使用Lambada表达式来表示
BinaryOperator接口,可以看到reduce方法接受一个函数,这个函数有两个参数,第一个参数是上次函数执行的返回值(也称为中间结果),第二个参数是stream中的元素,这个函数把这两个值相加,得到的和会被赋值给下次执行这个函数的第一个参数。要注意的是:第一次执行的时候第一个参数的值是Stream的第一个元素,第二个参数是Stream的第二个元素。这个方法返回值类型是Optional,
Optional accResult = Stream.of(1, 2, 3, 4) .reduce((acc, item) -> { System.out.println("acc : " + acc); acc += item; System.out.println("item: " + item); System.out.println("acc+ : " + acc); System.out.println("--------"); return acc; }); System.out.println("accResult: " + accResult.get()); System.out.println("--------"); // 结果打印 -------- acc : 1 item: 2 acc+ : 3 -------- acc : 3 item: 3 acc+ : 6 -------- acc : 6 item: 4 acc+ : 10 -------- accResult: 10 --------
Collector实际应用
为了感受到
Collector
的威力,让我们来看一下我们要根据任务类型来对任务进行分类的例子。在Java8中,我们可以通过编写如下的代码达到将任务根据类型分组的目的。private static Map<TaskType, List<Task>> groupTasksByType(List<Task> tasks) { return tasks.stream().collect(Collectors.groupingBy(task -> task.getType())); }
上面的代码使用了定义在辅助类
Collectors
中的groupingBy
收集器。它创建了一个映射,其中TaskType
是它的键,而包含了所有拥有相同TaskType
的任务的列表是它的值。为了在Java7中达到相同的效果,你需要编写如下的代码。public static void main(String[] args) { List<Task> tasks = getTasks(); Map<TaskType, List<Task>> allTasksByType = new HashMap<>(); for (Task task : tasks) { List<Task> existingTasksByType = allTasksByType.get(task.getType()); if (existingTasksByType == null) { List<Task> tasksByType = new ArrayList<>(); tasksByType.add(task); allTasksByType.put(task.getType(), tasksByType); } else { existingTasksByType.add(task); } } for (Map.Entry<TaskType, List<Task>> entry : allTasksByType.entrySet()) { System.out.println(String.format("%s =>> %s", entry.getKey(), entry.getValue())); } }
- 收集器:常用的规约操作
Collectors
辅助类提供了大量的静态辅助方法来创建收集器为常见的使用场景服务,像将元素收集到一个集合中、分组和分割元素,或者根据不同的标准来概述元素。将数据收集进一个列表
import static java.util.stream.Collectors.toList; public class Example2_ReduceValue { public List<String> allTitles(List<Task> tasks) { return tasks.stream().map(Task::getTitle).collect(toList()); } }