开发当中一直都有在使用函数式编程,尤其是在Stream类时,今天就来对jdk8中的函数式编程来做一个学习汇总。
我们最常用的面向对象编程(Java)属于命令式编程(Imperative Programming)这种编程范式。常见的编程范式还有逻辑式编程(Logic Programming),函数式编程(Functional Programming)。函数式编程作为一种编程范式,在科学领域,是一种编写计算机程序数据结构和元素的方式,它把计算过程当做是数学函数的求值,而避免更改状态和可变数据。
在介绍函数式编程之前,让我们来看一下jdk8中提供的Lambda表达式的五种形式:
1.
Runnable noArguments = () -> System.out.println("Hello World");
2.
ActionListener oneArgument = event -> System.out.println("button clicked");
3.
Runnable multiStatement = () -> {
System.out.print("Hello");
System.out.println(" World");
};
4.
BinaryOperator add = (x, y) -> x + y;
5.
BinaryOperator addExplicit = (Long x, Long y) -> x + y;
1中所示的 Lambda表达式不包含参数,使用空括号 () 表示没有参数。该 Lambda 表达式 实现了 Runnable 接口,该接口也只有一个 run 方法,没有参数,且返回类型为void。2中所示的Lambda表达式包含且只包含一个参数,可省略参数的括号。Lambda表达式的主体不仅可以是一个表达式,而且也可以是一段代码块,使用大括号 ({})将代码块括起来,如3所示。该代码块和普通方法遵循的规则别无二致,可以用返回或抛出异常来退出。只有一行代码的Lambda表达式也可使用大括号,用以明确Lambda表达式从何处开始、到哪里结束。Lambda表达式也可以表示包含多个参数的方法,如4所示。这时就有必要思考怎样去阅读该Lambda 表达式。这行代码并不是将两个数字相加,而是创建了一个函数,用来计算两个数字相加的结果。变量add的类型是BinaryOperator。当需要限定使用该Lambda表达式的使用范围,比如说只能传递Long型的参数时,则可以将参数具体化为Long型,如5。
我们以Spring中创建一个自定义的事件监听器为例:
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
/**
* Handle an application event.
* @param event the event to respond to
*/
void onApplicationEvent(E event);
}
ApplicationListener 只有一个抽象方法:actionPerformed,被用来表示行为:接受一个参数,返回空。当我们要自定义一个自己的ApplicationListener时,可以通过匿名内部列的形式实现:
@SpringBootApplication
public class App {
private static final Logger log = LoggerFactory.getLogger(App.class);
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
@Bean
public ApplicationListener newApplicationListener() {
System.out.println("create a ApplicationListener");
return new ApplicationListener() {
@Override
public void onApplicationEvent(ApplicationEvent event) {
System.out.println("process event");
}
};
}
}
当然,还可以使用上文提到的Lambda表达式的形式
@SpringBootApplication
public class App {
private static final Logger log = LoggerFactory.getLogger(App.class);
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
@Bean
public ApplicationListener newApplicationListener() {
System.out.println("create a ApplicationListener");
return event -> {
System.out.println("process event");
};
}
}
一个限制是,函数接口里面只能有一个抽象方法
。
其实在上一节当中,我们已经使用了函数接口。jdk中为我们提供了@FunctionalInterface注解,该注解的作用是,当我们对一个接口进行标注时,编译器会检查该接口是否只用一个抽象方法。也就是说,即使我们的接口不标注该注解,只要满足接口只有一个抽象函数的要求,就是一个函数接口。ApplicationListener就是一个函数接口。
JDK8中提供了一组常用的核心函数接口:
接口 | 参数 | 返回类型 | 描述 |
---|---|---|---|
Predicate | T | boolean | 用于判别一个对象。比如求一个人是否为男性 |
Consumer | T | void | 用于接收一个对象进行处理但没有返回,比如接收一个人并打印他的名字 |
Function | T | R | 转换一个对象为不同类型的对象 |
Supplier | None | T | 提供一个对象 |
UnaryOperator | T | T | 接收对象并返回同类型的对象 |
BinaryOperator | (T, T) | T | 接收两个同类型的对象,并返回一个原类型对象 |
除此之外,还有其他一些很多的函数接口,这些接口都在java.util.function包下
任意点开一个接口,我们都可以发现,该接口只会有一个抽象函数,0个或者多个default函数。
在大多数场景中,我们使用函数接口进行编程主要是在Stream中,打开Stream的类可以发现,该类的方法参数很多都是使用到了函数接口,以Stream类foreach为例:
public static void main(String[] args) {
List stringList = new ArrayList<>();
stringList.add("1");
stringList.add("2");
stringList.stream().forEach((value) -> {
System.out.println(value);
});
}
其实质是:
public static void main(String[] args) {
List stringList = new ArrayList<>();
stringList.add("1");
stringList.add("2");
Consumer stringConsumer = (value) -> {
System.out.println("value is : " + value);
};
stringList.stream().forEach(stringConsumer);
}
在使用Stream的过程中,我们也许还会见到下面这种情况:
public class App {
private static final Logger log = LoggerFactory.getLogger(App.class);
public static void main(String[] args) {
List list = Lists.newArrayList(3, 5, 2, 9, 1);
int maxInt = list.stream()
.max(Integer::compareTo)
.get();
assertEquals(maxInt, 9);
List list2 = Lists.newArrayList(-3, 5, -2, 9, -1);
assertEquals(list2.stream().map(Math::abs).allMatch(e -> e > 0), true);
Comparator comparator = new Comparator() {
@Override
public int compare(Person o1, Person o2) {
return o1.getAge().compareTo(o2.getAge());
}
};
List PersonList = Lists.newArrayList(new Person("teny",23), new Person("tome",55));
Person minAgePerson = PersonList.stream()
.min(comparator::compare)
.get();
assertEquals(minAgePerson.getAge(), 23);
Person newPerson = Person.createPerson(Person::new);
}
public static class Person {
private String name;
private Integer age;
public static Person createPerson(Supplier supplier) {
return supplier.get();
}
public Person() {
this.name = "";
this.age = -1;
}
public Person(String name, Integer age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public Integer getAge() {
return age;
}
}
}
Integer::compareTo
也是属于Java8引入的新特性,叫做方法引用(Method References),其实就是 (int1, int2) -> int1.compareTo(int2) 的简写。
引用方法有下面几种方式
Comparator comparator = new Comparator() {
@Override
public int compare(Person o1, Person o2) {
return o1.getAge().compareTo(o2.getAge());
}
};
List PersonList = Lists.newArrayList(new Person("teny",23), new Person("tome",55));
Person minAgePerson = PersonList.stream()
.min(comparator::compare)
.get();
assertEquals(minAgePerson.getAge(), 23);
List<Integer> list2 = Lists.newArrayList(-3, 5, -2, 9, -1);
assertEquals(list2.stream().map(Math::abs).allMatch(e -> e > 0), true);
List<Integer> list = Lists.newArrayList(3, 5, 2, 9, 1);
int maxInt = list.stream()
.max(Integer::compareTo)
.get();
assertEquals(maxInt, 9);
Person newPerson = Person.createPerson(Person::new);
欢迎关注个人公众号: