函数式编程
函数是面向过程的程序设计的基本单元,而函数式编程(function Programming)其思想更接近数学计算。
而数学上的计算越抽象离计算机硬件越远,执行效率就低。
函数式编程就是一种抽象程度很高的编程范式,纯粹的函数式编程语言编写的函数没有变量。由于函数内部变量状态不确定,输入输出不确定,这种称为有副作用的。
函数式编程最早是数学家阿隆佐·邱奇研究的一套函数变换逻辑,又称为Lambda Calculus。
Lambda表达式
函数式编程(Functional Programming)是把函数作为基本运算单元,函数可以作为变量,可以接收函数,还可以返回函数。
以Comparator
为例,想要调用Arrays.sort()
时,传入一个Comparator
实例,以匿名类方式编写如下:
1 | String[] array = ... |
改用Lamabda表达式
1 | String[] array = new String[] {"apple", "ipad", "mbp"}; |
- Lamabda 表达式,只需要写出方法定义
(s1, s2)->{return s1.compare(s2);}
- 参数类型可以省略,编译器会自动推断出String
->{...}
表示方法体,如果只有一行return xxx
,可进一步简化为Arrays.sort(array, (s1, s2)-> s1.compareTo(s2));
- 返回值类型也由编译器自动推断
FunctionalInterface
把只定义单方法的接口称为FuncationalInterface
,用注解@FunctionalInterface
标记。例如Callable
接口:
1 |
|
方法引用
除了Lambda表达式,还可以传入方法引用。如
1 | public class Main{ |
所谓方法引用就是,如果某个方法签名和接口恰好一直,就可以传入方法引用。
因为Comparator<String>
接口定义方法是int compare(string, string)
除了和静态方法cmp方法名不同其他都一直,即签名一致。
构造方法引用
1 | public class Main { |
后面我们会讲到Stream
的map()
方法。现在我们看到,这里的map()
需要传入的FunctionalInterface的定义是:
1 |
|
把泛型对应上就是方法签名Person apply(String)
,即传入参数String
,返回类型Person
。而Person
类的构造方法恰好满足这个条件,因为构造方法的参数是String
,而构造方法虽然没有return
语句,但它会隐式地返回this
实例,类型就是Person
,因此,此处可以引用构造方法。构造方法的引用写法是类名::new
,因此,此处传入Person::new
。
使用Stream
Java 8开始不但引入Lambda表达式,还引入了全新的流式API,Stream API
,位于java.util.stream
包中。不同于InputStream
,它代表的是任意Java对象序列。
java.util.stream
- 存储,顺序输出的任意Java对象实例
- 用途,内存计算/业务逻辑
- 元素,可能未分配、实时计算
- 用途:惰性计算
举个例子:
比如要表示一个全体自然数的集合,因为自然数无限无法用List表示出来,但是Stream
可以做到
1 | Stream<BigInteger> naturals = createNaturalStream(); |
暂时不考虑createNaturalStream()
是如何实现的,看看如何使用。
先将这个Steam中每个自然数做个平方,得到另一个Steam。
1 | Stream<BigInteger> streaNxN = naturals.map(n->n.multiply(n)); //平方 |
因为这个有无穷的元素,要打印它需要变成有限个元素,可以用limit()
方法截取,然后循环打印。
1 | naturals.map(n -> n.multiply(n)) // 不计算 |
惰性计算的特点是:一个Stream
转换为另一个Stream
时,实际上只存储了转换规则,并没有任何计算发生。
因此Stream API
的基本用法是:创建一个Stream
,然后做若干次转换,最后调用一个求值方法获取真正计算的结果。
创建Stream
- 使用
Stream.of()
静态方法,传入可变参数创建一个能输出确定元素的Stream。
1 | Stream<String> stream = Stream.of("A", "B", "C"); |
虽然这种方式基本上没啥实质性用途,但测试的时候很方便。
- 基于数组或Collection
1 | Stream<String> stream1 = Arrays.stream(new Stringp[]{"A", "B", "C"}); |
- 基于Supplier
1 | Stream<Integer> natual = Stream.generate(new NatualSupplier()); |
基于
Supplier
创建的Stream
会不断调用Supplier.get()
方法来不断产生下一个元素,这种Stream
保存的不是元素,而是算法,它可以用来表示无限序列。
- 其他方法
因为Java的规范不支持基本类型,所以无法用Stream<int>
, 为了保存int
, 只能用String<Integer>
但会产生频繁的装箱和拆箱。为了提高效率,Java标准库提供了IntStream
,LongStream
和DoubleStream
这三种使用基本类型的Stream
,使用方法和泛型Stream
没有打的区别。
1 | IntStream is = Arrays.Stream(new int[] {1,2,3}); |
使用map
Strean.map()
是Stream
常见的一个转换方法,它把一个Stream转化为另一个Stream。
所谓map就是把一种操作运算,映射到一个序列的每一个元素上。
1 | Stream<Integer> s = Stream.of(1,2,3,4,5); |
查看Stream的源码,会发现map()
方法接受的对象是Function
接口对象,它定义了一个apply()
方法, 负责把一个T
类型转换为R
类型:
1 | <R> Stream<R> map(Function<? super T, ? extends R> mapper); |
1 |
|
通过若干步map
转换,可以写出逻辑简单、清晰的代码。
使用filter
Stream.filter()
是Stream
的另一个常用转换方法。
所谓filter()
操作,就是对一个Stream
的所有元素一一进行测试,不满足条件的就被”过滤”掉,身下的满足条件的元素就构成了一个新的Stream
。
比如:
1 | IntStream.of(1, 2, 3, 4, 5, 6, 7, 8, 9) |
filter()
方法接收的对象是Predicate
接口对象,它定义了一个test()
方法,负责判断元素是否符合条件:
1 |
|
使用reduce
map()和filter()都是Stream的转换方法,而Stream.reduce()
则是Stream
的一个聚合方法,它可以把一个Stream
的所有元素按照聚合函数聚合成一个结果。
1 | int sum = Stream.of(1,2,3,4,5,6,7,8,9).reduce(0, (acc, n)-> acc + n); |
reduce()
方法传入的对象是BinaryOperator
接口,它定义了一个apply()
方法。上面例子中负责把上次累加的结果和本次的元素相加,返回累加的结果。
1 |
|
reduce()
是聚合方法,聚合方法会立刻对Stream
进行计算。
输出
Stream
可以输出为集合。
输出为List
因为List的元素是确定的Java对象,因此,把Stream
变为List
不是一个转化操作,而是一个聚合操作,它会强制Stream
输出每个元素。
调用collect(Collectors.toList())
把Stream
的每个元素收集到List
1 | Stream<String> stream = Stream.of("apple", "", null, "pear"); |
输出为数组
把Stream的元素输出为数组和输出为List类似,只需要调用toArray()
方法,并传入数组的构造方法
:
1 | List<String> list = List.of("apple", "ipad", "iphone"); |
传入的构造方法String[]::new
,签名实际上是IntFunction<String[]>
定义的String[] apply(int)
, 即传入int参数,获得String[]
数组的返回值。
输出为Map
添加到Map时需要key和value,因此需要指定两个映射函数,分别把元素映射为key和value:
1 | Stream<String> stream = Stream.of("A:Apple", "MS:Microsoft"); |
分组输出
分组输出使用Collectors.groupingBy()
1 | List<String> list = List.of("Apple", "Banana", "Blackberry", "Coconut", "Avocado", "Cherry", "Apricots"); |
其他操作
- 排序
sorted()
- 要求
Stream
的每个元素必须实现Comparable
接口 - 自定义排序,需指定Comparator,如
sorted(String::compareToIgnoreCase)
- sorted()是转换操作,返回新的Stream
- 要求
- 去重
distinct()
- 截取 用于将无限转化为有限的Stream
skip()
跳过当前Stream
的前N个元素limit()
用于截取当前Stream
最多前N个元素
- 合并
concat()
flatMap()
把Stream的每个元素映射为Stream
,然后合并成一个新的Stream
1 | Stream<List<Integer>> s = Stream.of( |
- 并行处理
parallel()
1 | Stream<String> s = ... |
其他聚合方法
count() 返回元素个数
,max(Compareator<? super T> cp)找出最大
- 针对
IntStream
、LongStream
和DoubleStream
聚合方法- sum() 求和
- average() 求平均数
- 用来测试元素是否满足条件
boolean allmatch(Predicate<? super T>)
所有元素是否满足boolean anyMatch(Predicate<? super T>)
是否至少有一个满足
- forEach(),循环打印