java-lambda函数式编程

java-lambda函数式编程

Posted by John Doe on 2023-05-13
Words 4.3k and Reading Time 20 Minutes
Viewed Times

简介

java函数式编程可以简单概括:

基本函数 + lambda表达式 + 方法引用 + stream API = java函数式编程

本文介绍java.util.function包下常用的函数式接口及其实战

基本函数

完整文档地址

接口 描述
Predicate 表示一个参数的谓词(布尔值函数)。
Consumer 表示接受单个输入参数并且不返回结果的操作。
Function 表示接受一个参数并产生结果的函数。
Supplier 代表结果供应商。
UnaryOperator 表示对单个操作数产生与其操作数相同类型的结果的操作。
BiFunction 表示接受两个参数并产生结果的函数。
BinaryOperator 表示对同一类型的两个操作数的操作,产生与操作数相同类型的结果。
IntFunction 表示一个接受int值参数并产生结果的函数。
DoubleConsumer 表示接受单个 double值参数的操作,不返回任何结果。
DoubleFunction 表示接受双值参数并产生结果的函数。
BiConsumer 表示接受两个输入参数并且不返回结果的操作。

以上是在函数式编程中的基本函数模型,我们大可以将其与数学函数做关联:y = x +1,我们仅仅需要关注这个函数的输入输出即可。 以predicte函数举例:该函数输入一个表达式,输出一个布尔类型 一元函数function:输入一个类型参数输出为另一个类型,当然这两个类型可以是相同的,当相同时也可以使用unaryOperator来代替。具体下面有给出实际场景的代码断:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
public class FunctionDemo { 


public static void main(String[] args) {

//断言型
// predicate();
//消费型
// consumer();
//一元函数 输入输出不同
// function();
//提供型
// supplier();
//一元函数 输入输出类型相同
// unaryOperator();
//二元函数 输入输出不同
// biFunction();
//二元函数 输入输出相同
binaryOperator();
}

/** * */
public static void predicate(){

Predicate<Integer> predicate = i -> i > 0;
IntPredicate intPredicate = i -> i > 0;
System.out.print(predicate.test(6));
System.out.print(intPredicate.test(-1));
}

public static void consumer(){

Consumer<String> consumer = s -> System.out.println(s);
consumer.accept("我是一个消费者");
}

public static void function(){

Function<Integer,String> function = x -> "数字是:"+ x;
System.out.println(function.apply(88));
}

public static void supplier(){

Supplier<String> supplier = () -> "我是一个提供者";
System.out.println(supplier.get());
}

public static void unaryOperator(){

UnaryOperator<Integer> unaryOperator = x -> ++x;
System.out.println(unaryOperator.apply(1));
}

public static void biFunction(){

BiFunction<Integer,Double,Double> biFunction = (x,y) -> {

++x;
++y;
return x+y;
};
System.out.println(biFunction.apply(1,2.3));
}

public static void binaryOperator(){

IntBinaryOperator intBinaryOperator = (x,y) -> x + y;
System.out.println(intBinaryOperator.applyAsInt(2,3));
}


Map<Function<Model, String>, BiConsumer<Model, String>> getFunctionMap() {
HashMap<Function<Model, String>, BiConsumer<Model, String>> functionMap = Maps.newHashMap();
functionMap.put(Model::getJul, Model::setJul);
functionMap.put(Model::getAug, Model::setAug);
functionMap.put(Model::getSep, Model::setSep);
functionMap.put(Model::getOct, Model::setOct);
functionMap.put(Model::getNov, Model::setNov);
functionMap.put(Model::getDec, Model::setDec);
functionMap.put(Model::getJan, Model::setJan);
functionMap.put(Model::getFeb, Model::setFeb);
functionMap.put(Model::getMar, Model::setMar);
functionMap.put(Model::getApr, Model::setApr);
functionMap.put(Model::getMay, Model::setMay);
functionMap.put(Model::getJun, Model::setJun);
return functionMap;
}

public void tranfer() {
Map<Function<OverheadCostUnionCategoryVO, String>, BiConsumer<OverheadCostUnionCategoryVO, String>> functionMap = getFunctionMap();
functionMap.forEach((k, v) -> doSomething(k, v));
}


void doSomething(Function<OverheadCostUnionCategoryVO, String> k, BiConsumer<OverheadCostUnionCategoryVO, String>> setMethod) {
setMethod.accept(child, stripTrailingZeros(total));
}

}

