高阶函数这里指的可不是数学里的那个,这里主要要从维度这个概念理解,本来函数生成的是值,也就是说,函数比值高维,那么如果我们有一个函数能生成函数或者是以函数为参数,那么显然就比普通的生成值的函数更高维,因为我可以生成你。
定义:高阶函数是一个能接受函数作为参数或能够把函数作为返回值的函数。
public interface Function{ String str(String s); } public class Procudure{ // 下面就是一个标准的高阶函数 public Function(String s){ return s -> s.upperCase(); } }
这里有两点:
java.util.function
中的接口,或是自定义一个函数式接口来为专门的接口创建别名但是这只是基本的,还记得函数式编程的意义吗?这里的关键在于,有时候,我们可以根据接受的函数,让高阶函数生成一个新的函数。
public class test { public static Function<String, String> transform(Function<String, String> f){ return f.andThen(String::toUpperCase); } public static void main(String[] args) { Function<String, String> transform = test2.transform(str -> { return str.substring(0, 2); }); String s = transform.apply("abcdefg"); System.out.println(s); } }
可以看到,这里我们通过andThen()
这个Function
接口的通用方法,连接了前后两个方法,并且使得无论我们输入了什么,都会将该字符串转化为全大写,后面我们输入了一个截取前两个字符作为返回值的方法,但很明显,这里可以有更多的选择,并且我们实际上也可以通过方法引用来引用某些定义好的函数,非常灵活。
什么是闭包?
考虑一个lambda表达式,它使用了其函数作用域之外的变量。当返回该函数时会发生什么呢?也即,当我们通过调用lambda表达式产生的匿名方法引用这些外部变量会发生什么呢?
如果一门语言能够解决这个问题,我们就认为该语言是支持闭包的,或者也可以说它支持词法作用域。
这里还涉及到一个术语:变量捕获
上面听起来是不是不明白,没关系,给个例子:
public class Example{ IntSupplier plus(int x){ int y = 1; return () -> x + y; } }
考虑这个类和其中的方法plus(int x)
,你会不会发现有一些问题。
因为我们的plus(int x)
方法返回的是一个函数,这里假设返回的函数是f(int x)
,也就是说,f(int x)
返回时,plus(int x)
已经执行结束,所以其中的变量int y = 1;
已经脱离了作用域,那么等到我们获取了f(int x)
的对象再调用到f(int x)
方法时,这个y
要怎么办呢?
你会发现,上面的这个方法是可以被编译执行成功的,但是下面的这个就不行:
public class Example{ IntSupplier plus(int x){ int y = 1; return () -> x + (++y); } }
为什么呢?编译器提示:lambda 表达式中使用的变量应为 final 或有效 final
这句话就说的很明白了,对于第一个例子,我们的y
虽然没final
关键字,但它是事实上的final
变量,一旦这里赋值就不会再改动,而对于第二个方法来说则相当于把y
赋予了新的值。
这里如果我们使用的是引用,比如下面这个例子
public class Example{ IntSupplier plus(int x){ Queue<Integer> y = new LinkedList<>(); y.offer(1); return () -> x + y.poll(); } }
注意,这里是可以通过编译的,因为实际上我们只需要保证这个引用所指向的对象不被修改,避免后面调用返回的函数时却突然发现找不到对应的对象即可。
所以,Java提供的闭包的条件是,我们必须要能够保证,被捕获的变量是final
的。