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 effectively  final

在 Lambda 表达式当中不允许声明一个与局部变量同名的参数或者局部变量。

String first = "";  
Comparator comparator = (first, second) -> Integer.compare(first.length(), second.length());  //编译会出错 



二、#函数式接口

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();
}

你可能感兴趣的:(JDK8新特性【Lambda表达式和函数式接口】)