lambda表达式

lambda表达式组成

形如以下:

(o1,o2) -> Integer.compare(o1,o2)

1
左边 -> 右边
  • -> 被称为lambda操作符或箭头操作符
  • 左边:lambda形参列表(其实就是接口中的抽象方法的形参列表)
  • 右边:lambda体 (其实就是重写的抽象方法的方法体)

lambda表达式使用

1.无参无返回值

1
2
3
4
5
6
7
8
9
public void test01(){
Runnable ri = new Runnable() {
@Override
public void run() {
System.out.println("好好学习,天天向上");
}
};
ri.run();
}
1
2
3
4
public void test02(){
Runnable r2 = ()-> {System.out.println("学会lambda,快乐每一天");};
r2.run();
}

2. 需要一个参数但无返回值

1
2
3
4
5
6
7
8
9
public void test05(){
Consumer<String> consumer = new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
};
consumer.accept("这一路上走走停停");
}
1
2
3
4
public void test06(){
Consumer<String> consumer = (String s)->{System.out.println(s);};
consumer.accept("留下少年漂流的痕迹");
}

3.数据类型可以省略,由编译器去推断出,称为“类型推断”

1
2
3
4
5
6
7
8
9
public void test05(){
Consumer<String> consumer = new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
};
consumer.accept("这一路上走走停停");
}
1
2
3
4
public void test06(){
Consumer<String> consumer = (s)->{System.out.println(s);};//类型推断
consumer.accept("留下少年漂流的痕迹");
}

4.lambda若只需要一个参数时,参数的小括号可以省略

1
2
3
4
5
6
7
8
9
public void test05(){
Consumer<String> consumer = new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
};
consumer.accept("这一路上走走停停");
}
1
2
3
4
public void test06(){
Consumer<String> consumer = s->{System.out.println(s);};//类型推断
consumer.accept("留下少年漂流的痕迹");
}

5.lambda需要两个或以上的参数,多条执行语句,并且可以有返回值

1
2
3
4
5
6
7
8
9
10
11
12
public void test03(){
Comparator<Integer> comparator = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
System.out.println(o1);
System.out.println(02);
return Integer.compare(o1,o2);
}
};
int compare = comparator.compare(12, 21);
System.out.println(compare);
}
1
2
3
4
5
6
7
8
public void test04(){
Comparator<Integer> comparator = (o1,o2)-> {
System.out.println(o1);
System.out.println(02);
return Integer.compare(o1,o2);};
int compare = comparator.compare(32,23);
System.out.println(compare);
}

6.当lambda体只有一条语句时,return与大括号若有,都可以省略

1
2
3
4
5
6
7
8
9
10
public void test03(){
Comparator<Integer> comparator = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return Integer.compare(o1,o2);
}
};
int compare = comparator.compare(12, 21);
System.out.println(compare);
}
1
2
3
4
5
public void test04(){
Comparator<Integer> comparator = (o1,o2)-> Integer.compare(o1,o2);
int compare = comparator.compare(32,23);
System.out.println(compare);
}

方法引用

我们可以直接使用两个冒号::来调用方法

  • 静态方法引用
  • 非静态 实例方法引用
  • 非静态 类方法引用
  • 构造函数方法引用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
public class MethodReferenceDemo { 

public static void main(String[] args) {

//消费者 方法引用模式
// consumer();
//静态方法引用
// callStaticMethod();
//非静态 实例方法引用
// callMethod();
//非静态 类方法引用
// callMethodByClass();
//构造函数方法引用
// callConstructorMethod();
//数据不变模式
callMethod2();
}

public static void consumer(){

Consumer<String> consumer = System.out::println;
consumer.accept("我是一个消费者");
}

private static void callStaticMethod() {

Consumer<Dog> consumer = Dog::bark;
consumer.accept(new Dog());
}

private static void callMethod() {

Dog dog = new Dog();
Function<Integer,Integer> function = dog::eat;
System.out.println("还剩[" + function.apply(3) + "]斤狗粮");
}

private static void callMethodByClass() {

BiFunction<Dog,Integer,Integer> biFunction = Dog::eat;
System.out.println("还剩[" + biFunction.apply(new Dog(),4) + "]斤狗粮");
}

private static void callConstructorMethod() {

Supplier<Dog> supplier = Dog::new;
System.out.println("new 了一个对象" + supplier.get());
}

private static void callMethod2() {

Dog dog = new Dog();
Function<Integer,Integer> function = dog::eat; //函数声明
dog = null;
System.out.println("还剩[" + function.apply(3) + "]斤狗粮");
}

}

