JDK8新特性【Lambda表达式和函数式接口】

一、Lambda表达式 Lambda表达式(也称为闭包),lambda表达式本质上是一个匿名方法。它允许我们将函数当成参数传递给某个方法,或者把代码本身当作数据处理。

函数式开发者非常熟悉这些概念。很多JVM平台上的语言(Groovy、Scala等)从诞生之日就支持Lambda表达式,但是Java开发者没有选择,只能使用匿名内部类代替Lambda表达式。
1. 语法 lambda 表达式的语法格式如下:
(parameters) -> expression


(parameters) ->{ statements; }

在最简单的形式中,一个lambda可以由:用逗号分隔的参数列表–>符号函数体三部分表示
Arrays.asList( "a", "b", "d" ).forEach( e -> System.out.println( e ) );

如果Lambda表达式需要更复杂的语句块,则可以使用花括号将该语句块括起来,类似于Java中的函数体
Arrays.asList( "a", "b", "d" ).forEach( e -> { System.out.print( e ); System.out.print( e ); } );

2. 以下是lambda表达式的重要特征:
  • 可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
  • 可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
  • 可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
  • 可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值。
3. Lambda 表达式的简单例子:
// 1. 不需要参数,返回值为 5 () -> 5// 2. 接收一个参数(数字类型),返回其2倍的值 x -> 2 * x// 3. 接受2个参数(数字),并返回他们的差值 (x, y) -> x – y// 4. 接收2个int型整数,返回他们的和 (int x, int y) -> x + y// 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void) (String s) -> System.out.print(s)

4. demo 使用 Lambda 表达式需要注意以下两点:
Lambda 表达式主要用来定义行内执行的方法类型接口,例如,一个简单方法接口。在下面例子中,我们使用各种类型的Lambda表达式来定义MathOperation接口的方法。然后我们定义了sayMessage的执行。
Lambda 表达式免去了使用匿名方法的麻烦,并且给予Java简单但是强大的函数化的编程能力。
public class Java8Tester { public static void main(String args[]){ Java8Tester tester = new Java8Tester(); // 类型声明 MathOperation addition = (int a, int b) -> a + b; // 不用类型声明 MathOperation subtraction = (a, b) -> a - b; // 大括号中的返回语句 MathOperation multiplication = (int a, int b) -> { return a * b; }; // 没有大括号及返回语句 MathOperation division = (int a, int b) -> a / b; System.out.println("10 + 5 = " + tester.operate(10, 5, addition)); System.out.println("10 - 5 = " + tester.operate(10, 5, subtraction)); System.out.println("10 x 5 = " + tester.operate(10, 5, multiplication)); System.out.println("10 / 5 = " + tester.operate(10, 5, division)); // 不用括号 GreetingService greetService1 = message -> System.out.println("Hello " + message); // 用括号 GreetingService greetService2 = (message) -> System.out.println("Hello " + message); greetService1.sayMessage("Runoob"); greetService2.sayMessage("Google"); }interface MathOperation { int operation(int a, int b); }interface GreetingService { void sayMessage(String message); }private int operate(int a, int b, MathOperation mathOperation){ return mathOperation.operation(a, b); } }

执行以上脚本,输出结果为:
$ javac Java8Tester.java $ java Java8Tester 10 + 5 = 15 10 - 5 = 5 10 x 5 = 50 10 / 5 = 2 Hello Runoob Hello Google

5. 变量作用域
接口变量必须是 publicstaticfinal 修饰的
lambda 表达式只能引用标记了 final 的外层局部变量,这就是说不能在 lambda 内部修改定义在域外的局部变量,否则会编译错误。
在 Java8Tester.java 文件输入以下代码:
public class Java8Tester { final static String salutation = "Hello! "; public static void main(String args[]){ GreetingService greetService1 = message -> System.out.println(salutation + message); greetService1.sayMessage("Runoob"); }interface GreetingService { void sayMessage(String message); } }

执行以上脚本,输出结果为:
$ javac Java8Tester.java $ java Java8Tester Hello! Runoob

我们也可以直接在 lambda 表达式中访问外层的局部变量:
public class Java8Tester { public static void main(String args[]) { final int num = 1; Converter s = (param) -> System.out.println(String.valueOf(param + num)); s.convert(2); // 输出结果为 3 } public interface Converter { void convert(int i); } }

lambda 表达式的局部变量可以不用声明为 final,但是必须不可被后面的代码修改(即隐性的具有 final 的语义)
int num = 1; Converter s = (param) -> System.out.println(String.valueOf(param + num)); s.convert(2); num = 5; //报错信息:Local variable num defined in an enclosing scope must be final or effectivelyfinal

在 Lambda 表达式当中不允许声明一个与局部变量同名的参数或者局部变量。
String first = ""; Comparator comparator = (first, second) -> Integer.compare(first.length(), second.length()); //编译会出错

二、#函数式接口
【JDK8新特性【Lambda表达式和函数式接口】】Lambda的设计者们为了让现有的功能与Lambda表达式良好兼容,考虑了很多方法,于是产生了函数接口这个概念。函数接口指的是只有一个函数的接口,这样的接口可以隐式转换为Lambda表达式。
使用lambda表达式,新建一个类的时候,必须满足以下三点:
  • 新建的类必须是接口,此接口称为函数式接口
  • 此接口只能有一个非抽象类接口,但是可以使用default参数添加扩展方法
  • 忽略object类方法
1. 什么是函数式接口 所谓的函数式接口,当然首先是一个接口,然后就是在这个接口里面只能有一个抽象方法。
这种类型的接口也称为SAM接口,即Single Abstract Method interfaces
2. 测试
  • 准备一个接口
public interface MyTest { void test1(); }

  • 使用lambda表达式实现该接口
public static void main(String[] args) { MyTest myTest = () -> System.out.println("hello world!"); myTest.test1(); }

得到输出为:hello world!
3. 如果接口类里面有多个方法行不行呢?--- 可以 默认方法和静态方法不会破坏函数式接口的定义
  • 在接口中加入一个方法
public interface MyTest { void test1(); void test2(); }

会看到报如下错误
Multiple non-overriding abstract methods found in interface--在接口中找到多个非重写抽象方法
找到重点词多个抽象方法,那就是需要将抽象方法减少喽,使用default关键字,改造代码如下:
public interface MyTest { void test1(); default void test2() { System.out.println("Bye bye world!"); } }

再次调用
public static void main(String[] args) { MyTest myTest = () -> System.out.println("hello world!"); myTest.test1(); myTest.test2(); }

out:
hello world! Bye bye world!

4. 如何声明一个接口为函数式接口 在接口上使用@FunctionalInterface,这样会在接口不符合函数式接口定义规范的时候就报错。
@FunctionalInterface public interface Functional { void method(); }

    推荐阅读