zoukankan      html  css  js  c++  java
  • Java 8 新增的 Lambda 表达式

    Lambda 表达式是 Java 8 的重要更新,也是一个被广大开发者期待已久的新特性。Lambda 表达式支持将代码块作为方法参数,Lambda 表达式允许使用更简洁的代码来创建只有一个抽象方法的接口(这种接口被称为函数式接口)的实例。

    一、Lambda 表达式入门

    下面先使用匿名内部类来改写前面介绍的 command 表达式的例子,改写后的程序如下。

    public interface Command {
        // 接口里定义的process()方法用于封装“处理行为”
        void process(int[] target);
    }
    
    public class ProcessArray {
        public void process(int[] target, Command cmd) {
            cmd.process(target);
        }
    }
    
    public class CommandTest {
        public static void main(String[] args) {
            ProcessArray pa = new ProcessArray();
            int[] array = { 3, -4, 6, 4 };
            // 处理数组,具体处理行为取决于匿名内部类
            pa.process(array, new Command() {
                public void process(int[] target) {
                    int sum = 0;
                    for (int tmp : target) {
                        sum += tmp;
                    }
                    System.out.println("数组元素的总和是:" + sum);
                }
            });
        }
    }

    前面已经提到,ProcessArray 类的 process() 方法处理数组时,希望可以动态传入一段代码作为具体的处理行为,因此程序创建了一个匿名内部类实例来封装处理行为。从上面代码可以看出,用于封装处理行为的关键就是实现程序中的粗体字方法。但为了向 process() 方法传入这段粗体字代码,程序不得不使用匿名内部类的语法来创建对象。

    Lambda 表达式完全可用于简化创建匿名内部类对象,因此可将上面代码改为如下形式。

    public class CommandTest2 {
        public static void main(String[] args) {
            ProcessArray pa = new ProcessArray();
            int[] array = { 3, -4, 6, 4 };
            // 处理数组,具体处理行为取决于匿名内部类
            pa.process(array, (int[] target) -> {
                int sum = 0;
                for (int tmp : target) {
                    sum += tmp;
                }
                System.out.println("数组元素的总和是:" + sum);
            });
        }
    }

    从上面程序中的粗体字代码可以看出,这段粗体字代码与创建匿名内部类时需要实现的 process(int[] target) 方法完全相同,只是不需要 new Xxx(){} 这种烦琐的代码,不需要指出重写的方法名字,也不需要给出重写的方法的返回值类型————只要给出重写的方法括号以及括号里的形参列表即可。

    从上面介绍可以看出,当使用 Lambda 表达式代替匿名内部类创建对象时,Lambda 表达式的代码块将会代替实现抽象方法的方法体,Lambda 表达式就相当一个匿名方法。

    从上面语法格式可以看出,Lambda 表达式的主要作用就是代替匿名内部类的烦琐语法。它由三部分组成。

    • 形参列表。形参列表允许省略形参类型。如果形参列表中只有一个参数,甚至连形参列表的圆括号也可以省略。
    • 箭头(->)。必须通过英文中画线和大于符号组成。
    • 代码块。如果代码块只包含一条语句,Lambda表达式允许省略代码块的花括号,那么这条语句就不要用花括号表示语句结束。Lambda 代码块只有一条 return 语句,甚至可以省略 return 关键字。Lambda 表达式需要返回值,而它的代码块中仅有一条省略了 return 的语句,Lambda表达式会自动返回这条语句的值。

    下面程序示范了 Lambda 表达式的几种简化写法。

    interface Eatable {
        void taste();
    }
    
    interface Flyable {
        void fly(String weather);
    }
    
    interface Addable {
        int add(int a, int b);
    }
    
    public class LambdaQs {
        // 调用该方法需要Eatable对象
        public void eat(Eatable e) {
            System.out.println(e);
            e.taste();
        }
    
        // 调用该方法需要Flyable对象
        public void drive(Flyable f) {
            System.out.println("我正在驾驶:" + f);
            f.fly("【碧空如洗的晴日】");
        }
    
        // 调用该方法需要Addable对象
        public void test(Addable add) {
            System.out.println("5与3的和为:" + add.add(5, 3));
        }
    
        public static void main(String[] args) {
            LambdaQs lq = new LambdaQs();
            // Lambda表达式的代码块只有一条语句,可以省略花括号。
            lq.eat(() -> System.out.println("苹果的味道不错!"));
            // Lambda表达式的形参列表只有一个形参,省略圆括号
            lq.drive(weather -> {
                System.out.println("今天天气是:" + weather);
                System.out.println("直升机飞行平稳");
            });
            // Lambda表达式的代码块只有一条语句,省略花括号
            // 代码块中只有一条语句,即使该表达式需要返回值,也可以省略return关键字。
            lq.test((a, b) -> a + b);
        }
    }

    上面程序中的第一段粗体字代码使用 Lambda 表达式相当于不带形参的匿名方法,由于该 Lambda表达式的代码块只有一行代码,因此可以省略代码块的花括号;第二段粗体字代码使用 Lambda 表达式相当于只带一个形参的匿名方法,由于该 Lambda 表达式的形参列表只有一个形参,因此省略了形参列表的圆括号;第三段粗体字代码的 Lambda 表达式的代码块中只有一行语句,这行语句的返回值将作为该代码块的返回值。

    上面程序中的第一处粗体字代码调用 eat() 方法,调用该方法需要一个 Eatable 类型的参数,但实际传入的是 Lambda 表达式;第二处粗体字代码调用 drive() 方法,调用该方法需要一个 Flyable 类型的参数,但实际传入的是 Lambda 表达式:第三处粗体字代码调用 test() 方法,调用该方法需要一个 Addable 类型的参数,但实际传入的是 Lambda 表达式。但上面程序可以正常编译、运行,这说明 Lambda 表达式实际上将会被当成一个“任意类型”的对象,到底需要当成何种类型的对象,这取决于运行环境的需要。下面将详细介绍Lambda表达式被当成何种对象。

    二、Lambda 表达式与函数式接口

    Lambda 表达式的类型,也被称为“目标类型(target type)”,Lambda 表达式的目标类型必须是“函数式接口(functional interface)”。函数式接口代表只包含一个抽象方法的接口。函数式接口可以包含多个默认方法、类方法,但只能声明一个抽象方法。

    如果采用匿名内部类语法来创建函数式接口的实例,则只需要实现一个抽象方法,在这种情况下即可采用 Lambda 表达式来创建对象,该表达式创建出来的对象的目标类型就是这个函数式接口。查询 Java 8 的 API 文档,可以发现大量的函数式接口,例如:Runnable、ActionListener 等接口都是函数式接口。

    提示:Java 8 专门为函数式接口提供了 @FunctionalInterface 注解,该注解通常放在接口定义前面,该注解对程序功能没有任何作用,它用于告诉编译器执行更严格检查一一检查该接口必须是函数式接口,否则编泽器就会报错

    由于 Lambda 表达式的结果就是被当成对象,因此程序中完全可以使用 Lambda 表达式进行赋值,例如如下代码。

    // Runnable接口中只包含一个无参数的方法
    // Lambda表达式代表的匿名方法实现了Runnable接口中唯一的、无参数的方法
    // 因此下面的Lambda表达式创建了一个Runnable对象
    Runnable r = () -> {
      for(int i = 0 ; i < 100 ; i ++){
        System.out.println();
      }
    };

    提示:Runnable 是 Java 本身提供的一个函数式接口。

    从上面粗体字代码可以看出,Lambda 表达式实现的是匿名方法一一因此它只能实现特定函数式接口中的唯一方法。这意味着 Lambda 表达式有如下两个限制。

    • Lambda 表达式的目标类型必须是明确的函数式接囗。
    • Lambda 表达式只能为函数式接囗创建对象。Lambda 表达式只能实现一个方法,因此它只能为只有一个抽象方法的接口(函数式接口)创建对象。

    关于上面第一点限制,看下面代码是否正确。

    Object obj = () -> {
        for(int i = 0 ; i < 100 ; i ++){
            System.out.println();
        }
    };

    上面代码与前一段代码几乎完全相同,只是此时程序将 Lambda 表达式不再赋值给 Runnable 变量,而是直接赋值给 Object 变量.编译上面代码,会报如下错误:不兼容的类型:Object 不是函数接口

    从该错误信息可以看出,Lambda 表达式的目标类型必须是明确的函数式接口。上面代码将Lambda表达式赋值给 Object 变量,编译器只能确定该 Lambda 表达式的类型为 Object,而 Object 并不是函数式接口,因此上面代码报错。

    为了保证 Lambda 表达式的目标类型是一个明确的函数式接口,可以有如下三种常见方式。

    • 将 Lambda 表达式赋值给函数式接口类型的变量。
    • 将 Lambda 表达式作为函数式接口类型的参数传给某个方法。
    • 使用函数式接口对 Lambda 表达式进行强制类型转换。

    因此,只要将上面代码改为如下形式即可。

    Object obj1 = (Runnable)() -> {
        for(int i = 0 ; i < 100 ; i ++){
            System.out.println();
        }
    };

    上面代码中的粗体字代码对 Lambda 表达式执行了强制类型转换,这样就可以确定该表达式的目标类型为 Runnable 函数式接口。

    需要说明的是,同样的 Lambda 表达式的目标类型完全可能是变化的一一一唯一的要求是,Lambda表达式实现的匿名方法与目标类型(函数式接口)中唯一的抽象方法有相同的形参列表。

    例如定义了如下接口:

    @FunctionalInterface
    interface FkTest{
        void run();
    }

    上面的函数式接口中仅定义了一个不带参数的方法,因此前面强制转型为 Runnable 的 Lambda 表达式也可强转为 FkTest 类型一一一因为 FkTest 接口中的唯一的抽象方法是不带参数的,而该 Lambda 表达式也是不带参数的。因此,下面代码是正确的。

    // 同样的Lambda表达式可以被当成不同的目标类型,唯一的要求是:
    // Lambda表达式的形参列表与函数式接口中唯一的抽象方法的形参列表相同
    Object obj2 = (FkTest)() -> {
        for(int i = 0 ; i < 100 ; i ++){
            System.out.println();
        }
    };

    Java 8 在 java.util.function 包下预定义了大量函数式接口,典型地包含如下4类接囗。

    • XxxFunction:这类接口中通常包含一个 apply() 抽象方法,该方法对参数进行处理、转换(apply()方法的处理逻辑由 Lambda 表达式来实现),然后返回一个新的值。该函数式接口通常用于对指定数据进行转换处理。
    • XxxConsumer:这类接口中通常包含一个 accept() 抽象方法,该方法与 XxxFunction 接口中的apply()方法基本相似,也负责对参数进行处理,只是该方法不会返回处理结果。
    • XxxxPredicate:这类接口中通常包含一个 test() 抽象方法,该方法通常用来对参数进行某种判断(test()方法的判断逻辑由 Lambda 表达式来实现然后返回一个 boolean 值。该接口通常用于判断参数是否满足特定条件,经常用于进行筛滤数据。
    • XxxSupplier:这类接口中通常包含一个 getAsXxx() 抽象方法,该方法不需要输入参数,该方法会按某种逻辑算法(getAsXxx() 方法的逻辑算法由 Lambda 表达式来实现)返回一个数据。

    综上所述,不难发现 Lambda 表达式的本质很简单,就是使用简洁的语法来创建函数式接口的实例一一这种语法避免了匿名内部类的烦琐。

    三、方法引用与构造器引用

    前面已经介绍过,如果 Lambda 表达式的代码块只有一条代码,程序就可以省略 Lambda 表达式中代码块的花括号。不仅如此,如果 Lambda 表达式的代码块只有一条代码,还可以在代码块中使用方法引用和构造器引用。

    方法引用和构造器引用可以让 Lambda 表达式的代码块更加简洁。方法引用和构造器引用都需要使用两个英文冒号。Lambda 表达式支持下图所示的几种引用方式。

    1. 引用类方法

    先看第一种方法引用:引用类方法。例如,定义了如下函数式接口。

    @FunctionalInterface
    interface Converter{
      Integer convert(String from);
    }

    该函数式接口中包含一个 convert() 方法,该方法负责将 String 参数转换为 Integer。下面代码使用 Lambda 表达式来创建一个 Converter 对象。

    // 下面代码使用Lambda表达式创建Converter对象
    Converter converter1 = from -> Integer.valueOf(from);

    上面 Lambda 表达式的代码块只有一条语句,因此程序省略了该代码块的花括号;而且由于表达式所实现的 convert() 方法需要返回值,因此 Lambda 表达式将会把这条代码的值作为返回值。

    接下来程序就可以调用 converterl 对象的 convert() 方法将字符串转换为整数了,例如如下代码:

    Integer val = converter1.convert("99");
    System.out.println(val); // 输出整数99

    上面代码调用 converter1 对象的 conver() 方法时——由于 converter1 对象是 Lambda 表达式创建的,conver() 方法执行体就是 Lambda 表达式的代码块部分,因此上面程序输出99。

    上面 Lambda 表达式的代码块只有一行调用类方法的代码,因此可以使用如下方法引用进行替换。

    // 方法引用代替Lambda表达式:引用类方法。
    // 函数式接口中被实现方法的全部参数传给该类方法作为参数。
    Converter converter1 = Integer::valueOf;

    对于上面的类方法引用,也就是调用 Integer 类的 valueOf() 类方法来实现 Converter 函数式接口中唯一的抽象方法,当调用 Converter 接口中的唯一的抽象方法时,调用参数将会传给 Integer 类的 valueOf() 类方法。

    2.引用特定对象的实例方法

    下面看第二种方法引用:引用特定对象的实例方法。先使用 Lambda 表达式来创建一个 Converter 对象。

    // 下面代码使用Lambda表达式创建Converter对象
    Converter converter2 = from -> "fkit.org".indexOf(from);

    上面 Lambda 表达式的代码块只有一条语句,因此程序省略了该代码块的花括号;而且由于表达式所实现的 conver() 方法需要返回值,因此 Lambda 表达式将会把这条代码的值作为返回值。接下来程序就可以调用 converter2 对象的 convert()方法将字符串转换为整数了,例如如下代码:

    Integer value = converter2.convert("it");
    System.out.println(value); // 输出2

    上面代码调用 converter2 对象的 convert() 方法时————由于converter2对象是 Lambda 表达式创建的,convert() 方法执行体就是 Lambda 表达式的代码块部分,因此上面程序输出2。

    上面 Lambda 表达式的代码块只有一行调用 "fkit.org" 的 indexOf() 实例方法的代码,因此可以使用如下方法引用进行替换。

    // 方法引用代替Lambda表达式:引用特定对象的实例方法。
    // 函数式接口中被实现方法的全部参数传给该方法作为参数。
    Converter converter2 = "fkit.org"::indexOf;

    对于上面的实例方法引用,也就是调用 "fkit.org" 的 indexOf() 实例方法来实现 Converter 函数式接口中唯一的抽象方法,当调用 Converter 接口中的唯一的抽象方法时,调用参数将会传给 "fkit.org" 对象的 indexOf() 实例方法。

    3.引用某类对象的实例方法

    下面看第三种方法引用:引用某类对象的实例方法。例如,定义了如下函数式接口。

    @FunctionalInterface
    interface MyTest{
        String test(String a , int b , int c);
    }

    该函数式接口中包含一个 test() 抽象方法,该方法负责根据 String、int、int 三个参数生成一个 String 返回值。下面代码使用 Lambda 表达式来创建一个 MyTest 对象。

    // 下面代码使用Lambda表达式创建MyTest对象
    MyTest mt = (a , b , c) -> a.substring(b , c);

    上面 Lambda 表达式的代码块只有一条语句,因此程序省略了该代码块的花括号;而且由于表达式所实现的 test() 方法需要返回值,因此 Lambda 表达式将会把这条代码的值作为返回值。

    接下来程序就可以调用 mt 对象的 test() 方法了,例如如下代码:

    String str = mt.test("Java I Love you" , 2 , 9);
    System.out.println(str); // 输出:va I Lo

    上面代码调用 mt 对象的 test() 方法时一一由于 mt 对象是 Lambda 表达式创建的,test() 方法执行体就是 Lambda 表达式的代码块部分,因此上面程序输出va l Lo。

    上面 Lambda 表达式的代码块只有一行 a.substring(b , c); 因此可以使用如下方法引用进行替换。

    // 方法引用代替Lambda表达式:引用某类对象的实例方法。
    // 函数式接口中被实现方法的第一个参数作为调用者,
    // 后面的参数全部传给该方法作为参数。
    MyTest mt = String::substring;

    对于上面的实例方法引用,也就是调用某个 String 对象的 substring() 实例方法来实现 MyTest 函数式接口中唯一的抽象方法,当调用 MyTest 接口中的唯一的抽象方法时,第一个调用参数将作为 substring()  方法的调用者,剩下的调用参数会作为 substring() 实例方法的调用参数。

    4.引用构造器

    下面看构造器引用。例如,定义了如下函数式接口。

    @FunctionalInterface
    interface YourTest{
        JFrame win(String title);
    }

    该函数式接口中包含一个 win() 抽象方法,该方法负责根据 String 参数生成一个 JFrame 返回值。下面代码使用 Lambda 表达式来创建一个 YourTest 对象。

    // 下面代码使用Lambda表达式创建YourTest对象
    YourTest yt = (String a) -> new JFrame(a);

    上面 Lambda 表达式的代码块只有一条语句,因此程序省略了该代码块的花括号;而且由于表达式所实现的 win() 方法需要返回值,因此 Lambda 表达式将会把这条代码的值作为返回值。接下来程序就可以调用 yt 对象的 win() 方法了,例如如下代码:

    JFrame jf = yt.win("我的窗口");
    System.out.println(jf);

    上面代码调用 yt 对象的 win() 方法时一一由于 yt 对象是 Lambda 表达式创建的,因此 win() 方法执行体就是 Lambda 表达式的代码块部分,即执行体就是执行 new JFrame(a); 语句,并将这条语句的值作为方法的返回值。

    上面 Lambda 表达式的代码块只有一行 new JFrame(a);,因此可以使用如下构造器引用进行替换。

    // 构造器引用代替Lambda表达式。
    // 函数式接口中被实现方法的全部参数传给该构造器作为参数。
    YourTest yt = JFrame::new;

    对于上面的构造器引用,也就是调用某个 JFrame 类的构造器来实现 YourTest 函数式接口中唯一的抽象方法,当调用 YourTest 接口中的唯一的抽象方法时,调用参数将会传给 JFrame 构造器。从上面程序中可以看出,调用 YourTest 对象的 win() 抽象方法时,实际只传入了一个 String 类型的参数,这个 String 类型的参数会被传给 JFrame 构造器————这就确定了是调用 JFrame 类的、带一个 String 参数的构造器。

    四、Lambda表达式与名内部类的联系和区别

    从前面介绍可以看出,Lambda 表达式是匿名内部类的一种简化,因此它可以部分取代匿名内部类的作用,Lambda 表达式与匿名内部类存在如下相同点。

    • Lambda 表达式与匿名内部类一样,都可以直接访问 “effectively final” 的局部变量,以及外部类的成员变量(包括实例变量和类变量)。
    • Lambda 表达式创建的对象与匿名内部类生成的对象一样,都可以直接调用从接口中继承的默认方法。

    下面程序示范了 Lambda 表达式与匿名内部类的相似之处。

    @FunctionalInterface
    interface Displayable {
        // 定义一个抽象方法和默认方法
        void display();
    
        default int add(int a, int b) {
            return a + b;
        }
    }
    
    public class LambdaAndInner {
        private int age = 12;
        private static String name = "疯狂软件教育中心";
    
        public void test() {
            String book = "疯狂Java讲义";
            Displayable dis = ()->{
                //访问“effectively final”的局部变量
                System.out.println("book局部变量为:"+book);
                //访问外部类的实例变量和变量
                System.out.println("外部类的age实例变量为:"+age);
                System.out.println("外部类的name实例变量为:"+name);
            };
            dis.display();
            //调用dis对象从接口中继承的add()方法
            System.out.println(dis.add(3, 5));
        }
        
        public static void main(String[] args) {
            LambdaAndInner lambda = new LambdaAndInner();
            lambda.test();
        }
    }

    上面程序使用 Lambda 表达式创建了一个 Displayable 的对象,Lambda 表达式的代码块中的三行粗体字代码分别示范了访问“effectively final”的局部变量、外部类的实例变量和类变量。从这点来看,Lambda 表达式的代码块与匿名内部类的方法体是相同的。

    与匿名内部类相似的是,由于 Lambda 表达式访问了 book 局部变量,因此该局部变量相当于有一个隐式的 final 修饰,因此同样不允许对book局部变量重新赋值。

    当程序使用 Lambda 表达式创建了 Displayable 的对象之后,该对象不仅可调用接口中唯一的抽象方法,也可调用接口中的默认方法,如上面程序中号粗体字代码所示。

    Lambda 表达式与匿名内部类主要存在如下区别。

    • 匿名内部类可以为任意接口创建实例一一不管接口包含多少个抽象方法,只要匿名内部类实现所有的抽象方法即可:但Lambda表达式只能为函数式接口创建实例。
    • 匿名内部类可以为抽象类甚至普通类创建实例:但Lambda表达式只能为函数式接口创建实例。
    • 匿名内部类实现的抽象方法的方法体允许调用接口中定义的默认方法:但 Lambda 表达式的代码块不允许调用接口中定义的默认方法。

    对于 Lambda 表达式的代码块不允许调用接口中定义的默认方法的限制,可以尝试对上面的 LambdaAndInner.java 程序稍做修改,在 Lambda 表达式的代码块中增加如下一行:

    //尝试调用接口中的默认方法,编译器会报错
    System.out.println(add(3, 5));

    虽然 Lambda 表达式的目标类型:Displayable 中包含了 add() 方法,但 Lambda 表达式的代码块不允许调用这个方法;如果将上面的 Lambda 表达式改为匿名内部类的写法,当匿名内部类实现 display() 抽象方法时,则完全可以调用这个 add() 方法。

    五、使用 Lambda 表达式调用 Arrays 的类方法

    Arrays 类的有些方法需要 Comparator、Xxxoperator、XxxFunction 等接口的实例,这些接囗都是函数式接口,因此可以使用 Lambda 表达式来调用 Arrays 的方法。例如如下程序。

    public class LambdaArrays {
        public static void main(String[] args) {
            String[] arr1 = new String[] { "java", "fkava", "fkit", "ios", "android" };
            Arrays.parallelSort(arr1, (o1, o2) -> o1.length() - o2.length());
            System.out.println(Arrays.toString(arr1));
            int[] arr2 = new int[] { 3, -4, 25, 16, 30, 18 };
            // left代表数组中前一个所索引处的元素,计算第一个元素时,left为1
            // right代表数组中当前索引处的元素
            Arrays.parallelPrefix(arr2, (left, right) -> left * right);
            System.out.println(Arrays.toString(arr2));
            long[] arr3 = new long[5];
            // operand代表正在计算的元素索引
            Arrays.parallelSetAll(arr3, operand -> operand * 5);
            System.out.println(Arrays.toString(arr3));
        }
    }

    上面程序中的粗体字代码就是 Lambda 表达式,第一段粗体字代码的 Lambda 表达式的目标类型是 Comparator,该 Comparator 指定了判断字符串大小的标准:字符串越长,即可认为该字符串越大;第二段粗体字代码的 Lambda 表达式的目标类型是 IntBinaryOperator,该对象将会根据前后两个元素来计算当前元素的值;第三段粗体字代码的 Lambda 表达式的目标类型是 IntToLongFunction,该对象将会根据元素的索引来计算当前元素的值。编译、运行该程序,即可看到如下输出:

    [ios, java, fkit, fkava, android]
    [3, -12, -300, -4800, -144000, -2592000]
    [0, 5, 10, 15, 20]

    通过该程序不难看出:Lambda 表达式可以让程序更加简洁。

    =============================分割线=============================

    六、更多例子

    1、例子一

    import java.util.ArrayList;
    import java.util.Comparator;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    import java.util.function.Consumer;
    
    import org.junit.Test;
    
    /*
     * 一、Lambda 表达式的基础语法:Java8中引入了一个新的操作符 "->" 该操作符称为箭头操作符或 Lambda 操作符
     *                             箭头操作符将 Lambda 表达式拆分成两部分:
     *
     * 左侧:Lambda 表达式的参数列表
     * 右侧:Lambda 表达式中所需执行的功能, 即 Lambda 体
     *
     * 语法格式一:无参数,无返回值
     *         () -> System.out.println("Hello Lambda!");
     *
     * 语法格式二:有一个参数,并且无返回值
     *         (x) -> System.out.println(x)
     *
     * 语法格式三:若只有一个参数,小括号可以省略不写
     *         x -> System.out.println(x)
     *
     * 语法格式四:有两个以上的参数,有返回值,并且 Lambda 体中有多条语句
     *        Comparator<Integer> com = (x, y) -> {
     *            System.out.println("函数式接口");
     *            return Integer.compare(x, y);
     *        };
     *
     * 语法格式五:若 Lambda 体中只有一条语句, return 和 大括号都可以省略不写
     *         Comparator<Integer> com = (x, y) -> Integer.compare(x, y);
     *
     * 语法格式六:Lambda 表达式的参数列表的数据类型可以省略不写,因为JVM编译器通过上下文推断出,数据类型,即“类型推断”
     *         (Integer x, Integer y) -> Integer.compare(x, y);
     *
     * 上联:左右遇一括号省
     * 下联:左侧推断类型省
     * 横批:能省则省
     *
     * 二、Lambda 表达式需要“函数式接口”的支持
     * 函数式接口:接口中只有一个抽象方法的接口,称为函数式接口。 可以使用注解 @FunctionalInterface 修饰
     *              可以检查是否是函数式接口
     */
    public class TestLambda2 {
    
        @Test
        public void test1() {
            int num = 0;//jdk 1.7 前,必须是 final
    
            Runnable r = new Runnable() {
                @Override
                public void run() {
                    System.out.println("Hello World!" + num);
                }
            };
    
            r.run();
            System.out.println("-------------------------------");
    
            Runnable r1 = () -> System.out.println("Hello Lambda!");
            r1.run();
        }
    
        @Test
        public void test2() {
            Consumer<String> con = x -> System.out.println(x);
            con.accept("我大尚硅谷威武!");
        }
    
        @Test
        public void test3() {
            Comparator<Integer> com = (x, y) -> {
                System.out.println("函数式接口");
                return Integer.compare(x, y);
            };
        }
    
        @Test
        public void test4() {
            Comparator<Integer> com = (x, y) -> Integer.compare(x, y);
        }
    
        @Test
        public void test5() {
            String[] strs = {"aaa", "bbb", "ccc"};
            List<String> list = new ArrayList<>();
            show(new HashMap<>());
        }
    
        public void show(Map<String, Integer> map) {}
    
        //需求:对一个数进行运算
        @Test
        public void test6() {
            Integer num = operation(100, (x) -> x * x);
            System.out.println(num);
            System.out.println(operation(200, (y) -> y + 200));
        }
    
        public Integer operation(Integer num, MyFun mf) {
            return mf.getValue(num);
        }
    }
    
    @FunctionalInterface
    public interface MyFun {
      public Integer getValue(Integer num);
    }

    2、例子二

    import java.util.Arrays;
    import java.util.Collections;
    import java.util.List;
    
    import org.junit.Test;
    
    import com.atguigu.java8.Employee;
    
    public class TestLambda {
    
        List<Employee> emps = Arrays.asList(
                new Employee(101, "张三", 18, 9999.99),
                new Employee(102, "李四", 59, 6666.66),
                new Employee(103, "王五", 28, 3333.33),
                new Employee(104, "赵六", 8, 7777.77),
                new Employee(105, "田七", 38, 5555.55)
        );
    
        @Test
        public void test1() {
            Collections.sort(emps, (e1, e2) -> {
                if (e1.getAge() == e2.getAge()) {
                    return e1.getName().compareTo(e2.getName());
                } else {
                    return -Integer.compare(e1.getAge(), e2.getAge());
                }
            });
    
            for (Employee emp : emps) {
                System.out.println(emp);
            }
        }
    
        @Test
        public void test2() {
            String trimStr = strHandler("			 我大尚硅谷威武   ", (str) -> str.trim());
            System.out.println(trimStr);
    
            String upper = strHandler("abcdef", (str) -> str.toUpperCase());
            System.out.println(upper);
    
            String newStr = strHandler("我大尚硅谷威武", (str) -> str.substring(2, 5));
            System.out.println(newStr);
        }
    
        //需求:用于处理字符串
        public String strHandler(String str, MyFunction mf) {
            return mf.getValue(str);
        }
    
        @Test
        public void test3() {
            op(100L, 200L, (x, y) -> x + y);
    
            op(100L, 200L, (x, y) -> x * y);
        }
    
        //需求:对于两个 Long 型数据进行处理
        public void op(Long l1, Long l2, MyFunction2<Long, Long> mf) {
            System.out.println(mf.getValue(l1, l2));
        }
    }
    
    
    @FunctionalInterface
    public interface MyFunction {    
        public String getValue(String str);
    }
    
    public interface MyFunction2<T, R> {
        public R getValue(T t1, T t2);    
    }

    3、例子三

    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.List;
    import java.util.function.Consumer;
    import java.util.function.Function;
    import java.util.function.Predicate;
    import java.util.function.Supplier;
    
    import org.junit.Test;
    
    /*
     * Java8 内置的四大核心函数式接口
     *
     * Consumer<T> : 消费型接口
     *         void accept(T t);
     *
     * Supplier<T> : 供给型接口
     *         T get();
     *
     * Function<T, R> : 函数型接口
     *         R apply(T t);
     *
     * Predicate<T> : 断言型接口
     *         boolean test(T t);
     *
     */
    public class TestLambda3 {
    
        //Predicate<T> 断言型接口:
        @Test
        public void test4() {
            List<String> list = Arrays.asList("Hello", "atguigu", "Lambda", "www", "ok");
            List<String> strList = filterStr(list, (s) -> s.length() > 3);
    
            for (String str : strList) {
                System.out.println(str);
            }
        }
    
        //需求:将满足条件的字符串,放入集合中
        public List<String> filterStr(List<String> list, Predicate<String> pre) {
            List<String> strList = new ArrayList<>();
            for (String str : list) {
                if (pre.test(str)) {
                    strList.add(str);
                }
            }
            return strList;
        }
    
        //Function<T, R> 函数型接口:
        @Test
        public void test3() {
            String newStr = strHandler("			 我大尚硅谷威武   ", (str) -> str.trim());
            System.out.println(newStr);
    
            String subStr = strHandler("我大尚硅谷威武", (str) -> str.substring(2, 5));
            System.out.println(subStr);
        }
    
        //需求:用于处理字符串
        public String strHandler(String str, Function<String, String> fun) {
            return fun.apply(str);
        }
    
        //Supplier<T> 供给型接口 :
        @Test
        public void test2() {
            List<Integer> numList = getNumList(10, () -> (int) (Math.random() * 100));
    
            for (Integer num : numList) {
                System.out.println(num);
            }
        }
    
        //需求:产生指定个数的整数,并放入集合中
        public List<Integer> getNumList(int num, Supplier<Integer> sup) {
            List<Integer> list = new ArrayList<>();
    
            for (int i = 0; i < num; i++) {
                Integer n = sup.get();
                list.add(n);
            }
    
            return list;
        }
    
        //Consumer<T> 消费型接口 :
        @Test
        public void test1() {
            happy(10000, (m) -> System.out.println("你们刚哥喜欢大宝剑,每次消费:" + m + "元"));
        }
    
        public void happy(double money, Consumer<Double> con) {
            con.accept(money);
        }
    }

    4、例子四

    import java.io.PrintStream;
    import java.util.Comparator;
    import java.util.function.BiFunction;
    import java.util.function.BiPredicate;
    import java.util.function.Consumer;
    import java.util.function.Function;
    import java.util.function.Supplier;
    
    import org.junit.Test;
    
    /*
     * 一、方法引用:若 Lambda 体中的功能,已经有方法提供了实现,可以使用方法引用
     *               (可以将方法引用理解为 Lambda 表达式的另外一种表现形式)
     *
     * 1. 对象的引用 :: 实例方法名
     *
     * 2. 类名 :: 静态方法名
     *
     * 3. 类名 :: 实例方法名
     *
     * 注意:
     *      ①方法引用所引用的方法的参数列表与返回值类型,需要与函数式接口中抽象方法的参数列表和返回值类型保持一致!
     *      ②若Lambda 的参数列表的第一个参数,是实例方法的调用者,第二个参数(或无参)是实例方法的参数时,格式: ClassName::MethodName
     *
     * 二、构造器引用 :构造器的参数列表,需要与函数式接口中参数列表保持一致!
     *
     * 1. 类名 :: new
     *
     * 三、数组引用
     *
     *     类型[] :: new;
     *
     *
     */
    public class TestMethodRef {
        //数组引用
        @Test
        public void test8() {
            Function<Integer, String[]> fun = (args) -> new String[args];
            String[] strs = fun.apply(10);
            System.out.println(strs.length);
    
            System.out.println("--------------------------");
    
            Function<Integer, Employee[]> fun2 = Employee[]::new;
            Employee[] emps = fun2.apply(20);
            System.out.println(emps.length);
        }
    
        //构造器引用
        @Test
        public void test7() {
            Function<String, Employee> fun = Employee::new;
            BiFunction<String, Integer, Employee> fun2 = Employee::new;
        }
    
        @Test
        public void test6() {
            Supplier<Employee> sup = () -> new Employee();
            System.out.println(sup.get());
            System.out.println("------------------------------------");
            Supplier<Employee> sup2 = Employee::new;
            System.out.println(sup2.get());
        }
    
        //类名 :: 实例方法名
        @Test
        public void test5() {
            BiPredicate<String, String> bp = (x, y) -> x.equals(y);
            System.out.println(bp.test("abcde", "abcde"));
            System.out.println("-----------------------------------------");
            BiPredicate<String, String> bp2 = String::equals;
            System.out.println(bp2.test("abc", "abc"));
            System.out.println("-----------------------------------------");
            Function<Employee, String> fun = (e) -> e.show();
            System.out.println(fun.apply(new Employee()));
            System.out.println("-----------------------------------------");
            Function<Employee, String> fun2 = Employee::show;
            System.out.println(fun2.apply(new Employee()));
        }
    
        //类名 :: 静态方法名
        @Test
        public void test4() {
            Comparator<Integer> com = (x, y) -> Integer.compare(x, y);
            System.out.println("-------------------------------------");
            Comparator<Integer> com2 = Integer::compare;
        }
    
        @Test
        public void test3() {
            BiFunction<Double, Double, Double> fun = (x, y) -> Math.max(x, y);
            System.out.println(fun.apply(1.5, 22.2));
            System.out.println("--------------------------------------------------");
            BiFunction<Double, Double, Double> fun2 = Math::max;
            System.out.println(fun2.apply(1.2, 1.5));
        }
    
        //对象的引用 :: 实例方法名
        @Test
        public void test2() {
            Employee emp = new Employee(101, "张三", 18, 9999.99);
            Supplier<String> sup = () -> emp.getName();
            System.out.println(sup.get());
            System.out.println("----------------------------------");
            Supplier<String> sup2 = emp::getName;
            System.out.println(sup2.get());
        }
    
        @Test
        public void test1() {
            PrintStream ps = System.out;
            Consumer<String> con = (str) -> ps.println(str);
            con.accept("Hello World!");
            System.out.println("--------------------------------");
            Consumer<String> con2 = ps::println;
            con2.accept("Hello Java8!");
            Consumer<String> con3 = System.out::println;
        }
    }
    
    @Data
    public class Employee {
        private int id;
        private String name;
        private int age;
        private double salary;
    
        public Employee() {}
    
        public Employee(String name) {
            this.name = name;
        }
    
        public Employee(String name, int age) {
            this.name = name;
            this.age = age;
        }
    
        public Employee(int id, String name, int age, double salary) {
            this.id = id;
            this.name = name;
            this.age = age;
            this.salary = salary;
        }
    
        public String show() {
            return "测试方法引用!";
        }
    }
  • 相关阅读:
    上传视频到七牛云django端实现
    课程全文检索接口
    搜索引擎工作原理
    创建订单并生成支付链接接口
    支付宝支付流程
    通过课程查询商品信息
    如何使用 RESTClient 调试微信支付接口
    关于HTML使用ComDlg ActiveX 无法弹出相应对话框的问题1
    Android自定义View的实现方法,带你一步步深入了解View(四)
    Android视图状态及重绘流程分析,带你一步步深入了解View(三)
  • 原文地址:https://www.cnblogs.com/jwen1994/p/12241957.html
Copyright © 2011-2022 走看看