Java8之lambda表达式

概述

  • 一个lambda表达式是一个带有参数的代码块;
  • 当你想把代码块在以后的某个时间执行时,可以使用lambda表达式;
  • lambda表达式可以转换为函数式接口;
  • lambda表达式可以在闭包作用域中有效地访问final变量;
  • 方法和构造器引用可以引用方法或构造器,但无需调用它们;
  • 可以向接口添加默认(default)和静态(static)方法来提供具体的实现;
  • 必须解决接口中多个默认方法之间的冲突。

lambda表达式的语法

Java中的lambda表达式的格式:参数、箭头->、以及一个表达式。

  • 如果负责计算的代码无法用一个表达式表示,那么可以用编写方法的方式来编写:即用{}包裹代码并明确使用return语句,例如:
(String first, String second) -> {
                if (first.length() < second.length())
                    return -1;
                else if (first.length() > second.length())
                    return 1;
                else
                    return 0;
            }
  • 如果lambda表达式没有参数,你仍然可以提供一对空的小括号,如同不含参数的方法一样:
() -> { 
                for (int i = 0; i < 10; i++) doSomething();
            }
  • 如果一个lambda的参数类型是可以被推导的,那就可以省略它们的类型,例如:
Comparator<String> comp = (first, second) -> Integer.compare(first.length(), second.length());//编译器会根据Lambda表达式对应的函数式接口Comparator<String>进行自动推断
  • 如果某个方法只含有一个参数,并且该参数的类型可以被推导出来,甚至可以省略小括号:
EventHandler<ActionEvent> listener = event -> System.out.println("Hello world!");//无需(event) ->或者(ActionEvent event) ->
  • 永远不要为一个lambda表达式执行返回类型,它总会从上下文中被推导出来。例如:
(first, second) -> Integer.compare(first.length(), second.length());//可以被使用在期望结果类型为int的上下文中。

注意:
- 可以向对待方法参数一样向lambda表达式的参数添加注解或final修饰符,如下:

(final String name) -> {}
(@NonNull String name) -> {}
  • 在lambda表达式中,只在某些分支中返回值(其他分支没有返回值)是不合法的。例如:
(int x) -> {if (x > 0) return 1;}//是不合法的。

函数式接口

对于只包含一个抽象方法的接口,可以通过lambda表达式来创建该接口的对象。这种接口被称为函数式接口(Functional Interface)。也就是说:任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口。
例如:Runnable就是一个函数式接口。我们以Arrays.sort方法为例。在Java8之前,可以借助匿名内部类来实现:

List<String> words = Arrays.asList("apple", "banana", "pear");
            words.sort(new Comparator<String>() {
                @Override
                public int compare(String w1, String w2) {
                return Integer.compare(w1.length(), w2.length());
                }
            });

该方法的参数需要一个Comparator接口(该接口只有一个抽象方法,是一个函数式接口)的实例。接下来编写一个lambda表达式:

words.sort((String first, String second) -> {
                return Integer.compare(first.length(), second.length());
            });

在这个表达式背后,Arrays.sort会接收一个Comparator接口的类的实例。调用该对象的compare的方法会执行lambda表达式中的代码。
按照1.2节第3点,继续简化:

words.sort((first, second) -> {
                return Integer.compare(first.length(), second.length());
            });

如果lambda表达式的代码块只是return后面的一个表达式,还可以继续简化:

words.sort((first, second) -> Integer.compare(first.length(), second.length()));

任何可以接受一个函数式接口实例的地方,都可以用Lambda表达式。

方法引用

有些时候,你只想传递给其他代码的操作已经有实现的方法了。例如,你只想在按钮被点击时打印event对象,你可以像如下代码一样调用:

button.setOnAction(event -> System.out.println(event));

如果只调用println方法给setOnAction就更好了。下面是修改后的代码:

button.setOnAction(System.out::println);

表达式System.out::println是一个方法引用,类似于 x -> System.out.println(x)。
::操作符将方法名和对象或类的名字分隔开来。以下是三种主要的使用情况:
- 对象::实例方法
- 类::静态方法
- 类::实例方法

构造器引用

构造器引用与方法引用类似,不同的是构造器引用中方法名是new。例如Button::new表示Button类的构造器引用。

变量作用域

通常,我们希望能够在lambda表达式的闭合方法或类中访问其他的变量,例如:

public static void main(String[] args) {
                repeatMessage("Hello", 20);
                }
                public static void repeatMessage(String text,int count){
                Runnable r = () -> {
                    for(int i = 0; i < count; i++){
                    System.out.println(text);
                    Thread.yield();
                    }
                };
                new Thread(r).start();
                }

注意看lambda表达式中的变量count和text,它们并没有在lambda表达式中被定义,而是方法repeatMessage的参数变量。如果你思考一下,就会发现这里有一些隐含的东西。lambda表达式可能会在repeatMessage返回之后才运行,此时参数变量已经消失了。如果保留text和count变量会怎样呢?
为了理解这一点,我们需要对lambda表达式有更深入的理解。一个lambda表达式包括三个部分:

  • 一段代码
  • 参数
  • 自由变量的值//这里的“自由”指的是那些不是参数并且没有在代码中定义的变量。

在我们的示例中,lambda表达式有两个自由变量,text和count。数据结构表示lambda表达式必须存储这两个变量的值,即“Hello”和20。我们可以说,这些值已经被lambda表达式捕获了(这是一个技术实现的细节。例如,你可以将一个lambda表达式转换为一个只含一个方法的对象,这样自由变量的值就会被复制到该对象的实例变量中)。
如你所见,lambda表达式可以捕获闭合作用域中的变量值。在java中,为了确保被捕获的值是被良好定义的,需要遵守一个重要的约束。在lambda表达式中,被引用的变量的值不可以被更改。例如,下面这个表达式是不合法的:

public static void repeatMessage(String text,int count){
                Runnable r = () -> {
                while(count > 0){
                    count--;        //错误,不能更改已捕获变量的值
                    System.out.println(text);
                    Thread.yield();
                 }
                 };
                 new Thread(r).start();
            }

做出这个约束是有原因的。更改lambda表达式中的变量不是线程安全的。假设有一系列并发的任务,每个线程都会更新一个共享的计数器。
lambda表达式的方法体与嵌套代码块有着相同的作用域。因此它也适用同样的命名冲突和屏蔽规则。在lambda表达式中不允许声明一个与局部变量同名的参数或者局部变量。

Path first = Paths.get("/usr/bin");
            Comparator<String> comp = (first,second) ->
                Integer.compare(first.length(),second.length());
            //错误,变量first已经定义了

在一个方法里,你不能有两个同名的局部变量,因此,你也不能在lambda表达式中引入这样的变量。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值