Java函数式编程
前言
不对概念进行理解,直接学应用,往往会不得要领,失去方向,所以学习前先弄明白概念是非常重要的。
参考:(英文还可以的,可以看看这个大神写的,比较全面,包含了函数式接口和Stream部分)
https://jenkov.com/tutorials/java-functional-programming/index.html
概念理解
在理解函数式编程之前,先回顾下什么是面向对象编程。所谓面向对象编程,简单一点理解:就是把现实中的事物抽象为一个对象,通过对象与对象之间的通信达到系统运作的目的。
而函数式编程,也可以理解为面向函数编程。那什么是面向函数编程呢,或者什么可以抽象为函数呢?其实就是过程,或者说是方法。(此处值得细品)这里不展开,下面通过Java内建的几个函数接口理解一下。
预备知识
- Java lambada表达式
- Java stream编程
Java内建的函数式接口
Function接口
接口定义:
public interface Function<T,R> {
public <R> apply(T parameter);
}
抽象:这个接口完成了一件事情,传入一个类型T的参数,然后返回类型为R的结果。
看一个例子:
List<Student> studentList =
Arrays.asList(new Student("晓明",20),new Student("晓红",17));
Stream<Student> stream = studentList.stream();
List<String> nameList = stream.map(new Function<Student, String>() {
@Override
public String apply(Student student) {
return student.getName();
}
}).collect(Collectors.toList());
这里我们做了一件事情,将student的name取出来组成一个列表。当然上面是为了看清楚function,所以写成接口实现的形式,实际开发中我们常常使用lambada表达式:
stream.map(student->student.getName()).collect(Collectors.toList());
或者直接方法引用
stream.map(Student::getName).collect(Collectors.toList());
到这里,我们可以简单的理解,Function抽象了一种对象的transform。当然可能从很多角度可以有很多外延含义,大家可以自己发挥想象。
Predicate接口
接口定义:
public interface Predicate<T> {
boolean test(T t);
}
抽象:传入一个参数,返回一个true/false
还是刚刚的例子,我要找出所有成年的学生:
List<Student> studentList =
Arrays.asList(new Student("晓明",20),new Student("晓红",21));
Stream<Student> stream = studentList.stream();
List<Student> adultStudentList = stream.filter(new Predicate<Student>() {
@Override
public boolean test(Student student) {
return student.getAge() >= 18;
}
}).collect(Collectors.toList());
lambada简化:
stream.filter(student->student.getAge() >= 18).collect(Collectors.toList());
我们可以简单的理解:Predicate抽象了【是】还是【不是】的这个逻辑。
UnaryOperator接口
接口定义:(unary:一元)
public interface UnaryOperator<T> extends Function<T, T> {
//这里忽略了一些静态方法
}
抽象:这里继承了Function<T,T>接口,通过泛型可以看到:传入类型T参数,返回还是T类型参数。
看个例子:过年了,所有的学生长一岁:
List<Student> studentList =
Arrays.asList(new Student("晓明",20),new Student("晓红",21));
UnaryOperator<Student> ageIncrease = (student)-> {student.age = student.age + 1;return student;};
for (Student student : studentList) {
ageIncrease.apply(student);
}
这里就不举stream的例子了,stream里面的迭代器会用到UnaryOperator接口,感兴趣可以自己尝试用其实现一个斐波拉契数列?
BinaryOperator接口
接口定义:(unary:二元)
public interface BinaryOperator<T> extends BiFunction<T,T,T> {
//省略静态方法
}
抽象:对比UnaryOperator接口(一元),BinaryOperator接口自然是二元(两个相同类型的运算参数)。
直接看例子:(算一下学生总年龄)
List<Student> studentList =
Arrays.asList(new Student("晓明", 20),
new Student("晓红", 21),
new Student("张三", 22),
new Student("李四", 23)
);
Stream<Student> stream = studentList.stream();
Optional<Integer> reduce = stream.map(Student::getAge).reduce((age, totalAge) -> {
return age + totalAge;
});
System.out.println(reduce.get());
这里大概写下reduce接口的运行过程:
- 第一次age=21,totalAge=22
- 第二次age=22,totalAge=43(21+22)
- 第三次age=23,totalAge=65(22+43)
Supplier接口
接口定义:
public interface Supplier<T> {
T get();
}
抽象:supplier英文意思是提供,这里可以理解为生成器?
还是看例子:(自动生成年龄)
Supplier<Integer> supplier = () -> new Integer((int) (Math.random() * 100D));
这个接口不常用,不再赘述,大家可以随意发挥。Stream.generate(Supplier)静态方法也可以用于生成斐波那契数列,可以自己试试。
Consumer接口
接口定义:(跟名字一样,消费者)
public interface Consumer<T> {
void accept(T t);
}
抽象:消费者…
看例子:过年了,所有的学生长一岁(上面写过了,用这个接口试一下)
List<Student> studentList =
Arrays.asList(new Student("晓明", 20),new Student("晓红", 21));
Stream<Student> stream = studentList.stream();
//foreach的参数就是Consumer
stream.forEach(student -> student.age++);
结尾
剩下的就是大伙自己体会了,再结合Stream编程看看,应该会有所体悟。
当然Java还内置了很多函数式接口,想了解更多可以到java.util.function包下看看。