函数式编程(Functional Programming)是编程范式的一种。最常见的编程范式是命令式编程(Impera Programming),比如面向过程、面向对象编程都属于命令式编程,大家用得最多、最熟悉。函数式编程并非近几年的新技术或新思维,其诞生已有50多年时间。
在函数式编程里面,一切都是数学函数。当然,函数式编程语言里也可以有对象,但这些对象是不可变的——要么是函数参数要么是返回值。函数式编程语言里没有for等循环,而是通过递归、把函数当成参数传递的方式实现循环效果。
简而言之,函数式编程的特点:一切皆函数(称算子?)、一切数据皆不可变,一切计算都是函数作用在不可变数据上产生新不可变数据的过程。
注:Java 8的主要变化(详情可参阅 Java8 新特性)
- lambda表达式
- 函数式接口
- 方法引用
- 接口默认方法
- Stream
- 用Optional取代null
- 新的日志和时间:推荐用Instance代替Date、用LocalDateTime代替Calendar、用DateTimeFormatter代替SimpleDateFormat
- CompletableFuture
- 去除了永久代(PermGen) 被元空间(Metaspace)代替
2 Java 8函数式编程(Lambda表达式)
Java 8开始支持函数式编程,其是通过Lambda表达式语法来支持的。
Java SE 8 adds a relatively small number of new language features -- lambda expressions, method references, default and static methods in interfaces, and more widespread use of type inference
2.1 Lambda表达式
Lambda表达式可以看成是对 特定接口的匿名实现类语法 的简写(只是语法上看如是,它们在JVM层面是有明显区别的,见后文),这里的“特定接口”是指函数式接口,若不是函数式接口则传参时若使用Lambda表达式编译器会报错。示例:
new Thread(new Runnable() { @Override public void run() { } });//通过匿名内部类创建Runnable接口实现类作为Thread的参数 new Thread(() -> { });//通过Labmda表达式创建Thread的参数 Comparator<String> c = (s1, s2) -> s1.compareToIgnoreCase(s2); Comparator<String> c = (String s1, String s2) -> s1.compareToIgnoreCase(s2); FileFilter java = f -> f.getName().endsWith(".java"); button.addActionListener(e -> ui.dazzle(e.getModifiers()));
Supplier<Runnable> c = () -> () -> { System.out.println("hi"); };
Callable<Integer> c = flag ? (() -> 23) : (() -> 42);
Lambda表达式代替匿名内部类作为函数的参数时,相当于向函数传递了个算子。与匿名内部类相比,Lambda表达式省略了方法名、方法的参数类型有无均可,当然最好不要写类型以由编译器自动推断从而提高灵活性。
2.2 函数式接口
函数式接口:接口中【与Objec的方法签名不同的抽象方法】有且只有一个的接口。
注:
由于java中Object是任意类或接口的父类,故该抽象方法签名不能与Object中的方法一样。可以一样,但此时该方法不会被认为是函数式接口的抽象方法。
接口中可有非抽象方法,如default、static method等。
可在接口上加@FunctionalInterface,此时编译器会检查接口是否符合函数式接口规范。
示例:
@FunctionalInterface public interface CallBack { public String run();// 须有且只有一个抽象方法 public String toString();//允许,但不会被认为是函数式接口的抽象方法,因与Object中的方法的signature一样 default String getName() {// 允许有default方法 return "zhangsan"; } public static String getVersion() {// 允许有static方法 return "1"; } }
Java 8 定义了一系列常用的函数式接口(java.util.function包下),可覆盖绝大多数需求场景,可直接使用这些而不是自己另外定义。主要有:
| 函数接口 | 抽象方法 | 功能 | 示例 |
| Predicate<T> | boolean test(T t) | 根据输入的T值得到布尔值 | 身高大于177cm? |
| Supplier<T> | T get() | 产生T类型的消息 | |
| Consumer<T> | void accept(T t) | 消费T类型的消息 | |
| Function<T, R> | R apply(T t) | 将T类型值转换成R类型值 | 根据student对象获取名字 |
| BiPredicate<T, U> | boolean test(T t, U u) | 根据输入的T、U两值得到布尔值 | |
| BiConsumer<T, U> | void accept(T t, U u) | 消费T、U两种类型的两个消息 | |
| BiFunction<T, R, R> | R apply(T t, U u) | 将T、U类型值转成R类型值 | 两个数乘积 |
| UnaryOperator | T apply(T t) | Function的特例,一元操作 | 整数取反 |
| BinaryOperator | T apply(T t, T u) | BiFunction的特例,二元操作 | 两数求和 |
| IntPredicate | boolean test(int t) | Predicate的特例,只不过输入值为int类型 | |
| IntSupplier | int getAsInt() | 类似于Supplier,只不过返回值为int类型 | |
| IntConsumer | void accept(int t) | Consumer的特例,只不过输入轴为int类型 | |
| IntFunction<R> | R apply(int t) | Function的特例,只不过输入值为int类型 | |
| IntUnaryOperator | int applyAsInt(int operand) | 类似于UnaryOperator,只不过输入值为int类型 | |
| IntBinaryOperator | int applyAsInt(int left, int right) | 类似于BinaryOperator,只不过输入值为int类型 | |
| ToIntFunction<T> | int applyAsInt(T t) | 类似于Function,只不过返回值为int类型 | |
| ToIntBiFunction<T, U> | int applyAsInt(T t, U u) | 类似于BiFunction,只不过返回值为int类型 | |
| IntToLongFunction | long applyAsLong(int value) | 类似于Function,只不过输入和返回值类型分别为int、long |
关于上述各函数使用的详细介绍,首推去看源码(也可参阅:Java8新特性指南之函数式接口),这些函数式接口结合Java 8中引入的Stream一起使用能够可以很大程度提高开发者的编码效率。
除了上述几种函数式接口外,还提供了int、long、double 的primitive specializations,如IntSupplier、LongBinaryOperator、LongToDoubleFunction等,其他primitive types的可以转换为这三个primitive types的。详情可参阅 java.util.function 包下的源码。
Lambda表达式和匿名内部类的区别:
匿名内部类编译后编译器会为匿名类也单独生成class文件,lambda表达式则不会。=> lambda表达式中的this的意义跟在表达式外的一样。示例:
1 public class Hello { 2 Runnable r1 = () -> { System.out.println(this); } 3 Runnable r2 = () -> { System.out.println(toString()); } 4 5 public String toString() { return "Hello, world!"; } 6 7 public static void main(String... args) { 8 new Hello().r1.run(); 9 new Hello().r2.run(); 10 } 11 } 12 //结果:输出两个“Hello, world!” 13 //若r1、r2用匿名内部类,则输出的两个结果不同,类似于:Hello$1@5b89a773 and Hello$2@537a7706