写点什么

Java-8 新特性:学习如何使用 Lambda 表达式(二

  • 2022 年 4 月 20 日
  • 本文字数:2804 字

    阅读完需:约 9 分钟

方法参考

有时你已经有了一个适合你需求的方法,你想将它传递给其他一些方案。例如,假设您希望在单击按钮时只打印事件对象。你可以这样写


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


println方法传递给setOnAction方法更直观。以下示例显示了它:


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


System.out::println是一个方法引用,类似于 lambda 表达式。我们可以在这里用方法引用替换 lambda。


想象一下,你想要忽略一个字母大小写的排序字符串。您可以编写如下代码:


Arrays.sort(strs, String::compareToIgnoreCase)


运算符::将方法名称与对象或类的名称分开。主要有三种情况:


  • 对象的实例方法;

  • 一个类的静态方法;

  • 类的实例方法;


在前两种情况下,方法引用等效于带有方法参数的 lambda 表达式。如上所示,System.out::println相当于x -> System.out.println(x)。同样,Math::pow相当于(x, y) -> Math.pow(x, y)。在第三种情况下,第一个参数成为方法的目标。例如,String::compareToIgnoreCase与...相同(x, y) -> x.compareToIgnoreCase(y)


众所周知,类可以有多个具有相同名称的重载方法。在这种情况下,编译器将尝试从上下文中找到要选择的内容。例如,该Math.max方法有两个版本,一个用于 int,一个用于 double 值。调用哪一个取决于Math::max转换的功能接口的方法签名。方法引用不是单独存在的。与幕后的 lambdas 类似,它们总是变成功能接口的实例。


您可能想到是否可以使用this在方法参考中捕获参数。是的你可以。例如,this::equals相当于x -> this.equals(x)。它也可以使用super。当我们使用super::instanceMethod它成为目标并调用给定方法的基类版本。这是一个非常真实的例子:


class Speaker {public void speak() {System.out.println("Hello, world!");}}class ConcurrentSpeaker extends Speaker {public void speak() {Thread t = new Thread(super::speak);t.start();}}


当线程启动时,run调用方法并super::speak执行,调用speak其基类的方法。请注意,在内部类中,您可以this将封闭类的引用,捕获为EnclosingClass.this::methodEnclosingClass.super::method

构造函数参考

构造函数引用与方法引用相同,只是方法名称为new。例如,Button::new是类的构造函数引用Button。将调用哪个构造函数取决于上下文。想象一下,您想要将字符串列表转换为按钮数组。在这种情况下,您应该在每个字符串上调用构造函数。它可能是这样的:


List<String> strs = ...;Stream<Button> stream = strs.stream().map(Button::new);List<Button> buttons = stream.collect(Collectors.toList());


有关stream的更多信息,您可以查看文档。在这种情况下,最重要的是该方法为每个列表元素调用构造函数。有多个构造函数,但编译器选择带有参数的构造函数,因为从上下文中可以明显看出应该调用带有字符串的构造函数。map``collect``map``Button(String)``Button``String


还可以为数组类型创建构造函数引用。例如,int数组的构造函数引用是int[]::new。它需要一个参数:数组的长度。它等同于 lambda 表达式x -> new int[x]


数组的构造函数引用对于超越 Java 的限制很有用。创建泛型类型的数组是不可能的T。表达式new T[n]不正确,因为它将替换为new Object[n]。对于图书馆作者来说这是一个问题。想象一下,我们想拥有一系列按钮。有一种方法toArray在类Stream返回的数组Object


Object[] buttons = stream.toArray();


但那不是你想要的。用户不想要Objects,只有按钮。该库使用构造函数引用解决了这个问题。你应该传递Button[]::new给方法toArray


Button[] buttons = stream.to Java 开源项目【ali1024.coding.net/public/P7/Java/git】 Array(Button[]::new);


toArray方法调用此构造函数以获取所需类型的数组,然后在填充后返回此数组。

可变范围

通常,您希望从 lambda 表达式中的封闭范围访问变量。考虑以下代码:


public static void repeatText(String text, int count) {Runnable r = () -> {for (int i = 0; i < count; i++) {System.out.println(text);Thread.yield();}};new Thread(r).start();}


看下面这个调用:


repeatText("Hi!", 2000); // Prints Hi 2000 times in a separate thread


注意变量counttext没有在 lambda 表达式中定义; 这些是封闭方法的参数。


如果仔细观察这段代码,你会发现幕后有某种魔力。该repeatText方法可以在 lambda 表达式的代码运行之前返回,而那时参数counttext变量将消失,但它们仍然可用于 lambda。秘密是什么?


要了解发生了什么,我们需要提高对 lambda 表达式的理解。lambda 表达式由三个部分组成:


  1. 代码块

  2. 参数

  3. 自由变量不是参数,也没有在 lambda 中定义


在我们的案例中有两个自由变量,textcount。表示 lambda 的数据结构必须存储它们的值,“嗨!” 他们说这些值是由 lambda 表达式捕获的。(如何完成取决于实现。例如,实现可以使用一个方法将 lambda 表达式转换为对象,并将自由变量的值复制到对象的实例变量中。)


有一个特殊的术语“封闭”; 它是一个代码块和自由变量的值。Lambda 用一种方便的语法表示 Java 中的闭包。顺便说一句,内部类总是封闭的。


因此,lambda 表达式可以捕获封闭范围中变量的值,只是有一些限制。你无法更改这些捕获变量的值。以下代码不正确:


public static void repeatText(String text, int count) {Runnable r = () -> {while (count > 0) {count--; // Error: Can't modify captured variableSystem.out.println(text);Thread.yield();}};new Thread(r).start();}


这种限制是合理的,因为 lambda 表达式中的变量变量不是线程安全的。想象一下,我们有一系列并发任务,每个任务都更新一个共享计数器。


int matchCount = 0;for (Path p : files)new Thread(() -> { if (p has some property) matchCount++; }).start();// Illegal to change matchCount


如果这段代码是正确的,那将是非常非常糟糕的。increment 运算符++不是原子的,如果多个线程同时执行此代码,则无法控制结果。


内部类也可以从外部类中捕获值。在 Java 8 之前,内部类只能访问final局部变量。此规则已扩展为与 lambda 表达式匹配。现在,内部类可以处理其值 《一线大厂 Java 面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义》开源 不会发生变化的任何变量(实际上是final变量)。


不要指望编译器捕获所有并发访问错误。您应该知道,此修改规则仅适用于局部变量。如果我们使用外部类的实例或静态变量,则不会出现错误,结果是未定义的。


您也可以修改共享对象,即使它不健全。例如,

最后

现在其实从大厂招聘需求可见,在招聘要求上有高并发经验优先,包括很多朋友之前都是做传统行业或者外包项目,一直在小公司,技术搞的比较简单,没有怎么搞过分布式系统,但是现在互联网公司一般都是做分布式系统。


所以说,如果你想进大厂,想脱离传统行业,这些技术知识都是你必备的,下面自己手打了一份 Java 并发体系思维导图,希望对你有所帮助。



用户头像

还未添加个人签名 2022.04.13 加入

还未添加个人简介

评论

发布
暂无评论
Java-8新特性:学习如何使用Lambda表达式(二_Java_爱好编程进阶_InfoQ写作社区