简述函数式编程
函数式编程是一种编程方式,符合一定的范式(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;
   |