java8 Lambda介绍

Java8 @FunctionalInterface

java8 的java.util.function包中函数式接口

java8 Lambda介绍

一. 为什么需要lambda

二. lambda 语法

三、变量作用域

四、方法引用

五、函数式接口  

5.1、函数式接口介绍

5.2、函数式接口的使用

5.3、函数式编程

六. lambda 配合 集合的使用

 

一、为什么需要 lambda

        Java是一种面向对象的编程方式。而 lambda 属于一种 函数式编程。Java 为什么要引入?
        1. 结合函数式接口,可以消除很多重复性代码。例如:
   public static void test1() {
        String name = "";
        String name1 = "12345";
        System.out.println(validInput(name, inputStr -> inputStr.isEmpty() ? "名字不能为空" : inputStr));
        System.out.println(validInput(name1, inputStr -> inputStr.length() > 3 ? "名字过长" : inputStr));
    }

    public static String validInput(String name, Function<String, String> function) {
        return function.apply(name);
    }
 
        上述代码使用了lambda 表达式,如果不用这种方式,需要写比较多的 if 语句。
        再比如,lambda 表达式可以代替匿名内部类。
        
        2. 结合 集合的流式操作可以充分利用 CPU,利用现代多核的特性,提升效率。例如:
// 统计年龄在25-35岁的男女人数、比例    
public void boysAndGirls(List<Person> persons) {    
    Map<Integer, Integer> result = persons.parallelStream().filter(p -> p.getAge()>=25 && p.getAge()<=35).    
        collect(    
            Collectors.groupingBy(p->p.getSex(), Collectors.summingInt(p->1))    
    );    
    System.out.print("boysAndGirls result is " + result);    
    System.out.println(", ratio (male : female) is " + (float)result.get(Person.MALE)/result.get(Person.FEMALE));    
}    
 如上的 parallelStream 方法,即获取并行流,以达到充分利用多核的特性。

二、lambda 语法

Lambda 表达式,也可称为闭包,它是推动 Java 8 发布的最重要新特性。

Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中)。

使用 Lambda 表达式可以使代码变的更加简洁紧凑。

语法

lambda 表达式的语法格式如下:由三部分组成: 参数列表箭头( ->)表达式或者语句块,如下:

箭头操作符将 Lambda 表达式拆分成两部分:
左侧:Lambda 表达式的参数列表
右侧:Lambda 表达式中所需执行的功能, 即 Lambda 体

(参数) -> {表达式;}
(parameters) -> expression 或 (parameters) ->{ statements; }

以下是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)
解释:
  1. 参数类型省略–绝大多数情况,编译器都可以从上下文环境中推断出lambda表达式的参数类型。例如上面的第二条 x 和第三条 (x,y) 都没声明类型。
  2. 当lambda表达式的参数个数只有一个,可以省略小括号。 如上面 第二条的 x
  3. 当lambda表达式只包含一条语句时,可以省略大括号、return和语句结尾的分号。如上面的返回都没有 return 这东西。
  4. lambda 表达式 可以访问外部变量(外部变量不可变)。

示例2:

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

运行结果:

10 + 5 = 15
10 - 5 = 5
10 x 5 = 50
10 / 5 = 2
Hello Runoob
Hello Google

使用 Lambda 表达式需要注意以下两点:

  • Lambda 表达式主要用来定义行内执行的方法类型接口,例如,一个简单方法接口。在上面例子中,我们使用各种类型的Lambda表达式来定义MathOperation接口的方法。然后我们定义了sayMessage的执行。
  • Lambda 表达式免去了使用匿名方法的麻烦,并且给予Java简单但是强大的函数化的编程能力

三、变量作用域

lambda 表达式只能引用标记了 final 的外层局部变量,这就是说不能在 lambda 内部修改定义在域外的局部变量,否则会编译错误 。可以类似内部类和外部变量的关系。

示例5-1:在 lambda 表达式中访问外层的类的成员变量,通过new一个实例,通过实例引用成员变量的方式可以。

public class Java8Tester3 {

    //Java8Tester3的成员变量
    String salutation = "Hello! ";

    public static void main(String args[]) {
        Java8Tester3 jt = new Java8Tester3();
        GreetingService greetService1 = message -> System.out.println(jt.salutation + message);
        greetService1.sayMessage("Runoob");
    }

    interface GreetingService {
        void sayMessage(String message);
    }

}
 运行结果:
Hello! Runoob

示例5-2: lambda 表达式也可以引用标记了 final 的外层局部变量

public class Java8Tester3 {

    //Java8Tester3的成员变量,加final
    final String salutation = "Hello! ";

    public static void main(String args[]) {
        Java8Tester3 jt = new Java8Tester3();
        jt.test();
    }
    
    public void test() {
        GreetingService greetService1 = message -> System.out.println(salutation + message);
        greetService1.sayMessage("Runoob");
    }

    interface GreetingService {
        void sayMessage(String message);
    }

}

