资讯专栏INFORMATION COLUMN

Java8 Lamda详解

anquan / 1420人阅读

摘要:局部变量表达式的方法体与嵌套代码块有着相同的作用域。在表达式中不允许声明一个与局部变量同名的参数或者局部变量。不可变的约束只作用在局部变量上,如果是一个实例变量或者闭合类的静态变量,那么不会有任何错误被报告出来即使结果同样未定义。

完整的Java学习的路线图可以参考:我的编程之路--知识管理与知识体系

Lambda&Closures

Java8 Lambda表达式10个示例

闭包一般指存在自由变量的代码块,它与对象类似,都是用来描述一段代码与其环境的关系。在Java中,Lambda表达式就是闭包。事实上,内部类一直都是闭包,而Java8中为闭包赋予了更吸引人的语法。一个Lambda表达式包括三个部分:

一段代码

参数

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

Lambda表达式本身是构造了一个继承自某个函数式接口的子类,所以可以用父类指针指向它。Java中本质上闭包中是采用的值捕获,即不可以在闭包中使用可变对象。但是它实际上是允许捕获事实上不变量,譬如不可变的ArrayList,只是指针指向不可变罢了。虽然实现用的是值捕获,但效果看起来跟引用捕获一样;就算以后的Java扩展到允许通用的(对可变变量的)引用捕获,也不会跟已有的代码发生不兼容。

Java中最常见的闭包的使用如下所示:

Runnable task = () -> {
    // do something
};

Comparator cmp = (s1, s2) -> {
    return Integer.compare(s1.length(), s2.length());
};
方法引用

有时候Lambda表达式的代码就只是一个简单的方法调用而已,遇到这种情况,Lambda表达式还可以进一步简化为 方法引用(Method References) 。一共有四种形式的方法引用。

(1)静态方法引用

List ints = Arrays.asList(1, 2, 3);
ints.sort(Integer::compare);

(2)某个特定对象的实例方法

例如前面那个遍历并打印每一个word的例子可以写成这样:

words.forEach(System.out::println);

(3)某个类的实例方法

例如:

words.stream().map(word -> word.length()); // lambda
words.stream().map(String::length); // method reference

(4)构造函数引用

// lambda
words.stream().map(word -> {
    return new StringBuilder(word);
});
// constructor reference
words.stream().map(StringBuilder::new);
Variable Scope(变量作用域)

在Lambda中,变量的作用域与访问操作主要遵循以下规则:

本地变量(Local Variable)可以访问但是不可以修改。

类成员变量与静态变量可以被读写,即闭包中的this实际指向的是创建该Lambda表达式的方法的this参数。

函数式接口的默认方法不可以在Lambda表达式中被访问。

(1)局部变量
lambda表达式的方法体与嵌套代码块有着相同的作用域。因此它也适用同样的命名冲突和屏蔽规则。在lambda表达式中不允许声明一个与局部变量同名的参数或者局部变量。

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

在一个方法里,你不能有两个同名的局部变量,因此,你也不能在lambda表达式中引入这样的变量。在下一个示例中,lambda表达式有两个自由变量,text和count。数据结构表示lambda表达式必须存储这两个变量的值,即“Hello”和20。我们可以说,这些值已经被lambda表达式捕获了(这是一个技术实现的细节。例如,你可以将一个lambda表达式转换为一个只含一个方法的对象,这样自由变量的值就会被复制到该对象的实例变量中)。

package java8test;

public class T1 {
    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();
    }
}

(2)this

当你在lambda表达式中使用this关键字,你会引用创建该lambda表达式的方法的this参数,以下面的代码为例:

public class Application{
    public void doWork(){
        Runnable runner = () -> {....;System.out.println(this.toString());......};
    }
}

表达式this.toString()会调用Application对象的toString()方法,而不是Runnable实例的toString()方法。在lambda表达式中使用this,与在其他地方使用this没有什么不同。lambda表达式的作用域被嵌套在doWork()方法中,并且无论this位于方法的何处,其意义都是一样的。

