• 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
  • 相关阅读:
    第三次博客园作业
    centos7+jdk1.8+tomcat8 配置https
    输入30个数存入数组a,求出数的每个位数的平方和存入数组b,从小到大排列后输出(C语言)
    50个[100,300]的随机数,要求用二分法查找从键盘录入的关键数字。找到回复位置,找不到回复不存在(C语言)
    产生20个随机数,在[200,400]内,其中能被5整除的存入数组array2,要求输出array2中的平均值(C语言)
    最小生成树
    PTA路径判断
    PTA构造哈夫曼树
    图的其中两种表示方式
    中序遍历树并判断是否为二叉搜索树
  • 原文地址:https://www.cnblogs.com/duanxz/p/2588825.html
Copyright © 2020-2023  润新知