示例5-3: 我们也可以直接在 lambda 表达式中访问外层的局部变量:

public class Java8Tester4 {

    public static void main(String args[]) {
        //局部变量
        int num = 1;
        Converter<Integer, String> s = (param) -> System.out.println(param + num);
        s.convert(2); // 输出结果为 3
    }

    public interface Converter<T1, T2> {
        void convert(int i);
    }

}

 运行结果:3

示例5-4: 尝试在lambda表达式里修改外部变量的值,编译失败会报错“Local variable flag defined in an enclosing scope must be final or effectively final”

 

匿名内部类和局部内部类只能引用外部的fianl变量,因为局部变量在初始化后,又对这个变量进行了赋值。赋值后会认为这个变量不是final了,所以报错。把变量变成fianl即可不报错。

但是这样的话我们就不能实现关于最大年龄在排序时做的统计,解决方法很简单,将上面的maxAge换成数组类型存储最大年龄即可。

lambda表达式也有类似问题,其可以访问给它传递的变量,访问自己内部定义的变量,同时也能访问它外部的变量。但lambda表达式访问外部变量有一个非常重要的限制:变量不可变(只是引用不可变,而不是真正的不可变)。

四、方法引用

  • 方法引用通过方法的名字来指向一个方法。
  • 方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
  • 方法引用使用一对冒号 :: 

五、函数式接口

 5.1、函数式接口介绍

有且只有一个抽象方法的接口,称之为函数式接口。当然接口中可以有其他方法(默认,静态,私有)

@FunctionInterface注解:可以检测接口是否是函数式接口
是:编译成功, 否:编译失败

5.2、函数式接口的使用

一般可以作为方法的参数和返回值类型

例子:
1.定义一个函数式接口MyFunctionInterface中定义一个没有返回值的方法public void method();
2.定义一个接口的实现类MyFunctionInterFaceImpl实现函数式接口
3.定义一个测试类Demo中有一个静态方法show参数是这个函数式接口,调用接口中的方法public static void show(MyFunctionInterface myface){myface.method();}
4.在main方法中调用show方法的3中方式
a.调用show方法,参数是接口,所以可以传接口的实现类
show(new MyFunctionInterfaceImpl());
b.方法参数是一个接口,所以可以传递接口的匿名内部类
show(new MyFunctionInterface(){
@override
public void method(){System.out.print(“使用匿名内部类重写接口中的抽象方法”);}
});
c.调用show方法,方法参数是一个函数式接口所以可以使用Lambda表达式
show(()->{System.out.print(“使用lambda重写接口中的抽象方法”)});

5.3、函数式编程

5.3.1、函数式编程优点

  1. 函数式编程:使用Lambda表达式和方法引用简化程序
  2. Lambda表达式的延迟执行:有些场景的代码执行后,结果不一定会被使用,从而造成性能的浪费。而Lambda是延迟执行的,这正好可以作为解决方法,提升性能。
  3. 函数式接口作为方法的参数和返回值
    存在函数式接口就可以使用Lambda表达式。即可以使用Lambda进行传参。

使用Lambda优化日志案例:

1.Lambda的特点:延迟加载 lambda使用的前提:必须存在函数式接口(定义拼接字符串的接口)  
2.使用Lambda表达式作为参数传递,仅仅是把参数传递showLogger方法中,只有满足条件(只有满足其条件,才会执行,否则不会执行。因此,不会存在性能浪费。),日志的等级是1级才会调用接口MessageBuilder中的方法builderMessage才会进行拼接字符串。 
示例5.3:
定义一个函数式接口,即可以适用于lambda表达式的接口:
@FunctionalInterface
public interface Message {
    public abstract String messageBuilder();
}
定义一个类,在类中定义一个有两个参数的方法,其中一个是接口的对象,如下:
public class Test01 {
 
    public static void showMsg(int level, Message msg){
        if(level == 1){
            System.out.println(msg.messageBuilder());
        }
    }
 
    public static void main(String[] args) {
 
        String msg1 = "Hello ";
        String msg2 = "Wrold!!! ";
 
        showMsg(1,()->{
            System.out.println("拼接字符串");
            return msg1+msg2;
        });
    }
}
只有当level=1满足条件时,才会执行lambda表达式的内容,拼接字符串;否则就没有必要进行拼接。

5.3.2、常见的函数式接口(java.util.function包中)

Supplier接口
Java.util.function.Supplier<T> 接口中仅包含一个无参的方法:T get()。 用来获取一个泛型参数指定类型的接口数据。

由于是一个函数式接口,这就意味着对应的Lambda表达式对外提供一个符合泛型类型的对象数据。 Supplier<T>接口被称为生产型接口,指定接口的泛型是什么类型,那么接口中的get方法就会生产什么类型的数据。


Consumer接口
Java.util.function.Consumer<T>接口正好与Supplier接口相反,他不是生产一个数据,而是消费一个数据,其数据类型由泛型决定。 Consumer中包含抽象方法void accept(T t),意为消费一个指定泛型的数据。 Consumer接口是一个消费型接口,泛型执行什么类型,就可以使用accept方法消费什么类型的数据
.foreach(Consumer t)
Consumer中的默认方法andThen:组合消费


