1、为什么要函数式编程
不管是C语言,Java,还是数据库的SQL,函数Function绝对是我们最常打交道的对象。今天要介绍的“函数式编程”,是指Java8引入的新语法,虽然Java8在2014年就推出了,我想应该还是有很多同学不太了解。
我们以前接触的函数,基本都是接受变量参数,返回结果参数。这些参数可以是基本变量、对象和数组等,但能不能是一段代码块呢?Java里面代码块都能抽象成一个函数,所谓函数式编程,就是在定义一个函数时,它的参数也是一个函数的接口。在调用函数时,再去实现这个函数的接口,这个过程,也就出现了lambda表达式。
如下文,我们定义了一个函数 - 通知所有员工。
/**
* 通知员工
* @param notify
*/
public static void notifyEmployees( Consumer<Employee> notify){
List<Employee> employeeList=employeeMapper.getAllEmployees();
for(Employee employee : employeeList){
notify.accept(employee);
}
}
但是我们没有定义怎么去通知员工,可能是邮件通知,可能是短信通知,也有可能是微信通知,等等。我们将具体通知的方式,作为这个函数的一个传入参数。这里借助了Java自带的函数式接口 Consumer<T> ,后面会介绍它怎么用。那么我在调用这个方法的时候,就可以根据情况去实现这个函数式接口。
//通过邮件
notifyEmployees((employee)->{
sendEmail(employee.getEmail());
});
//通过短信
notifyEmployees((employee)->{
sendSms(employee.getPhoneNumber());
});
// 等等 ...
2、Lambda表达式
Lambda表达式支持将代码块作为方法参数,Lambda 表达式允许使用更简洁的代码来创建只有一个抽象方法的接口(这种接口被称为函数式接口)的实例。
在上文实现函数式编程时,你或许感觉奇怪,为什么通过 -> 箭头之类的语法,就能实现接口?这就是Lambda表达式的语法,其实是对我们以前创建匿名内部类的简写。
匿名内部类也是一种简化代码的方式,我最早接触到的匿名内部类,就是在多线程中创建Thread对象时,通过实现Runnable接口来创建。
Thread thread=new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"开始了!");
}
});
thread.start();
实际上new Thread(Runnable target) 接收的参数,是实现了Runnable接口的类的对象。但由于我们关心的是接口里面的实现方法,而不是类的名字,所以用匿名内部类的方式来创建对象。
但是在看了Runnable接口的定义后,你会发现这个类只有一个抽象方法,这就又让人很不爽。我关心的只是这个类里面的这一个抽象方法,能不能不去实现这个类?Lambda表达式就应运而生了,它让代码看起来更简洁美观。
Thread thread = new Thread(() -> System.out.println(Thread.currentThread().getName() + "开始了!"));
thread.start();
3、函数式接口
函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。函数式接口可以被隐式转换为 lambda 表达式。
我们再看一下Runnable这个函数式接口。
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
函数式接口的特点如下:
- 接口有且仅有一个抽象方法
- 允许定义静态方法
- 允许定义默认方法
- 允许java.lang.Object中的public方法
- 注解@FunctionInterface不是必须的,如果一个接口符合"函数式接口"定义,那么加不加该注解都没有影响。加上该注解能够更好地让编译器进行检查。如果编写的不是函数式接口,但是加上了@FunctionInterface,那么编译器会报错,类似于@Override 注解。
Java8定义了大量的预定义函数式接口,用于常见类型的代码传递,这些函数定义在java.util.function下。
- Predicate<T> - 断言
接受参数对象T,返回一个boolean结果
Predicate<String> isAdminPre=(String username)-> "admin".equals(username);
System.out.println(isAdminPre.test("kerry"));
System.out.println(isAdminPre.test("admin"));
- Consumer<T> - 消费者
接收参数对象T,不返回结果
Consumer<String> msgCon = (String msg) -> {
System.out.println("发送消息" + msg);
System.out.println("消息发送完成");
};
msgCon.accept("01!01!我是02!");
- Function<T,R> - 函数
接收参数对象T,返回结果对象R
Function<Date,String> timeFunc=(Date date)->{
Calendar calendar=Calendar.getInstance();
calendar.setTime(date);
int hour=calendar.get(Calendar.HOUR_OF_DAY);
if(hour>=6 && hour<12){
return "上午";
}else if(hour>=12&& hour<18){
return "下午";
}else {
return "晚上";
}
};
System.out.println(timeFunc.apply(new Date()));
- Supplier<T> - 供应者
不接受参数,提供对象T的创建工程
Supplier<String> uuidSup=()-> UUID.randomUUID().toString().replace("-","");
System.out.println(uuidSup.get());
- UnaryOperator<T> - 一元运算
接收参数对象T,返回结果对象T
UnaryOperator<String> trimUO=(String str)->{
if(str ==null){
return str;
}
return str.trim();
};
System.out.println(trimUO.apply(" Hello "));
- BinaryOperator<T> - 二元运算
接收两个T对象,返回一个T对象结果
BinaryOperator<Integer> maxBO=(Integer i1,Integer i2)-> i1>i2?i1:i2;
System.out.println(maxBO.apply(13,20));
4、函数式数据处理Stream
Java 8 API添加了一个新的抽象称为流Stream,可以让你以一种声明的方式处理数据。Stream API 借助于Lambda 表达式,极大的提高编程效率和程序可读性。
Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象,它专注于对集合对象进行各种非常便利、高效的聚合操作,或者大批量数据操作。这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。
元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果。
我个人感觉,Stream有点像是用Java代码编写的Hadoop。Hadoop利用分布式服务器,提高并行计算的能力,而Stream也同样可以通过多线程,提高数据处理的能力。它们也同样用map和reduce,做数据的映射和整合。
流的操作类型分为两种:
- Intermediate:一个流可以后面跟随零个或多个 intermediate 操作。其目的主要是打开流,做出某种程度的数据映射/过滤,然后返回一个新的流,交给下一个操作使用。这类操作都是惰性化的(lazy),就是说,仅仅调用到这类方法,并没有真正开始流的遍历。
- Terminal:一个流只能有一个 terminal 操作,当这个操作执行后,流就被使用“光”了,无法再被操作。所以这必定是流的最后一个操作。Terminal 操作的执行,才会真正开始流的遍历,并且会生成一个结果,或者一个 side effect。
常见的流操作方法有如下:
- Intermediate:
map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip、 parallel、 sequential、 unordered
- Terminal:
forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 iterator
- Short-circuiting:
anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 limit
关于Stream的知识点比较多,《Java 8 中的 Streams API 详解》 这篇文章是我看到比较好的Stream系列博客,建议大家直接看这篇文章。
5、非空处理Optional
Optional有点像Stream,是Java8推出的处理数据的包装类,很多方法的实现都和lambda表达式结合在一起,推近了函数式编程风格。Optional主要用来处理常见的空指针异常,学会它,你的代码会美观很多。
- 创建Optional实例
可通过 Optional.ofNullable(T value)和Optional.of(T value),前者允许被包装的对象为空。而后者,如果被包装的对象为空还是会报空指针异常。
从Optional实例中取回实际值对象的方法之一是使用 get()方法,不过这个方法会在值为null的时候抛出异常。要避免异常,你可以选择先用isPresent()验证是否有值。
User user=new User();
Optional<User> userOptional=Optional.ofNullable(user);
//或
Optional<User> userOptional=Optional.of(user);
- isPresent()
Optional对象的isPresent()方法返回boolean值,如果为空返回false,不为空返回true。
- ifPresent(Customer < T > customer)
该方法除了执行检查,还接受一个Consumer(消费者) 参数,如果对象不是空的,就对执行传入的 Lambda 表达式.
- 默认值 orElse()、orElseGet()
orElse(),如果有值则返回该值,否则返回传递给它的参数值。
orElseGet(),这个方法会在有值的时候返回值,如果没有值,它会执行作为参数传入的 Supplier 函数式接口,并将返回其执行结果。
User newUser=Optional.ofNullable(user).orElse(new User("anonymous"));
//或
User newUser=Optional.ofNullable(user).orElseGet(()->{
User sUser=new User("anonymous");
sUser.setAge(25);
return sUser;
});
值得注意的是,就算对象不为空,orElse() 方法仍然会创建User对象,只是不会给newUser赋值。而orElseGet() 方法,只有当对象为空的时候才会创建 User 对象。这个差异,在方法调用量大的时候,对性能产生的影响差异比较大。
- 返回异常 orElseThrow()
orElseThrow() 会在对象为空的时候抛出异常,而不是返回默认值,这个方法让我们有更丰富的语义,可以决定抛出什么样的异常,而不总是抛出 NullPointerException。
- 数据转换 map()、flatMap()
map() 对值应用(调用)作为参数的函数,然后将返回的值包装在Optional中。这就使对返回值进行链试调用的操作成为可能 。
flatMap() 也需要函数作为参数,并对值调用这个函数,但是会直接返回结果,并不会包装在Optional中。
User user=new User();
String name=Optional.ofNullable(user)
.map(User::getName)
.orElse("anonymous");
System.out.println(name);
map常用于链式方法判断中,例如上文代码实际上等于:
User user=new User();
String name="anonymous";
if(user!=null){
if(user.getName()!=null){
name=user.getName();
}
}
System.out.println(name);
- 过滤 filter()
filter() 接受一个 Predicate 参数,返回测试结果为 true 的值。如果测试结果为 false,会返回一个空的 Optional。
User user=new User("Kerry");
user.setEmail("kerry.wu@definesys.com");
Optional<User> filterUser=Optional.ofNullable(user)
.filter(u->u.getEmail()!=null&&u.getEmail().contains("@"));
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。