Stream流API

Stream API是Java 8中加入的一套新的API,主要用于处理集合操作。Stream流API是函数式编程的核心所在,它以一种流式编程来对数据进行各种加工运算。形象的来说你可以把它看作工业中的流水线,将原料放入流中经过操作1、操作2…操作N输出一个产品。Stream也是如此它分为创建操作、中间操作、终止操作。业务逻辑清晰简单、代码看上去优雅不少。

流通常是由三个部分组成:

  1. 数据源:流的获取,比如list.stream()方法;
  2. 中间处理:中间处理是对流元素的一系列处理。比如过滤filter,排序sorted,映射map;
  3. 终端处理:终端处理会生成结果,结果可以是任何不是流值。

创建操作

Stream流创建

Stream流创建

在jdk8中集合数组加入了不少流的方法其中就有直接通过实例或是工具类创建流。如:list.stream(),而数据没有自身API需要借助工具类Arrays来创建。这里通过parallelStream()并行流的模式来创建就可以透明的使用到多线程了。

注:通过阅读源码可以知Stream类与IntStream、LongStream并没有继承关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public class CreateStream { 


public static void main(String[] args) {

// collectionCreate();
// arrayCreate();
// numCreate();
selfCreate();
}

/** * 集合创建 */
public static void collectionCreate(){

List<String> list = new ArrayList<>();
Stream<String> stream = list.stream();
Stream<String> parallelStream = list.parallelStream();
}

/** * 数组创建 */
public static void arrayCreate(){

Integer[] array = new Integer[5];
Stream<Integer> stream = Arrays.stream(array);
}

/** * 数字创建 */
public static void numCreate(){

IntStream.of(1,2,3);
IntStream.rangeClosed(1,10);
new Random().ints().limit(10);
}

/** * 自己创建 */
public static void selfCreate(){

Random random = new Random();
Stream.generate(random::nextInt).limit(20);
Stream.iterate(2, (x) -> x*2).limit(10).forEach(System.out::println);
}
}

中间操作

中间操作分为有状态操作、无状态操作。无状态操作即该中间操作不依赖与另外的空间来存放临时结果。有状态即需要。这么说还是比较抽象,我们不妨来举个栗子0.0。

比如说:你的排序操作传统我们要进行排序是否需要依赖额外空间来进行大小的比较。去重操作需要额外空间来存放未重复的值。而像是filter只是单纯返回过滤后的结果无需额外空间。

这是一种说法,另一种说法该操作与其他操作,没有依赖关系即为无状态,反正则为有状态。这么说也没错,你看像是order操作不是就要等前面操作都执行完才可以执行吗。后面会提到一点就是Stream的操作模式实际上是每一条数据通过A操作B操作C操作来进行的,而到了中间有有状态操作是,必须停下等所有数据都操作到这一步时一起进行,否则你让他如何进行排序呢?

Stream流中间操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
public class MiddleStream { 

public static void main(String[] args) {

// mapOrMapToXXX();
// flatMap();
// peek();
// distinct();
// sort();
limitSkip();
}

/** * map操作 A -> B * filter操作 */
public static void mapOrMapToXXX(){

String s = "my name is 007";
Stream.of(s.split(" ")).map(String::length).forEach(System.out::println);
System.out.println("-------------");
Stream.of(s.split(" ")).filter(x -> x.length() > 2).mapToDouble(x -> x.length()).forEach(System.out::println);
}

/** * flatMap操作 A -> B list * IntStream/LongStream 并不是stream的子类需要进行装箱 */
public static void flatMap(){

String s = "my name is 007";
Stream.of(s.split(" ")).flatMap(x -> x.chars().boxed()).forEach(x -> System.out.println((char)x.intValue()));
}

/** * peek 要类型对应 */
public static void peek(){

IntStream.of(new int[]{
1,2,3,4,5}).peek(System.out::println).forEach(x->{
});
}

/** * distinct */
public static void distinct(){

IntStream.of(new int[]{
1,3,3,4,5}).distinct().forEach(System.out::println);
}

/** * sort */
public static void sort(){

IntStream.of(new int[]{
5,4,3,1,2}).sorted().forEach(System.out::println);
}

/** * limitSkip */
public static void limitSkip(){

IntStream.of(new int[]{
1,2,3,4,5,6,7,8}).skip(2).limit(2).forEach(System.out::println);
}
}

这里提到Stream有个特性叫做:惰性求值。什么意思呢?就是当stream没有调用到终止操作时,实际上是不会执行之前的所有过程的。这一点可以在demo工程中有相应的证明方法。 有接触过spark的同学可以将这一特性类比为Transformation和Action。

终止操作

终止操作即流水线的最后一个操作,往往就是返回你所要的产品。 这里分为短路操作和非短路操作:

非短路操作:从流中获取所有数据进行运算返回,有可能返回一个或多个值,但必定运用到了所有数据 短路操作:从流中截取部分数据返回。

Stream流终止操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
public class FinalStream { 

public static void main(String[] args){

// forEachOrdered();
// collect();
// reduce();
// minMixCount();
findFirst();
}

/** * forEachOrdered */
public static void forEachOrdered(){

IntStream.of(new int[]{
1,2,3,4,5,6,7}).parallel().forEach(System.out::println);
// IntStream.of(new int[]{1,2,3,4,5,6,7}).parallel().forEachOrdered(System.out::println);
}

/** * collect、toArray */
public static void collect(){

String s = "hello world!";
List<String> collect = Stream.of(s.split(" ")).collect(Collectors.toList());
System.out.println(collect);
}

/** * reduce */
public static void reduce(){

Integer[] intArr = new Integer[]{
1,2,3,4,5,6,7,8,9,10};
Optional<Integer> optional = Stream.of(intArr).reduce((x, y) -> x + y);
System.out.println(optional.get());
}

/** * minMixCount */
public static void minMixCount(){

Integer[] intArr = new Integer[]{
1,2,3,4,5,6,7,8,9,10};
Optional<Integer> optional = Stream.of(intArr).max(Comparator.comparingInt(x -> x));
System.out.println(optional.get());
}

//短路操作--------------------------------
/** * findFirst */
public static void findFirst(){

Optional<Integer> first = Stream.generate(() -> new Random().nextInt()).findFirst();
System.out.println(first.get());
}
}

并行流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
public class ParallelStream { 


public static void main(String[] args) {

// createParallelStream();
// feature2();
// feature3();
feature4();

}

/** * 特性一 并行流线程数 * 并行流线程数默认为cpu个数 * 默认线程池 */
public static void createParallelStream(){

IntStream.range(1, 100).parallel().forEach(ParallelStream::printDebug);
}

/** * 特性二 并行再串行 以最后一个流为准 */
private static void feature2(){

IntStream.range(1, 100).parallel().peek(ParallelStream::printDebug).sequential().peek(ParallelStream::printDebug2).count();
}

/** * 特性三 默认线程池与设置默认线程数 */
private static void feature3(){

System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism","3");
IntStream.range(1, 100).parallel().forEach(ParallelStream::printDebug);
}

/** * 特性四 自定义线程池 防止线程被阻塞 */
private static void feature4(){

ForkJoinPool forkJoinPool = new ForkJoinPool();
forkJoinPool.submit(() -> IntStream.range(1, 100).parallel().forEach(ParallelStream::printDebug));
forkJoinPool.shutdown();

try {

Thread.sleep(10000);
} catch (InterruptedException e) {

e.printStackTrace();
}
}


private static void printDebug(int i){

// System.out.println(i);
System.out.println(Thread.currentThread().getName() + "debug:" + i);
try {

Thread.sleep(3000);
} catch (InterruptedException e) {

e.printStackTrace();
}
}

private static void printDebug2(int i){

System.err.println(i);
try {

Thread.sleep(3000);
} catch (InterruptedException e) {

e.printStackTrace();
}
}
}

级联表达式与柯里化

简单来说就是将一个复杂表达式拆解为多个简单表达式,比如数学中的: y=5! 可以等价为 y = 1 2 3 4 5

