Java 1.8 函数式编程详解
文章目录
- Java 1.8 函数式编程详解
- 一. 概述
- 1.1 java 8 新特性:
- 二. 函数式接口
- 2.1 函数式接口概述
- 2.2 Lambda表达式概述
- 2.3 java.util.function介绍
- 2.4 实战演示函数式编程
- 2.5 函数式接口之 Consumer
- 2.6 函数式接口之 Function
- 2.7 Function包结构介绍
- 三. Stream流处理
- 3.1 概述
- 3.2 Stream流对象的创建
- 3.3 Stream流对象的使用
- 3.4 对象流的使用--详解
- 3.5 补充
一. 概述
1.1 java 8 新特性:
- 概述:
- Java 8 正式版是一个有重大改变的版本,该版本对 Java 做了重大改进。本文章主要讲述java 1.8 函数式编程,主要涉及:函数式接口、Lambda 表达式、集合的流式操作;
- 新特性如下:
- 函数式接口
- Lambda
- 表达式
- 集合的流式操作
- 注解
- 安全性
- IO/NIO
- 全球化功能
二. 函数式接口
2.1 函数式接口概述
- java 1.8 引入的一个核心概念是函数式接口(Functional Interfaces)。通过在接口里面添加一个抽象方法,这些方法可以直接从接口中运行。如果一个接口定义个唯一一个抽象方法,那么这个接口就成为函数式接口。同时,引入了一个新的注解:@FunctionalInterface。可以把他它放在一个接口前,表示这个接口是一个函数式接口。这个注解是非必须的,只要接口只包含一个方法的接口,虚拟机会自动判断,不过最好在接口上使用注解 @FunctionalInterface 进行声明。在接口中添加了 @FunctionalInterface 的接口,只允许有一个抽象方法,否则编译器也会报错。
- java.lang.Runnable就是一个函数式接口
- 代码如下:
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
- 函数式接口,总的来说它是指有且只有一个未实现的方法的接口,一般通过Functionallterface这个注解来表明某个接口是一个函数式接口。函数式接口是java支持函数式编程的基础;
函数式编程语法,能够精简代码;
2.2 Lambda表达式概述
- 函数式接口的重要属性是:我们能够使用 Lambda 实例化它们,Lambda 表达式让你能够将函数作为方法参数,或者将代码作为数据对待。Lambda 表达式的引入给开发者带来了不少优点:在 Java 8 之前,匿名内部类,监听器和事件处理器的使用都显得很冗长,代码可读性很差,Lambda 表达式的应用则使代码变得更加紧凑,可读性增强;Lambda 表达式使并行操作大集合变得很方便,可以充分发挥多核 CPU 的优势,更易于为多核处理器编写代码;
- Lambda 表达式由三个部分组成:第一部分为一个括号内用逗号分隔的形式参数,参数是函数式接口里面方法的参数;第二部分为一个箭头符号:->;第三部分为方法体,可以是表达式和代码块。语法如下:
- 方法体为表达式,该表达式的值作为返回值返回
(parameters)-> expression
方法体为代码块,必须用 {} 来包裹起来,且需要一个 return 返回值,但若函数式接口里面方法返回值是 void,则无需返回值。
(parameters) -> {statements;}
例如,下面是使用匿名内部类和Lambda表达式的代码比较。
- 下面是使用匿名内部类的代码:
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.print("Helllo Lambda in actionPerformed");
}
});
下面是使用 Lambda 表达式后:
button.addActionListener(
\\actionPerformed 有一个参数 e 传入,所以用 (ActionEvent e)
(ActionEvent e)-> System.out.print("Helllo Lambda in actionPerformed")
);
上面是方法体包含了参数传入 (ActionEvent e),如果没有参数则只需 ( ),例如 Thread 中的 run 方法就没有参数传入,当它使用 Lambda 表达式后:
Thread t = new Thread(() -> { System.out.println("Hello from a thread in run");}); \\run 没有参数传入,所以用 (), 后面用 {} 包起方法体
通过上面两个代码的比较可以发现使用 Lambda 表达式可以简化代码,并提高代码的可读性。
为了进一步简化 Lambda 表达式,可以使用方法引用。例如,下面三种分别是使用内部类,使用 Lambda 表示式和使用方法引用方式的比较:
//1. 使用内部类
Function<Integer, String> f = new Function<Integer,String>(){
@Override
public String apply(Integer t) {
return null;
}
};
//2. 使用 Lambda 表达式
Function<Integer, String> f2 = (t)->String.valueOf(t);
//3. 使用方法引用的方式
Function<Integer, String> f1 = String::valueOf;
- 要使用 Lambda 表达式,需要定义一个函数式接口,这样往往会让程序充斥着过量的仅为 Lambda 表达式服务的函数式接口。为了减少这样过量的函数式接口,Java 8 在 java.util.function 中增加了不少新的函数式通用接口。例如:
- Function<T, R>:将 T 作为输入,返回 R 作为输出,他还包含了和其他函数组合的默认方法。
- Predicate :将 T 作为输入,返回一个布尔值作为输出,该接口包含多种默认方法来将 Predicate 组合成其他复杂的逻辑(与、或、非)。
- Consumer :将 T 作为输入,不返回任何内容,表示在单个参数上的操作.
例如,People 类中有一个方法 getMaleList 需要获取男性的列表,这里需要定义一个函数式接口 PersonInterface:
interface PersonInterface {
public boolean test(Person person);
}
public class People {
private List<Person> persons= new ArrayList<Person>();
public List<Person> getMaleList(PersonInterface filter) {
List<Person> res = new ArrayList<Person>();
persons.forEach(
(Person person) -> {
if (filter.test(person)) {//调用 PersonInterface 的方法
res.add(person);}}
);
return res;
}
}
// 为了去除 PersonInterface 这个函数式接口,可以用通用函数式接口 Predicate 替代如下:
class People{
private List<Person> persons= new ArrayList<Person>();
public List<Person> getMaleList(Predicate<Person> predicate) {
List<Person> res = new ArrayList<Person>();
persons.forEach(
person -> {
if (predicate.test(person)) {//调用 Predicate 的抽象方法 test
res.add(person);
}});
return res;
}
}
2.3 java.util.function介绍
- 概述: java.util.function它包含了很多类,用来支持java的函数式编程,该包中的函数式接口有:
序号 | 接口 | 描述 |
1 | BiConsumer<T,U> | 代表了一个接受两个参数的操作,并且不返回任何结果 |
2 | BiFunction<T,U,R> | 代表了一个接受两个输入参数的方法,并且返回一个结果 |
3 | BinaryOperator | 代表了一个作用于于两个同类型操作符的操作,并且返回了操作符同类型的结果 |
4 | BiPredicate<T,U> | 代表了一个两个参数的boolean值方法 |
5 | BooleanSupplier | 代表了boolean值结果的提供方 |
6 | Consumer | 代表了接受一个输入参数并且无返回的操作 |
7 | DoubleBinaryOperator | 代表了作用于两个double值操作符的操作,并且返回了一个double值的结果。 |
8 | DoubleConsumer | 代表一个接受double值参数的操作,并且不返回结果 |
9 | DoubleFunction | 代表接受一个double值参数的方法,并且返回结果 |
10 | DoublePredicate | 代表一个拥有double值参数的boolean值方法 |
11 | DoubleSupplier | 代表一个double值结构的提供方 |
12 | DoubleToIntFunction | 接受一个double类型输入,返回一个int类型结果 |
13 | DoubleToLongFunction | 接受一个double类型输入,返回一个long类型结果 |
14 | DoubleUnaryOperator | 接受一个参数同为类型double,返回值类型也为double 。 |
15 | Function<T,R> | 接受一个输入参数,返回一个结果。 |
16 | IntBinaryOperator | 接受两个参数同为类型int,返回值类型也为int |
17 | IntConsumer | 接受一个int类型的输入参数,无返回值 。 |
18 | IntFunction | 接受一个int类型输入参数,返回一个结果 。 |
19 | IntPredicate | 接受一个int输入参数,返回一个布尔值的结果。 |
20 | IntSupplier | 无参数,返回一个int类型结果。 |
21 | IntToDoubleFunction | 接受一个int类型输入,返回一个double类型结果 。 |
22 | IntToLongFunction | 接受一个int类型输入,返回一个long类型结果。 |
23 | IntUnaryOperator | 接受一个参数同为类型int,返回值类型也为int 。 |
24 | LongBinaryOperator | 接受两个参数同为类型long,返回值类型也为long。 |
25 | LongConsumer | 接受一个long类型的输入参数,无返回值。 |
26 | LongFunction | 接受一个long类型输入参数,返回一个结果。 |
27 | LongPredicate | R接受一个long输入参数,返回一个布尔值类型结果。 |
28 | LongSupplier | 无参数,返回一个结果long类型的值 |
29 | LongToDoubleFunction | 接受一个long类型输入,返回一个double类型结果 |
30 | LongToIntFunction | 接受一个long类型输入,返回一个int类型结果。 |
31 | LongUnaryOperator | 接受一个参数同为类型long,返回值类型也为long。 |
32 | ObjDoubleConsumer | 接受一个object类型和一个double类型的输入参数,无返回值。 |
33 | ObjIntConsumer | 接受一个object类型和一个int类型的输入参数,无返回值。 |
34 | ObjLongConsumer | 接受一个object类型和一个long类型的输入参数,无返回值。 |
35 | Predicate | 接受一个输入参数,返回一个布尔值结果。 |
36 | Supplier | 无参数,返回一个结果。 |
37 | ToDoubleBiFunction<T,U> | 接受两个输入参数,返回一个double类型结果 |
38 | ToDoubleFunction | 接受一个输入参数,返回一个double类型结果 |
39 | ToIntBiFunction<T,U> | 接受两个输入参数,返回一个int类型结果。 |
40 | ToIntFunction | 接受一个输入参数,返回一个int类型结果。 |
41 | ToLongBiFunction<T,U> | 接受两个输入参数,返回一个long类型结果。 |
42 | ToLongFunction | 接受一个输入参数,返回一个long类型结果。 |
43 | UnaryOperator | 接受一个参数为类型T,返回值类型也为T。 |
2.4 实战演示函数式编程
- Lambda初探:
- 定义Student接口:
public interface Student {
void xuexi();
}
- 实现此接口:
public static void main(String[] args) {
Student student=new Student() {
@Override
public void xuexi() {
System.err.println("学生的每天学习!");
}
};
}
- 使用lambda表达式简略:
public static void main(String[] args) {
Student student= () -> System.err.println("学生的每天学习!");
}
> 能使用lambda表达式的条件: 1. 是一个接口 2. 只有一个抽象方法 这里因为没有参数所以括号内的内容为空;
- Lambda表达式,有参数初探:
- 定义Student接口:
public interface Student {
void xuexi(String content);
}
- 实现此接口:
public static void main(String[] args) {
Student student= new Student() {
@Override
public void xuexi(String content) {
System.err.println("学生的"+content+"每天学习!");
}
};
student.xuexi("任务是");
}
- 使用Lambda表达式优化:
public static void main(String[] args) {
Student student= content -> System.err.println("学生的"+content+"每天学习!");
student.xuexi("任务是");
}
控制台打印结果如下:
- 什么是函数式接口:一个接口只有一个抽象方法,它就是函数式接口;
- 什么是函数式编程:满足函数式接口,并且使用此接口用lambda表达式写法时,就用到了函数式编程了;
上面示例已经说明,函数式编程接口都只有一个抽象方法,因此在采用这种写法时,编译器会将这段函数编译后当做该抽象方法的实现。 如果接口与有多个抽象方法,编译器就不知道这段函数式应该实现哪个方法了。
- 语法:
- 输入: -> 前面的部分,即被()包围的部分。此处只有一个输入参数,实际上输入可以有多个的,如两个参数时的写法:(a,b);当然也可以没有输入,此时直接就可以是();
- 函数体: ->后面的部分,即被{}包围的部分;可以是一段代码。
- 输出:函数式编程可以没有返回值,也可以有返回值。如果有返回值时,需要代码段的最后一句通过return的方式返回对应的值。
- 简略语法:
- 当函数体中只有一个语句时,可以去掉{}进一步简化:
- 比如一段代码:
Consumer c = new Consumer() {
@Override
public void accept(Object o) {
System.out.println(o);
}
};
- 转为lambda后:
Consumer c = (o) -> System.out.println(o);
- 然而这还不是最简的,由于此处只是进行打印,调用了System.out中的println静态方法对输入参数直接进行打印,因此可以简化成以下写法:
Consumer c = System.out::println;
System.out 表示要调用的哪个类 :: 后面的表示调用这个类的某个方法; 我们不但可以使用println,还可以使用这个类的其他方法进行替换;
- 总结:
通过最后一段代码,我们可以简单的理解函数式编程,Consumer接口直接就可以当成一个函数了,这个函数接收一个输入参数,然后针对这个输入进行处理;当然其本质上仍旧是一个对象,但我们已经省去了诸如老方式中的对象定义过程,直接使用一段代码来给函数式接口对象赋值。
而且最为关键的是,这个函数式对象因为本质上仍旧是一个对象,因此可以做为其它方法的参数或者返回值,可以与原有的代码实现无缝集成!
2.5 函数式接口之 Consumer
- 概述: Consumer是一个函数式编程接口:顾名思义,Consumer的意思就是消费,即针对某个东西我们来使用它,因此它包含有一个有输入而无输出的accept接口方法;除accept方法,它还包含有andThen这个方法;
- 其定义如下:
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
- 可见这个方法就是指定在调用当前Consumer后是否还要调用其它的Consumer;使用示例:
public static void consumerTest() {
Consumer f = System.out::println;
Consumer f2 = n -> System.out.println(n + "-F2");
//执行完F后再执行F2的Accept方法
f.andThen(f2).accept("test");
//连续执行F的Accept方法
f.andThen(f).andThen(f).andThen(f).accept("test1");
}
- 实战实例:
- Student学生类
/**
* @program:
* @description:
* @author: anyu
* @create: 2020-03-09
*/
@Data
public class Student implements Serializable {
private String name;
private String age;
}
- 测试类:
public static void main(String[] args) {
Consumer consumer = x -> System.out.println(JSON.toJSONString(x));
Student student = new Student();
student.setAge("18");
consumer.accept(student);
}
在不需要返回参数的情况下,我们可以使用Consumer接口来实现函数式编程;控制台打印结果如图:
2.6 函数式接口之 Function
- 概述:Function也是一个函数式编程接口;它代表的含义是“函数”,而函数经常是有输入输出的,因此它含有一个apply方法,包含一个输入与一个输出;\
- 除apply方法外,它还有compose与andThen及indentity三个方法,其使用见下述示例;
/**
* Function测试
*/
public static void functionTest() {
Function<Integer, Integer> f = s -> s++;
Function<Integer, Integer> g = s -> s * 2;
/**
* 下面表示在执行F时,先执行G,并且执行F时使用G的输出当作输入。
* 相当于以下代码:
* Integer a = g.apply(1);
* System.out.println(f.apply(a));
*/
System.out.println(f.compose(g).apply(1));
/**
* 表示执行F的Apply后使用其返回的值当作输入再执行G的Apply;
* 相当于以下代码
* Integer a = f.apply(1);
* System.out.println(g.apply(a));
*/
System.out.println(f.andThen(g).apply(1));
/**
* identity方法会返回一个不进行任何处理的Function,即输出与输入值相等;
*/
System.out.println(Function.identity().apply("a"));
}
- 在使用函数式编程的时候,我们不但可以去操作对象,还可以操作数组,操作基本数据类型,操作集合等等;接下来给大家实战示例一下:
- 使用对象:
public static void main(String[] args) {
Function<Student, String> g = Student::getName;
Student student = new Student();
student.setName("张三");
String apply = g.apply(student);
System.err.println(apply);
}
g 是定义的一个方法;第二步创建了一个对象;当把这个对象作为参数调用apply方法执行时,就会获得这个对象的名称作为String返回;拿到这个String返回时并打印到控制台,如图所示:
- 使用数组:
public static void main(String[] args) {
Function<Student[], String> g = students->students[0].getName() ; // 操作数组,打印数组中的Student对象的getName值
Student student = new Student(); // 定义对象
student.setName("暗余");
Student[] arr=new Student[]{student}; // 定义数组并将此对象传入
String apply = g.apply(arr); // 执行Function方法并获取到返回值
System.err.println(apply); // 打印返回值
}
- 使用 基本数据类型:
public static void main(String[] args) {
Function<Integer,Integer> function= x-> x+1;
System.out.println(function.apply(5));
}
结果控制台打印为 : 6
- 使用 集合:
public static void main(String[] args) {
Function<List<Student>,List<String>> function= x-> {
List<String> list= Lists.newArrayList();
for (Student student : x) {
list.add(student.getAge());
}
return list;
};
Student student1=new Student();
student1.setAge("18");
Student student2 =new Student();
student2.setAge("19");
List<Student> list=Lists.newArrayList();
Collections.addAll(list, student1, student2);
List<String> apply = function.apply(list);
System.err.println(JSON.toJSONString(apply));
}
- 上面的Function方法,是将Student对象的List转为String集合,只提取每个对象中的Age内容;由于逻辑较多无法通过一行代码完成。但是我们使用Stream流,可以继续简化。如下图所示:
public static void main(String[] args) {
Function<List<Student>,List<String>> function= x->x.stream().map(y-> y.getAge()).collect(Collectors.toList());
Student student1=new Student();
student1.setAge("18");
Student student2 =new Student();
student2.setAge("19");
List<Student> list=Lists.newArrayList();
Collections.addAll(list, student1, student2);
List<String> apply = function.apply(list);
System.err.println(JSON.toJSONString(apply));
}
很酷很优雅有没有 >< 下面将会为大家介绍Stream流;
2.7 Function包结构介绍
- 概述:当我们使用Function定义函数时,不但可以使用Function,还可以使用BiFunction以及BiConsumer等接口。
- 常用函数式接口表如下:
接口 | 描述 |
Function<T,R> | 接受一个输入参数,返回一个结果 |
Supplier | 无参数,返回一个结果 |
Consumer | 接受一个输入参数,并且不返回任何结果 |
BiFunction<T,U,R> | 接受两个输入参数的方法,并且返回一个结果 |
BiConsumer<T,U> | 接受两个输入参数的操作,并且不返回任何结果 |
三. Stream流处理
3.1 概述
- 概述: 说到Stream便容易想到I/O Stream,而实际上,谁规定“流”就一定是“IO流”呢?在Java 8中,得益于Lambda所带 来的函数式编程,引入了一个全新的Stream概念,用于解决已有集合类库既有的弊端。
- 传统集合的多步遍历代码:几乎所有的集合(如 Collection 接口或 Map 接口等)都支持直接或间接的遍历操作。而当我们需要对集合中的元 素进行操作的时候,除了必需的添加、删除、获取外,典型的就是集合遍历,在遍历的过程中再对集合内的数据进行处理,代码往往会写很多。
- 笔者概述:Stream流就是将集合放入一个工厂流水线中,进行一系列的如:过滤,跳过,映射,转换等操作,最后拿到我们需要的数据,如统计长度,新集合,新map等。就是对一个集合进行一系列处理,最后直接拿到我们想要的结果,并且代码量得到了大幅度的缩减,甚至一行代码完成了平时二十行代码达不到的效果!
3.2 Stream流对象的创建
- Stream创建的方式有多种,分别如下:
- 创建空的Stream对象
Stream stream=Stream.empty();
- 通过集合类中的stream或者parallelStream方法创建
List<String> list = Arrays.asList("a", "b", "c", "d");
Stream listStream = list.stream(); //获取串行的Stream对象
Stream parallelListStream = list.parallelStream(); //获取并行的Stream对象
- 通过Stream 中的of方法(可变参数)创建
Stream s= Stream.of("test");
Stream s1 =Stream.of("a","b","c","d");
of 内可以接受多种类型,比如数组也可以接收,代码如下:
int [] a=new int[]{10,11};
Stream<int[]> a1 = Stream.of(a);
- 通过Stream 中的iterate方法创建
public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f);
public static<T> Stream<T> iterate(T seed, Predicate<? super T> hasNext, UnaryOperator<T> next)
- 通过范围创建数值流
IntStream.range(0,100); //不包含最后一个数
IntStream.rangeClosed(0,99); //包含最后一个数
3.3 Stream流对象的使用
- 概述:Stream对象提供多个非常有用的方法,这些方法可以分成两类:
通过调用中间操作的方法,我们可以将Stream中的数据进行一系列的操作,如过滤,跳过,求最大值,最小值,排序,转换等一系列的过滤或转换或聚类等;
- 其清单如下所示,方法的具体说明及示例等请见后面:
- 中间操作如表:
方法 | 说明 |
sequential | 返回一个相等的串行的Stream对象,如果原Stream对象已经是串行就可能会返回原对象 |
parallel | 返回一个相等的并行的Stream对象,如果原Stream对象已经是并行的就会返回原对象 |
unordered | 返回一个不关心顺序的Stream对象,如果原对象已经是这类型的对象就会返回原对象 |
onClose | 返回一个相等的Steam对象,同时新的Stream对象在执行Close方法时会调用传入的Runnable对象 |
close | 关闭Stream对象 |
filter | 元素过滤:对Stream对象按指定的Predicate进行过滤,返回的Stream对象中仅包含未被过滤的元素 |
map | 元素一对一转换:使用传入的Function对象对Stream中的所有元素进行处理,返回的Stream对象中的元素为原元素处理后的 |
mapToInt | 元素一对一转换:将原Stream中的使用传入的IntFunction加工后返回一个IntStream对象 |
flatMap | 元素一对多转换:对原Stream中的所有元素进行操作,每个元素会有一个或者多个结果,然后将返回的所有元素组合成一个统一的Stream并返回; |
distinct | 去重:返回一个去重后的Stream对象 |
sorted | 排序:返回排序后的Stream对象 |
peek | 使用传入的Consumer对象对所有元素进行消费后,返回一个新的包含所有原来元素的Stream对象 |
limit | 获取有限个元素组成新的Stream对象返回 |
skip | 抛弃前指定个元素后使用剩下的元素组成新的Stream返回 |
takeWhile | 如果Stream是有序的(Ordered),那么返回最长命中序列(符合传入的Predicate的最长命中序列)组成的Stream;如果是无序的,那么返回的是所有符合传入的Predicate的元素序列组成的Stream。 |
dropWhile | 与takeWhile相反,如果是有序的,返回除最长命中序列外的所有元素组成的Stream;如果是无序的,返回所有未命中的元素组成的Stream。 |
- 终端操作如表:
方法 | 说明 |
iterator | 返回Stream中所有对象的迭代器; |
spliterator | 返回对所有对象进行的spliterator对象 |
forEach | 对所有元素进行迭代处理,无返回值 |
forEachOrdered | 按Stream的Encounter所决定的序列进行迭代处理,无返回值 |
toArray | 返回所有元素的数组 |
reduce | 使用一个初始化的值,与Stream中的元素一一做传入的二合运算后返回最终的值。每与一个元素做运算后的结果,再与下一个元素做运算。它不保证会按序列执行整个过程。 |
collect | 根据传入参数做相关汇聚计算 |
min | 返回所有元素中最小值的Optional对象;如果Stream中无任何元素,那么返回的Optional对象为Empty |
max | 与Min相反 |
count | 所有元素个数 |
anyMatch | 只要其中有一个元素满足传入的Predicate时返回True,否则返回False |
allMatch | 所有元素均满足传入的Predicate时返回True,否则False |
noneMatch | 所有元素均不满足传入的Predicate时返回True,否则False |
findFirst | 返回第一个元素的Optioanl对象;如果无元素返回的是空的Optional; 如果Stream是无序的,那么任何元素都可能被返回。 |
findAny | 返回任意一个元素的Optional对象,如果无元素返回的是空的Optioanl。 |
|isParallel| 判断是否当前Stream对象是并行的|
3.4 对象流的使用–详解
- Filter:
- 概述: 用于对Stream中的元素进行过滤,返回一个过滤后的Stream;
- 方法定义:
Stream<T> filter(Predicate<? super T> predicate);
- 使用示例:
- 定义Student.java学生类:
/**
* @program:
* @description:
* @author: anyu
* @create: 2020-03-09
*/
@Data
public class Student implements Serializable {
private String name;
private String age;
}
- 定义测试类:
public static void main(String[] args) {
Student student1=new Student();
student1.setAge("18");
Student student2 =new Student();
student2.setAge("19");
List<Student> list=Lists.newArrayList();
Collections.addAll(list, student1, student2);
long count = list.stream().filter(x -> x.getAge().equals("18")).count(); // 打印出list集合内年龄等于18岁的数据总数
System.err.println(count);
}
这里定义了一个有年龄对象的集合,使用filter能够过滤出年龄等于18岁的个数。实际上不使用.count,用其他的终结语句还能转换为另一个集合,或者找出等于18岁的这个数据的具体对象;
- map:
- 概述:它能对stream流内的元素进行转换,通过映射将元素依次进行转换处理;
- 方法定义:
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
- 使用示例:
public static void main(String[] args) {
Student student1=new Student();
student1.setAge("18");
Student student2 =new Student();
student2.setAge("19");
List<Student> list=Lists.newArrayList();
Collections.addAll(list, student1, student2);
List<String> collect = list.stream().map(x -> x.getAge() + "岁").collect(Collectors.toList());
System.err.println(JSON.toJSONString(collect));
}
这里的 x-> x… 里面的x可以用其他字母代替,它只是一个替代名称,不具备实际意义,表示集合内的这一个元素;通过map集合,我们可以将每个值进行映射,带了一个岁的后缀,当使用collect终结stream时,就转换成了一个新的stream流了。代码运行后控制台打印结果如下:
- flatMap:
- 概述: 元素一对多转换:对原Stream中的所有元素使用传入的Function进行处理,每个元素经过处理后生成一个多个元素的Stream对象,然后将返回的所有Stream对象中的所有元素组合成一个统一的Stream并返回。
- 方法定义:
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
- 使用示例:
public static void main(String[] args) {
Student student1 = new Student();
student1.setAge("18");
student1.setName("张三啊");
Student student2 = new Student();
student2.setAge("19");
student2.setName("李四啊");
List<Student> list = Lists.newArrayList();
Collections.addAll(list, student1, student2);
List<String> collect = list.stream().flatMap(x->Stream.of(x.getName())).collect(Collectors.toList());
System.err.println(JSON.toJSONString(collect));
}
> 这里的flatMap可以将每个元素再Stream流化,能够进行更细粒度的操作。比起Map的一对一,它的功能更为强大,具体的区别可百度。
- takeWhile:
- 概述:taskWhile用于Stream流的过滤操作,它筛选出符合条件的数据。当Stream流为有序Stream时,当遇到不符合条件的元素时,即进行终止。当Stream流为无序时,返回符合条件的所有元素;
它与Filter有点类似,不同之处在于当Stream是有序的,它遇到不符合条件的就会停止,而Filter会继续筛选。
- 方法定义:
default Stream<T> takeWhile(Predicate<? super T> predicate)
- 代码示例:
Stream<String> s = Stream.of("test", "t1", "t2", "teeeee", "aaaa", "taaa");
//以下结果将打印: "test", "t1", "t2", "teeeee",最后的那个taaa不会进行打印
s.takeWhile(n -> n.contains("t")).forEach(System.out::println);
- dropWhile:
- 概述:与takeWhile相反,返回takeWhile得不到的数据。它返回指定范围外的数据。当Stream是有序的,它遇到不符合条件的就会停止,然后拿取第一个不符合条件的后面的全部数据;
- 方法定义:
default Stream<T> dropWhile(Predicate<? super T> predicate)
- forEach
- 概述:forEach可以说是最常见的操作了,甚至对于List等实现了Collection接口的类可以不创建Stream而直接使用forEach。简单地说,forEach就是遍历并执行某个操作。
- 代码示例:
public static void main(String[] args) {
Student student1 = new Student();
student1.setAge("18");
student1.setName("张三啊");
Student student2 = new Student();
student2.setAge("19");
student2.setName("李四啊");
Student student3=new Student();
student3.setAge("20");
student3.setName("王五啊");
List<Student> list = Lists.newArrayList();
Collections.addAll(list, student1, student2,student3);
list.forEach(x-> System.err.println(x.getAge()));
}
使用Foreach能够对集合进行遍历,并进行一系列操作。集合可以直接使用foreach;控制台打印结果如下:
- sorted和distinct:
- 概述: sorted表示排序,distinct表示去重;
- 代码示例:
Integer[] arr = new Integer[]{5, 1, 2, 1, 3, 1, 2, 4}; // 千万不要用int
Stream.of(arr).sorted().forEach(System.out::println);
Stream.of(arr).distinct().forEach(System.out::println);
Stream.of(arr).distinct().sorted().forEach(System.out::println);
- collect:
- 概述:在流操作中,我们往往需求是从一个List得到另一个List,而不是直接通过forEach来打印。那么这个时候就需要使用到collect了。
- 示例如下:
public static void main(String[] args) {
Student student1 = new Student();
student1.setAge("18");
student1.setName("张三啊");
Student student2 = new Student();
student2.setAge("19");
student2.setName("李四啊");
Student student3=new Student();
student3.setAge("20");
student3.setName("王五啊");
List<Student> list = Lists.newArrayList();
Collections.addAll(list, student1, student2,student3);
List<Student> collect = list.stream().filter(x -> x.getAge().equals("19")).collect(Collectors.toList());
System.err.println(JSON.toJSONString(collect));
}
通过过滤出年龄等于19岁的元素,然后转为新的list,打印时就可以得到新的list集合了。打印结果如图所示:
3.5 补充
- 并行流:除了普通的stream之外还有parallelStream,区别比较直观,就是stream是单线程执行,parallelStream为多线程执行。parallelStream的创建及使用基本与Stream类似。