(3)引用的变量不可更改

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表达式中的变量不是线程安全的。假设有一系列并发的任务,每个线程都会更新一个共享的计数器。

int matches = 0;
for(Path p : files)
    new Thread(() -> {if(p中包含某些属性) matches++;}).start();    //非法更改matches的值

如果这段代码是合法的,那么会引起十分糟糕的结果。自增操作matches++不是原子操作,如果多个线程并发执行该自增操作,天晓得会发生什么。不要指望编译器会捕获所有并发访问错误。不可变的约束只作用在局部变量上,如果matches是一个实例变量或者闭合类的静态变量,那么不会有任何错误被报告出来即使结果同样未定义。同样,改变一个共享对象也是完全合法的,即使这样并不恰当。例如:

List matches = new ArrayList<>();
for(Path p: files)
//你可以改变matches的值,但是在多线程下是不安全的
    new Thread(() -> {if(p中包含某些属性) matches.add(p);}).start();

注意matches是“有效final”的(一个有效的final变量被初始化后,就永远不会再被赋一个新值的变量)。在我们的示例中,matches总是引用同一个ArrayList对象,但是,这个对象是可变的,因此是线程不安全的 。如果多个线程同时调用add方法,结果将无法预测。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/65627.html

相关文章

  • Java8的新特性

    摘要:语言特性系列的新特性的新特性的新特性的新特性的新特性的新特性的新特性的新特性的新特性序本文主要讲的新特性,也是一个重要的版本,在语法层面有更大的改动,支持了表达式,影响堪比的泛型支持。 Java语言特性系列 Java5的新特性 Java6的新特性 Java7的新特性 Java8的新特性 Java9的新特性 Java10的新特性 Java11的新特性 Java12的新特性 Java13...

    xi4oh4o 评论0 收藏0
  • Java学习路线总结,搬砖工逆袭Java架构师(全网最强)

    摘要:哪吒社区技能树打卡打卡贴函数式接口简介领域优质创作者哪吒公众号作者架构师奋斗者扫描主页左侧二维码,加入群聊,一起学习一起进步欢迎点赞收藏留言前情提要无意间听到领导们的谈话,现在公司的现状是码农太多,但能独立带队的人太少,简而言之,不缺干 ? 哪吒社区Java技能树打卡 【打卡贴 day2...

    Scorpion 评论0 收藏0
  • Java Optional 实践

    摘要:实践很简单的一个类,点开它的源代码,其中所有的方法都是与相关联的。从而很好地避免了空指针异常。方法,如果存在,返回包含的值,否则抛出异常。随便点开一个方法,都会在第一行为不该为的参数进行判断。 问题描述 在大热的Spring Boot 2.0中,在将原来的泛型改为了Optional,旨在让我们的代码更简洁。 showImg(https://segmentfault.com/img/bV...

    jaysun 评论0 收藏0
  • Python3,68个内置库函数详解,进阶必备,必须收藏!!!

    摘要:判断奇数是迭代器会根据提供的函数对指定序列做映射语法可以对可迭代对象中的每一个元素进行映射。 python内置库详解 1、引言2、内置库详解2.1 数据相关2.1...

    lindroid 评论0 收藏0
  • Java8-5-Function函数式接口进阶与默认方法详解

    摘要:函数式接口进阶与默认方法详解上一篇我们快速的借助示例演示了的简单应用,体会到了使用对集合处理的便捷和其与函数式接口密不可分的关系,所以为了更高效的使用,有必要更熟练的掌握函数式接口。 Java8-5-函数式接口进阶与默认方法详解上一篇我们快速的借助示例演示了stream api的简单应用,体会到了使用stream api对集合处理的便捷和其与函数式接口密不可分的关系,所以为了更高效的使...

    Miracle_lihb 评论0 收藏0

发表评论

0条评论

anquan

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<