Java Lambda 表达式

来源:Java Lambda 表达式 | 菜鸟教程


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

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

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

语法

lambda 表达式的语法格式如下:

(parameters) -> expression 或 (parameters) ->{ statements; }

lambda表达式的重要特征:

  • **可选类型声明:**不需要声明参数类型,编译器可以统一识别参数值。
  • **可选的参数圆括号:**一个参数无需定义圆括号,但多个参数需要定义圆括号。
  • **可选的大括号:**如果主体包含了一个语句,就不需要使用大括号。
  • **可选的返回关键字:**如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定表达式返回了一个数值。

Lambda 表达式实例

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)

在 Java8Tester.java 文件输入以下代码:

Java8Tester.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

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

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

变量作用域

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

在 Java8Tester.java 文件输入以下代码:

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 表达式中访问外层的局部变量:

Java8Tester.java 文件

public class Java8Tester {
    public static void main(String args[]) {
        final int num = 1;
        Converter<Integer, String> s = (param) -> System.out.println(String.valueOf(param + num));
        s.convert(2);  // 输出结果为 3
    }
 
    public interface Converter<T1, T2> {
        void convert(int i);
    }
}

lambda 表达式的局部变量可以不用声明为 final,但是必须不可被后面的代码修改(即隐性的具有 final 的语义)

int num = 1;  
Converter<Integer, String> 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<String> comparator = (first, second) -> Integer.compare(first.length(), second.length());  //编译会出错 

总结:

  1.Lambda表达式的使用条件

    ①必须对应函数式接口(有@functional interface)

    ②或接口中只有一个抽象方法的接口

  2.Lambda表达式的变量要求

    ①只能引用标记了 final 的外层局部变量,不能在 lambda 内部修改定义在域外的局部变量,否则会编译错误。

    ②lambda 表达式的局部变量可以不用声明为 final,但是必须不可被后面的代码修改(即隐性的具有 final 的语义)

  3.Lambda表达式的存在意义

    ①函数编程思想:

       函数式编程强调程序的执行结果比执行过程更重要。关注于描述问题,而不是怎么实现,隐藏实现细节。
       化繁为简。利用若干简单的执行单元让计算结果不断渐进,逐层推导复杂的运算。

  4.Lambda表达式的表现形式

        1.变量的形式:变量的类型为函数式接口,就么可以复制一个Lambda表达式【不常用】

        // 变量的形式
        Runnable r = ()->{
            System.out.println("任务代码");
        };
        // 函数式接口类型的变量
        Thread t = new Thread(r);

        2.参数的形式:方法的形参类型为函数式接口,就可以传入一个Lambda表达式【常用】

        // 变量的形式-比较器
        Comparator<Integer> comparable = (o1, o2)->{return o2 - o1;};
        // 创建集合
        ArrayList<Integer> list = new ArrayList<>();
        // 存入数据
        Collections.addAll(list,11,22,33,44,55);
        // 将函数式接口类型  的 形参类型,传给Collections
        Collections.sort(list,comparable);

        3.返回值的形式:方法的返回值类型为函数式接口,就可以返回一个Lambda表达式【常用】

        // 定义一个方法
        public static Comparator<Integer> getComparator(){
            return (Integer o1,Integer o2)->{return  o2-o1;};
        }
        public static void main (String[] args) {
            // 返回值形式
            Collections.sort(list,getComparator());
        }

利用lambda表达式对自定义对象进行排序


        //1.创建三个gf的对象
        GirlFriend gf1 = new GirlFriend("xiaoshishi",18,1.67);
        GirlFriend gf2 = new GirlFriend("xiaodandan",19,1.72);
        GirlFriend gf3 = new GirlFriend("xiaohuihui",19,1.78);

        //2.定义数组存储女朋友信息
        GirlFriend[] GFarr = {gf1,gf2,gf3};

        //3.利用arrays中是sort方法排序      (在这里使用强转会有一定问题:0.1的返回值是0)
        //使用匿名内部类的写法:
        Arrays.sort(GFarr, new Comparator<GirlFriend>() {
            @Override
            public int compare(GirlFriend o1, GirlFriend o2) {
                //按照年龄大小进行排序(大的在前),若年龄一样,则按照身高排序(高的在前)
                return (o1.getAge() == o2.getAge())?(int)(o1.getHeight()-o2.getHeight()):(o2.getAge()-o1.getAge());
            }
        });
        //输出
        System.out.println(Arrays.toString(GFarr));
        //使用lambda表达式:
        //按照年龄大小进行排序(小的在前),若年龄一样,则按照身高排序(矮的在前)
        Arrays.sort(GFarr,(o1,o2)->(o1.getAge() == o2.getAge())?(int)(o2.getHeight()-o1.getHeight()):(o1.getAge()-o2.getAge()));
        //输出
        System.out.println(Arrays.toString(GFarr));

爬楼梯问题


        可爱的小明特别喜欢爬楼梯,他有的时候一次爬一个台阶,有的时候一次爬两个台阶

        1.如果这个楼梯有20个台阶,小明一共有多少种爬法呢

        //本质上是斐波那契数列的变式
        System.out.println(getSum1(20));

    public static int getSum1(int index){
        return (index == 1 ) ? 1:((index == 2)?2:((getSum1(index -1)+getSum1(index -2))));
    }

  2.如果小明一次一个台阶,有时候两个台阶,有时候三个台阶,小明一共有多少种爬法?

		//也是类似斐波那契数列,不过要考虑有多少种情况
		System.out.println(getSum2(20));

	public static int getSum2(int index){
        //如果是三个爬法,则有四种情况:
        //如三个台阶时:111 12 21 3
        if(index == 1)return 1;
        if(index == 2)return 2;
        if(index == 3)return 4;
        return getSum2(index -1)+getSum2(index -2)+getSum2(index -3);
    }

猴子吃桃子


        有一堆桃子,猴子第一天吃了其中的一半,并多吃了一个!以后每天猴子都吃当前剩下来的一半,然后
        再多吃一个,第10天的时候(还没吃),发现只剩下一个桃子了,请问,最初总共多少个桃子?

        System.out.println("初始有" + getSum(10) + "个桃子。");

    public static int getSum(int day) {
        if (day == 1)
            return 1;
        else
            return (getSum(day - 1) + 1) * 2;
    }