注意:这里涉及一个基础概念数据不变性,说白了就是匿名类中运用到外部变量时,外部变量需要是常量。细心的你会发现在级联表达式中外部变量均为常量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/** * 级联表达式和柯里化 * * @author 旭旭 * @create 2018-08-12 1:09 **/
public class CurryDemo {


public static void main(String[] args) {

//级联表达式
Function<Integer,Function<Integer,Integer>> fun = x -> y -> x + y;
//柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数
//柯里化的意义:函数标准化
//高阶函数:返回函数的函数
System.out.println(fun.apply(2).apply(3));

Function<Integer,Function<Integer,Function<Integer,Integer>>> fun2 = x -> y -> z -> x + y + z;

}
}

收集器(终止操作因为内容较多提出来说明)

终止操作中将数据以集合方式回收,可以对数据进行分类统计等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/** * 收集器 * * @author 旭旭 * @create 2018-08-18 23:43 **/
public class CollectorsStream {


public static void main(String[] args) {

List<Student> students = new ArrayList<>();
students.add(new Student("一号",7,true,"一年级"));
students.add(new Student("二号",8,true,"二年级"));
students.add(new Student("三号",8,false,"二年级"));
students.add(new Student("四号",9,true,"三年级"));
students.add(new Student("五号",7,false,"一年级"));
students.add(new Student("六号",8,true,"二年级"));
students.add(new Student("七号",10,true,"四年级"));

// dataToList(students);
// summary(students);
// partitioning(students);
group(students);
}

/** * 获取某一数据的集合 */
public static void dataToList(List<Student> students){

List<Integer> list = students.stream().map(Student::getAge).collect(Collectors.toList());
System.out.println(list);
}

/** * 获取某一数据的汇总值 */
public static void summary(List<Student> students){

IntSummaryStatistics collect = students.stream().collect(Collectors.summarizingInt(Student::getAge));
System.out.println(collect);
}

/** * 根据某一数据分类 */
public static void partitioning(List<Student> students){

Map<Boolean, List<Student>> collect = students.stream().collect(Collectors.partitioningBy(x -> x.isGender()));
System.out.println(collect);
}

/** * 根据某一数据分组 */
public static void group(List<Student> students){

Map<String, Long> collect = students.stream().collect(Collectors.groupingBy(Student::getGrade, Collectors.counting()));
System.out.println(collect);
}
}

Stream特性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
/** * 流运行机制,基本特性 * * @author 旭旭 * @create 2018-08-19 22:51 **/
public class FeatureStream {

public static void main(String[] args){

// feature123();
feature46();
// feature5();
}

/** * 特性一 所有操作都是链式调用一个操作只迭代一次 * 特性二 每一个中间流返回一个新的流,里面的sourceStage都指向同一个地方就是Head * 特性三 Head -> NextStage -> NextStage -> null */
public static void feature123(){

Random random = new Random();
Stream<Integer> integerStream = Stream.generate(random::nextInt)
.limit(500)
.peek(x -> System.out.println("peek -> " + x))
.filter(x -> {
System.out.println("filter -> " + x);return x > 100000;});
integerStream.count();
}

/** * 特性四 有状态操作(多个参数操作),会把无状态操作阶段分隔,单独处理。 * parallel / sequetial 这个2个操作也是中间操作,但是他们不创建新的流,而是修改 * Head的并行状态,所以多次调用时只会生效最后一个。 */
public static void feature46(){

Random random = new Random();
Stream<Integer> integerStream = Stream.generate(random::nextInt)
.limit(500)
.peek(x -> System.out.println("peek -> " + x))
.filter(x -> {
System.out.println("filter -> " + x);return x > 100000;})
.sorted((x,y) -> {
System.out.println("sorted -> " + x);return x - y;})
.filter(x -> {
System.out.println("filter -> " + x);return x > 100000;})
// .parallel()
;
integerStream.count();
}


/** * 特性五 有状态操作并行环境下不一定能并行操作 */
public static void feature5(){

Random random = new Random();
Stream<Integer> integerStream = Stream.generate(random::nextInt)
.limit(500)
.peek(x -> print("peek -> " + x))
.filter(x -> {
print("filter -> " + x);return x > 100000;})
.sorted((x,y) -> {
print("sorted -> " + x);return x - y;})
.filter(x -> {
print("filter -> " + x);return x > 100000;})
.parallel();
integerStream.count();
}

private static void print(String x){

System.out.println(Thread.currentThread().getName() + " " + x);
}
}

This is copyright.

...

...

00:00
00:00