Predicate接口 (判断
Java.util.function.Predicate: Predicate接口对某种数据类型进行判断,从而得到一个boolean值结果。 Predicate接口中的抽象方法:boolean Test(T t)用来对指定类型的数据进行判断方法 结果:符合条件:返回true 不符合条件:返回flase 默认方法:and or !

Function接口
Java.util.function.Function<T,R>接口用来根据一个类型的数据得到另一个类型的数据,前者称为前置条件,后者称为后置条件。 抽象方法:R apply(T t) 根据T的类型参数获取R类型的结果
.map(Function<Object, Object> f)

5.3.3、函数式接口与lambda结合使用示例

如下,配合 Function,Consumer,Predicate 接口的使用,减少了代码的冗余:
package com.dxz.jdk;

import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;

public class lambdaDemo {
    public static void main(String[] args) throws InterruptedException {
        System.out.println("functionTest--------");
        functionTest();
        System.out.println("consumerTest--------");
        consumerTest();
        System.out.println("predicateTest--------");
        predicateTest();
    }

    // 第二个参数是Function接口
    public static String validInput(String name, Function<String, String> function) {
        return function.apply(name);
    }

    /**
     * Function接口的使用示例
     */
    public static void functionTest() {
        String name = "";
        String name1 = "12345";
        System.out.println(validInput(name, inputStr -> inputStr.isEmpty() ? "名字不能为空" : inputStr));
        System.out.println(validInput(name1, inputStr -> inputStr.length() > 3 ? "名字过长" : inputStr));
    }

    // 第二个参数是Consumer接口
    public static void validInput2(String name, Consumer<String> function) {
        function.accept(name);
    }

    /**
     * Consumer接口的使用示例
     */
    public static void consumerTest() {
        String name = "";
        String name1 = "12345";

        validInput2(name, inputStr -> System.out.println(inputStr.isEmpty() ? "名字不能为空" : "名字正常"));
        validInput2(name1, inputStr -> System.out.println(inputStr.isEmpty() ? "名字不能为空" : "名字正常"));

    }

    // 第二个参数是Predicate接口
    public static boolean validInput3(String name, Predicate<String> function) {
        return function.test(name);
    }

    /**
     * Predicate接口使用示例
     */
    public static void predicateTest() {
        String name = "";
        String name1 = "12";
        String name2 = "12345";

        System.out.println(validInput3(name, inputStr -> !inputStr.isEmpty() && inputStr.length() <= 3));
        System.out.println(validInput3(name1, inputStr -> !inputStr.isEmpty() && inputStr.length() <= 3));
        System.out.println(validInput3(name2, inputStr -> !inputStr.isEmpty() && inputStr.length() <= 3));

    }

    // 给出一个String类型的数组,求其中所有不重复素数的和
    public void distinctPrimarySum(String... numbers) {
        List<String> l = Arrays.asList(numbers);
        int sum = l.stream()
                .map(e -> new Integer(e))
                .filter(e -> Primes.isPrime(e))
                .distinct()
                .reduce(0,(x, y) -> x + y); // equivalent to .sum()
        System.out.println("distinctPrimarySum result is: " + sum);
    }

}

 运行结果:

functionTest--------
名字不能为空
名字过长
consumerTest--------
名字不能为空
名字正常
predicateTest--------
false
true
false

六、lambda 配合 集合的使用

    // 给出一个String类型的数组,求其中所有不重复素数的和
    public void distinctPrimarySum(String... numbers) {
        List<String> l = Arrays.asList(numbers);
        int sum = l.stream()
                .map(e -> new Integer(e))
                .filter(e -> Primes.isPrime(e))
                .distinct()
                .reduce(0,(x, y) -> x + y); // equivalent to .sum()
        System.out.println("distinctPrimarySum result is: " + sum);
    }
        Ps: lambda 配合 集合的使用应该是 JDK8 最重要的特性,既能简便了代码,也充分利用了现代多核的特性,各方面都提升了效率。
总结:
  1. 引入lambda:代码更简单,配合其他使用效率更高。
  2. lambda语法:基本组成由 参数列表,箭头,表达式组成。如果参数列表和表达式简单,还可以把括号什么的进一步省略。
  3. Lambda的使用:一般配合 Function,Consumer,Predicate或者自定义的函数式接口使用,使得代码更加方便;同时也配合集合使用,提升效率,提高阅读性。
 
参考:
1. Java8  为什么需要Lambda 表达式:http://developer.51cto.com/art/201304/387681.htm
2. Java8 初体验(一) lambda 表达式语法:http://ifeve.com/lambda/
3、https://blog.csdn.net/weixin_37963567/article/details/107706273
4、https://www.cnblogs.com/AganRun/p/11816069.html
原文地址:https://www.cnblogs.com/duanxz/p/2588825.html