1,免责声明,本文大部分内容摘自《Java8函数式编程》。在这本书的基础上,根据自己的理解和网上一些博文,精简或者修改。本次分享的内容,只用于技术分享,不作为任何商业用途。当然这本书是非常值得一读,强烈建议买一本!
2,本次分享的样例代码均上传到github上,请点击这里。
主要内容如下:
- 2.1 第一个Lambda表达式
- 2.2 如何识别Lambda表达式
- 2.3 引用的是值,而不是变量
- 2.4 函数接口
- 2.5 类型推导
- 2.6 要点回顾
Android Button 点击监听器语法如下:
// 例2-1 使用匿名内部类将行为和按钮单击进行关联 button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Log.v("View", "button clicked!"); } });
在这个例子中,我们创建一个新对象,它实现了View.OnClickListener
接口。这个接口只有一个方法onClick()
,当用户点击屏幕上的button,button就会被调用这个方法。匿名内部类实现了该方法,这实际上是一个代码即数据的例子 ―― 我们给button传递了一个代表某种行为的对象。
设计匿名内部类的目的,就是为了方便Java程序员将代码作为数据传递。不过,匿名内部类还是不够简便。为了调用一行重要的逻辑代码,不得不加上4行冗繁的样板代码。
尽管如此,样板代码并不是唯一的问题:这些代码还相当难读,因为它没有清楚地表达程序员的意图。我们不想传入对象,只想传入行为。在Java8中,上述代码可以写成一个Lambda表达式,如下:
// 例2-2 使用 Lambda 表达式将行为和按钮单击进行关联 button.setOnClickListener(v -> Log.v("View", "button clicked!"));
和传入一个实现某种接口的对象不同,我们传入了一段代码块――一个没有名字的函数。v
是参数名,和上面匿名内部类示例中的是同一个参数。->
将参数和Lambda表达式的主体分开,而主体是用户点击按钮时会运行的一些代码。
和使用匿名内部类另一处不同在于声明view参数的方式。使用匿名内部类时需要显示地声明参数类型View
,而在Lambda表达式中无需指定类型,程序依然可以编译。这是因为javac根据程序的上下文(setOnClickListener
方法的签名)在后台推断出了参数View的类型。这意味着如果参数类型不言明,则无需显示指定。为了增加可读性并能够迁就我们的习惯,声明参数也可以包含类型信息,因为有时候编译器不一定能根据上下文推断出参数类型。
Lambda表达式除了基本的形式之外,还有几种变体:
- (params) -> expression
- (params) -> statement
- (params) -> { statements }
具体例子如下:
// 例2-3 编写 Lambda 表达式的不同形式 // 方式一,不包含参数,使用空括号()表示没有参数。 Runnable noArguments = () -> System.out.println("Hello World!"); // 方式二,包含且只包含一个参数,可省略参数的括号。 View.OnClickListener clickListener = view -> System.out.println("Button Click!"); // 方式三,Lambda 表达式可以包含多个参数的方法。 BinaryOperator<Long> add = (x, y) -> x + y; // 方式四,与方式三类似,只不过指定了参数类型,如Long。 BinaryOperator<Long> add = (Long x, Long y) -> x + y; // 方式五,Lambda 表达式的主体不仅可以是一个表达式,而且也可以是一段代码块,使用大括号{}将代码块括起来。 BinaryOperator<Long> add2 = (x, y) -> { System.out.println("Hello BinaryOperator!"); return x + y; };
方式一,Lambda表达式不包含参数,使用空括号()表示没有参数。此时小括号不可以省略。该Lambda表达式实现了Runnable接口,该接口也只有一个run方法,没有参数,且放回类型为void。
方式二,Lambda表达式包含且只包含一个参数,可省略参数的小括号。
方式三,Lambda表达式可以包含多个参数的方法,此时需要小括号将参数括起来。这时就有必要考虑怎样去阅读该Lambda表达式。这行代码并不是将两个数字相加,而是创建了一个函数,用来计算两个数字相加的结果。变量add的类型BinaryOperator,它不是两个数字的和,而是将两个数字相加的那行代码。
方式四,Lambda表达式参数类型由编译器推断出来,这当然不错,但有时建议显示声明参数类型。
方式五,Lambda表达式的主体不仅可以是一个表达式,而且也可以是一段代码块,使用大括号({})将代码块括起来。该代码块和普通方法遵循的规则一模一样,可以用返回或抛出异常退出。只有一行代码的Lambda表达式也可使用大括号,用以证明Lambda表达式从何时开始、到哪里结束。
目标类型:是指Lambda表达式所在上下文环境的类型。比如:将Lambda表达式赋值给一个局部变量,或传递给一个方法作为参数,局部变量或方法参数的类型就是Lambda表达式的目标类型。下面的 2.4章节会具体介绍
Lambda表达式的类型依赖于上下文环境,是由编译器推断出来的。 目标类型也不是一个全新的概念。如例 2-4 所示,Java中初始化数组,数据类型就是根据上下文推断出来的。或者null,只有将null赋值给一个变量,才能知道它的类型。如例2-4:
// 例 2-4 等号右边的代码并没有声明类型,系统根据上下文推断出类型 final String[] array = {"hello", "world"}; String str = null;
在Java匿名内部类的方法里使用外部的变量。这时,需要将变量声明为final(JDK8 之前是强制的)。将变量声明为final,意味着不能为其重复赋值,即:该变量是一个特定的值。
// 例2-5 匿名内部类中使用 final 局部变量 final String world = "Hello world!"; button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Log.v("View", "button clicked! say " + world); }