简述函数式编程
函数式编程是一种编程方式,符合一定的范式(Paradigm)
特性:
- 函数是一等公民,可以直接传递。输入和输出都可以是一个函数。
- 输入到输出的计算过程,是一个表达式,因此每个函数要有返回值。
- 不影响外部,没有副作用。一个函数可以看成一个沙盒,不会修改外部变量。
- 不改变内部状态。不去修改变量,只返回新值。因此只取决于输入。同输入必定同输出。
- 惰性计算。不是绑定时立即计算,而是需要时再计算。无穷数列处理,消耗一个再取下一个。
好处:
1.简洁:代码行数减少。
2.易读:函数可链式组合,接近自然语言。
3.独立无状态:容易测试和debug.
4.并发:不修改就不需要加锁,天生适合并发。
Java8的函数式编程支持
- 经典的应用场景:链表排序
要自定义排序方式需要自己实现Comparator。虽然可以用匿名类免去了显式定义一个类的麻烦,但是依然有点繁琐。
可以发现我们实现一个类就是为了一个类中的方法,这个方法接受两个字符串并返回一个整型值。说到底真正关心的只有这个方法而已。
1 2 3 4 5 6 7
| List<String> names = Arrays.asList("foo","bar","baz"); Collections.sort(names,new Comparator<String>() { @Override public int compare(String a,String b) { return b.compareTo(a); } });
|
Java8的新特性可以用lambda表达式进行代码简化
符号->
的左边是输入,可以看作传参,右边是操作,可以看作函数体
1
| Collections.sort(names,(String a,String b) -> {return b.compareTo(a);});
|
输入类型可以进行推断,单行的简单逻辑可以省略函数体,进一步简化为
1
| Collections.sort(names,(a,b) -> b.compareTo(a));
|
实现机制:函数式接口
查看Collections.sort的实现,发现并没有任何改变,就是传入了一个Comparator接口的实现而已
1 2 3
| public static <T> void sort(List<T> list, Comparator<? super T> c) { list.sort(c); }
|
真正的玄机在Comparator这个接口,已经经过了改造
就compare方法本身而言没什么改变,但是整个接口多了一个标注FunctionalInterface,成为了函数式接口
函数式接口要求只有一个要实现的方法,简单的说就是只有一个方法的接口,compare就是这个方法。
然而Comparator接口还有其他方法存在。equals方法构成了对Object类的重载,这种不算。
此外还新增了default方法,这类方法可以具有方法体,类似抽象类中的非抽象方法,实现类可以直接使用。
1 2 3 4 5 6 7 8 9
| @FunctionalInterface public interface Comparator<T> { int compare(T o1, T o2); boolean equals(Object obj); default Comparator<T> reversed() { return Collections.reverseOrder(this); } }
|
成为函数式接口的好处是可以接受表达式实现,看上去像变量一样进行赋值
这就解释了为何sort函数没改变就可以支持新语法,因为它拿到的依然是Comparator接口的实现。
1 2
| Comparator<String> comparator = (a,b) -> b.compareTo(a); Collections.sort(names,comparator);
|
sort方法实际上是策略模式
,可以通过Comparator来定制比较行为。 lambda表达式简化了策略模式的实现。
方法引用
函数式编程中函数是一等公民,因此也可以像变量一样引用方法,然而Java并不能直接引用一个方法,因此解决方案是把方法包装成实现类
方法引用的关键是java.util.function包下的一组函数式接口
根据函数的特征使用不同函数式接口
- Function:单输入单输出
apply方法即为调用函数的方法
1 2 3 4 5
| @FunctionalInterface public interface Function<T, R> { R apply(T t); }
|
1 2 3
| @FunctionalInterface public interface BiFunction<T, U, R> { R apply(T t, U u);
|
1 2 3 4 5
| @FunctionalInterface public interface Consumer<T> { void accept(T t); }
|
1 2 3 4
| @FunctionalInterface public interface Supplier<T> { T get(); }
|
1 2 3 4
| @FunctionalInterface public interface Runnable { public abstract void run(); }
|
方法引用的符号是双冒号::
- 输入作为静态方法传值
形式:x -> Class.method(x)
1 2
| Function<Integer,String> function = String::valueOf; System.out.println(function.apply(123).getClass().getName());
|
- 输入作为已知实例方法的传值
形式: x -> obj.method(x)
1 2
| Consumer<Object> consumer = System.out::println; consumer.accept("foo");
|
- 调用输入自身方法
形式:x -> x.method()
1 2
| Function<String,Integer> function = String::length; System.out.println(function.apply("foo"));
|
1 2
| BiFunction<String,Locale,SimpleDateFormat> constructor = SimpleDateFormat::new; SimpleDateFormat sdf = constructor.apply("yyyyMMdd", Locale.CHINA);
|
1 2 3
| Function<Integer,String[]> function = String[]::new; String[] fiveStrings = function.apply(5); System.out.println(Arrays.toString(fiveStrings));
|
异常
因为函数式接口方法没有声明抛出异常,这种情况下表达式不能抛出需检查的异常
要么表达式内部try-catch处理掉,要么定义新的函数式方法允许异常抛出
1 2 3
| List<Boolean> created = Arrays.asList("foo.txt","bar.txt").stream() .map(p -> new File(p).createNewFile()) .collect(Collectors.toList());
|
内部处理掉
1 2 3 4 5 6 7 8 9 10 11 12
| public boolean createFile(String name) throws IOException { return new File(name).createNewFile(); } List<Boolean> created = Arrays.asList("foo.txt","bar.txt").stream() .map(p -> { try{ return createFile(p); }catch(IOException e){ return false; } }) .collect(Collectors.toList());
|
或者提供重新抛出非检查异常的版本
1 2 3 4 5 6 7 8 9 10
| public boolean createFile(String name) { try{ return new File(name).createNewFile(); }catch(UncheckedIOException e){ throw new RuntimeException(e); } } List<Boolean> created = Arrays.asList("foo.txt","bar.txt").stream() .map(p -> createFile(p)) .collect(Collectors.toList());
|
结合上述两种方法,建立新的函数式接口,允许抛出检查异常,并提供通用的转换逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| @FunctionalInterface public interface FunctionWithException<T, R, E extends Exception> {
R apply(T t) throws E; }
static <T, R, E extends Exception> Function<T, R> wrapper(FunctionWithException<T, R, E> fe) { return arg -> { try { return fe.apply(arg); } catch (Exception e) { throw new RuntimeException(e); } }; }
|
事实 final
按道理匿名内类访问外部变量,都需要声明final
Java8 新加了Effectively final功能,如果变量只赋值了一次,可以不显式声明final,由编译器自动加上
lambda表达式可以看成是匿名内部类的实现,也遵从此规则
1 2 3 4
| int x = 10; Arrays.asList(10, 20, 30).stream() .filter(y -> y > x) .forEach(System.out::println);
|
虽然在lambda表达式之后修改,但是修改造成了编译器没加final,报错
1 2 3 4 5
| int x = 10; Arrays.asList(10, 20, 30).stream() .filter(y -> y > x) .forEach(System.out::println); x = 20;
|