Java中Lambda表达式之Lambda语法与作用域解析

接上一篇:初探Lambda表达式/Java多核编程【2】并行与组合行为

本节是第二章开篇,前一章已经浅显地将所有新概念点到,书中剩下的部分将对这些概念做一个基础知识的补充与深入探讨实践。

本章将介绍Lambda表达式基础知识。

前言

把上一张书中的结语放到这里作为本章学习内容的开头,以此来概括Lambda表达式的优点:

  • 提升性能、自动的并行化
  • 更棒的API(comparing(...)细粒度的方法将成为标准)
  • 编码风格得到改进、代码简化

反观前面几篇文章中的代码实践,以上三个优点全部得到了验证。

Lambda语法

前文中我们已经提到,Java中无法声明独立的纯函数,但是Lambda的出现提供了一种与独立函数更为近似的实现方式。就只看Lambda形式,的确与很多精简语法的脚本语言中所声明的函数高度相似:

# CoffeeScript
eat = (x) ->
 alert("#{x} has been eatten!")

总之光看上去就像那么回事:)

那么Lambda表达式的语法又是怎样的呢?

  • 参数列表
  • Lambda体

两部分之间使用->分割,看几个例子:

p -> p.translate();
i -> new Point();
(a, b) -> return a + b;
() -> "Ha!";
(x, y, z) -> {
 x += y;
 y += z;
 z += x;
}

箭头左边接收任意数量的参数,右边则为表达式体,描述所需的行为。

显而易见,在一般情况下无需显式地指定参数类型,除非上下文的信息无法是编译器推断出相应的类型:

(int x, int y) -> x + y;

参数可以声明为final,也可以添加注解(@Nullable, etc.)。

表达式体部分可以为方法的调用,如str.length()等等,也可以是表达式,如加减乘除等等,即“语句Lambda”与“表达式Lambda”这两种形式。

另外关于返回值,有则用return sth_to_return;,没有则用return;或直接不写返回语句。

最后,需要注意的是Lambda表达式不需要也不允许使用throws关键字来声明可能产生并需要向上抛出的异常。

Lambda与匿名内部类

前几篇文章中常常将Lambda与匿名内部类做粗浅的类比与对比,现在我们将就这一点做具体深入的分析。

语法

首先在语法层面,Lambda表达式有时候被称为匿名内部类的“语法糖”,这表明了二者之间存在语法繁简的明显区别。

无标识性问题

其次便是标识性问题,我们知道Java中为了区分对象,每一个对象(即使是匿名内部类的实例)都具有唯一标识,而依赖于对象而存在的行为(即我们所说的方法)也会与此标识相关联。
例如:

String bar = "bar";
String foo = "foo";
System.out.println(bar.hashCode()); // => 97299
System.out.println(foo.hashCode()); // => 101574

但是对于Lambda表达式而言,情况便不是如此的明朗,根据具体情况的不同,Lambda自身可能拥有标识也可能没有。

况且,Lambda为的就是表示一种行为,趋向于纯函数,因此一般情况下是不需要使用标识加以区分的。

作用域规则

再者就是两者的作用域大小的区别。

对于匿名内部类而言,显而易见,在类内可以沿用父类型(即函数接口)的名字。

而对于Lambda,则不能。

我们用Runnable接口来举一个例子:

public interface LetsRun extends Runnable {
 String aString = "Big brother is watching.";
}
new Thread(
 new LetsRun() {
  @Override
  public void run() {
   System.out.println(aString);
  }
}).run();

显然,匿名内部类能够直接沿用我们在LetsRun这个函数式接口中声明的aString。

写完这段代码的同时,IDE给了我一个可以将匿名内部类折叠为Lambda的提示,现在就让它帮我们自动折叠一下:

new Thread((LetsRun) () -> System.out.println(LetsRun.aString)).run();

注意此时需要打印的内容也同时自动变成了LetsRun.aString,印证了上述特征,即Lambda不能直接访问父类型中的名字。

关于对外部变量的访问(后面书中将此称为“变量捕获”),不论是匿名内部类还是Lambda,对于域外部变量的权限都是有限的。

在匿名内部类中,可以读取外部量,但是不允许有修改变量的倾向。

也就是说,没有严格的限制规定被访问的外部量必须被声明为final:

// This is OK
String anotherString = "WAR IS PEACE / FREEDOM IS SLAVERY / IGNORANCE IS STRENGTH";
// Also OK
final String finalString = "Nineteen Eighty-Four";
new Thread(new LetsRun() {
 @Override
 public void run() {
  System.out.println(aString + "\n" + anotherString + "\n" + finalString);
 }
}).run();

倘若一旦在方法内修改了anotherString的值,编译就无法通过。
同样,折叠为Lambda后,依然是合法的:

new Thread((LetsRun) ()
  -> System.out.println(LetsRun.aString + "\n" + anotherString + "\n" + finalString)).run();

关于变量捕获的问题是下一小节的重点内容,在此暂时不做深究。

Lambda表达式在定义时,参数部分与表达式体内的命名可以暂时屏蔽掉字段的名称:

public class Foo {
 String x, y;
 BinaryOperator binaryOperator = (x, y) -> x.hashCode() + y.hashCode();
 // ...
}

另外,Lambda相当于语句块,因此表达式体内持有和外部相同的语境,即this与super拥有相同含义:

public class MySuperClass {
 public static final String aString = "Father";
}
public class MyClass extends MySuperClass {
 public static final String aString = "Son";
 public void aMethod() {
  new Thread((LetsRun) () -> {
   System.out.println("--- Lambda ---");
   System.out.println(super.aString);
   System.out.println(this.aString);
  }).run();
  System.out.println("--- Outside ---");
  System.out.println(super.aString);
  System.out.println(this.aString);
 }
}

运行结果:

--- Lambda ---
Father
Son
--- Outside ---
Father
Son

Lambda无法引用自身,因此可以用一种尴尬的方式递归调用自己:

intUnaryOperator = i -> i == 0 ? 1 : i * intUnaryOperator.applyAsInt(i - 1);

小结

Lambda不从父类型中继承任何名字,包括:

接口的静态final字段

接口的静态嵌套类

默认方法(将在后续介绍)

将全部被排除在作用域之外。

Lambda参数与表达式体中的局部声明可以屏蔽字段名。

Lambda中的this和super的含义完全同外部一致。

而若在匿名内部类访问外部对象的当前实例须用OuterClass.this,非常笨拙:

new Thread((LetsRun) () ->
  System.out.println(Foo.this.getClass().toString())
).run();

递归Lambda时须注意Lambda变量无法被初始化,只能直接调用相应函数式接口中的方法。

本章代码:

Foo.java

import java.util.function.BinaryOperator;
public class Foo {
 String x, y;
 BinaryOperator binaryOperator = (x, y) -> x.hashCode() + y.hashCode();
 public static void main(String[] args) {
  String bar = "bar";
  String foo = "foo";
  System.out.println(bar.hashCode());
  System.out.println(foo.hashCode());
  new Thread((LetsRun) () -> System.out.println(LetsRun.aString)).run();
  String anotherString = "WAR IS PEACE / FREEDOM IS SLAVERY / IGNORANCE IS STRENGTH";
  final String finalString = "Nineteen Eighty-Four";
  new Thread((LetsRun) () -> System.out.println(LetsRun.aString + "\n" + anotherString + "\n" + finalString)).run();
  new MyClass().aMethod();
  new Foo().accessOuterClassInAnnoymousInnerClass();
 }
 public void accessOuterClassInAnnoymousInnerClass() {
  new Thread((LetsRun) () ->
    System.out.println(Foo.this.getClass().toString())
  ).run();
 }
}

LetsRun.java

public interface LetsRun extends Runnable {
 String aString = "Big brother is watching.";
}

MyClass.java

import java.util.function.IntUnaryOperator;
public class MyClass extends MySuperClass {
 public static final String aString = "Son";
 IntUnaryOperator intUnaryOperator = null;
 public void aMethod() {
  new Thread((LetsRun) () -> {
   System.out.println("--- Lambda ---");
   System.out.println(super.aString);
   System.out.println(this.aString);
  }).run();
  System.out.println("--- Outside ---");
  System.out.println(super.aString);
  System.out.println(this.aString);
 }
 public void factorial() {
  intUnaryOperator = i -> i == 0 ? 1 : i * intUnaryOperator.applyAsInt(i - 1);
 }
}

MySuperClass.java

public class MySuperClass {
 public static final String aString = "Father";
}

以及运行结果:

97299
101574
Big brother is watching.
Big brother is watching.
WAR IS PEACE / FREEDOM IS SLAVERY / IGNORANCE IS STRENGTH
Nineteen Eighty-Four
--- Lambda ---
Father
Son
--- Outside ---
Father
Son
class Foo

以上所述是小编给大家介绍的Java中Lambda表达式之Lambda语法与作用域解析,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对我们网站的支持!

(0)

相关推荐

  • Java函数式编程(一):你好,Lambda表达式

    第一章 你好,lambda表达式! 第一节 Java的编码风格正面临着翻天覆地的变化. 我们每天的工作将会变成更简单方便,更富表现力.Java这种新的编程方式早在数十年前就已经出现在别的编程语言里面了.这些新特性引入Java后,我们可以写出更简洁,优雅,表达性更强,错误更少的代码.我们可以用更少的代码来实现各种策略和设计模式. 在本书中我们将通过日常编程中的一些例子来探索函数式风格的编程.在使用这种全新的优雅的方式进行设计编码之前,我们先来看下它到底好在哪里. 改变了你的思考方式 命令式风格--

  • Java8新特性之Lambda表达式浅析

    说到java 8,首先会想到lambda(闭包)以及虚拟扩展方法(default method),这个特性早已经被各大技术网站炒得沸沸扬扬了,也是我们java 8系列开篇要讲的第一特性(JEP126 http://openjdk.java.net/jeps/126),jdk8的一些库已经应用了lambda表达式重新设计了,理解他对学习java 8新特性有着重要的意义. 一.函数式接口 函数式接口(functional interface 也叫功能性接口,其实是同一个东西).简单来说,函数式接口是

  • Java Lambda表达式之从集合到流

    从集合到流 现在我们用代码来具体表示对某一集合进行迭代操作,我们希望定义一个Contact类来表示联系人,并将ContactList中所有String类型的联系人姓名全部包装进Contact类中: List<Contact> contacts = new ArrayList<>(); contactList.forEach(new Consumer<String>() { @Override public void accept(String s) { Contact

  • Java编程中使用lambda表达式的奇技淫巧

    为什么使用Lambda表达式 先看几个例子: 第一个例子,在一个独立的线程中执行某项任务,我们通常这么实现: class Worker implements Runnable { public void run() { for (int i = 0; i < 100; i++) doWork(); } ... } Worker w = new Worker(); new Thread(w).start(); 第二个例子,自定义字符串比较的方法(通过字符串长度),一般这么做: class Leng

  • Java8新特性lambda表达式有什么用(用法实例)

    我们期待了很久lambda为java带来闭包的概念,但是如果我们不在集合中使用它的话,就损失了很大价值.现有接口迁移成为lambda风格的问题已经通过default methods解决了,在这篇文章将深入解析Java集合里面的批量数据操作(bulk operation),解开lambda最强作用的神秘面纱. 1.关于JSR335 JSR是Java Specification Requests的缩写,意思是Java 规范请求,Java 8 版本的主要改进是 Lambda 项目(JSR 335),其

  • Java中Lambda表达式并行与组合行为

    从串行到并行 串行指一个步骤一个步骤地处理,也就是通常情况下,代码一行一行地执行. 如果将我们常用的迭代器式的循环展开的话,就是串行执行了循环体内所定义的操作: sum += arr.get(0); sum += arr.get(1); sum += arr.get(2); //... 在书的一开始,就提到Java需要支持集合的并行计算(而Lambda为这个需求提供了可能). 这些功能将全部被实现于库代码中,对于我们使用者,实现并行的复杂性被大大降低(最低程度上只需要调用相关方法). 另外,关于

  • Java Lambda表达式详解和实例

    简介 Lambda表达式是Java SE 8中一个重要的新特性.lambda表达式允许你通过表达式来代替功能接口. lambda表达式就和方法一样,它提供了一个正常的参数列表和一个使用这些参数的主体(body,可以是一个表达式或一个代码块). Lambda表达式还增强了集合库. Java SE 8添加了2个对集合数据进行批量操作的包: java.util.function 包以及 java.util.stream 包. 流(stream)就如同迭代器(iterator),但附加了许多额外的功能.

  • Java中Lambda表达式之Lambda语法与作用域解析

    接上一篇:初探Lambda表达式/Java多核编程[2]并行与组合行为 本节是第二章开篇,前一章已经浅显地将所有新概念点到,书中剩下的部分将对这些概念做一个基础知识的补充与深入探讨实践. 本章将介绍Lambda表达式基础知识. 前言 把上一张书中的结语放到这里作为本章学习内容的开头,以此来概括Lambda表达式的优点: 提升性能.自动的并行化 更棒的API(comparing(...)细粒度的方法将成为标准) 编码风格得到改进.代码简化 反观前面几篇文章中的代码实践,以上三个优点全部得到了验证.

  • 深入理解Java8新特性之Lambda表达式的基本语法和自定义函数式接口

    1.写在前面 目前我们学习Java主要用到的应该就是Java8了,或者说大部分企业当前使用的也是Java8.那么既然Java8的应用如此之广泛,一定有一些亮点所在: Lambda 表达式 函数式接口 方法引用与构造器引用 Stream API 接口中的默认方法与静态方法 新时间日期API 其他新特性 速度更快.代码更少(增加了新的语法 Lambda 表达式).强大的 Stream API.便于并行.最大化减少空指针异常 Optional. 2.为什么要使用Lambda表达式? Lambda 是一

  • C# Lambda表达式及Lambda表达式树的创建过程

    每次写博客,第一句话都是这样的:程序员很苦逼,除了会写程序,还得会写博客!当然,希望将来的一天,某位老板看到此博客,给你的程序员职工加点薪资吧!因为程序员的世界除了苦逼就是沉默.我眼中的程序员大多都不爱说话,默默承受着编程的巨大压力,除了技术上的交流外,他们不愿意也不擅长和别人交流,更不乐意任何人走进他们的内心! 题外话说多了,咱进入正题: 上一节中,我们讲到:在 2.0 之前的 C# 版本中,声明委托的唯一方法是使用命名方法.C# 2.0 引入了匿名方法,而在 C# 3.0 及更高版本中,La

  • Java中&&与?表达式结合时出现的坑

    前言 首先是背景,刚放假回家比较闲,就把以前写了一些算法题的一个项目拿出来继续写,想把其中的插入排序修改成支持升序和降序的,然后就出现了这个坑,具体是这样的: 先把插入排序的代码摆出来吧. /** * 插入排序 * @param arr 输入数组 * @param order 顺序 1为升序 0为降序 */ static void insertionSort(int arr[],int order){ for (int i = 1; i < arr.length; i++) { int get

  • java中Lamda表达式讲解

    Lamda表达式 1.λ 希腊字母表中排序第十一位的字母,英语名称为 Lamda 2.避免匿名内部类定义过多 3.可以让你的代码看起来很简洁 4.去掉了一堆没有意义的代码,留下核心的逻辑 5.其实质属于函数式编程的概念 (params)-> expression[表达式] (params) -> statement[语句] (params) -> {statements} a -> System.out.println("i like lamda-->"+

  • java中带参数的try(){}语法含义详解

    目录 带参数的try(){}语法含义 最简形式为 需要注意的是 对try(){}的简单理解 好比往FileOutputStream写东西 这样写很难受,可以进行优化 带参数的try(){}语法含义 带资源的try语句(try-with-resource) 最简形式为 try(Resource res = xxx)//可指定多个资源 {      work with res } try块退出时,会自动调用res.close()方法,关闭资源. PS:在coreJava第9版的第一卷的486页有解释

  • Java语法中Lambda表达式无法抛出异常的解决

    目录 Lambda表达式无法抛出异常 1.Demo 例子 2.编译通过 lambda表达式异常应该如何处理 我们看一个例子 Lambda表达式无法抛出异常 1.Demo 例子 错误提示 - Unhandled exception: java.io.IOException; public static void main(String[] args) throws IOException{ Stream.of("a", "b", "c").forE

  • java 8 lambda表达式中的异常处理操作

    简介 java 8中引入了lambda表达式,lambda表达式可以让我们的代码更加简介,业务逻辑更加清晰,但是在lambda表达式中使用的Functional Interface并没有很好的处理异常,因为JDK提供的这些Functional Interface通常都是没有抛出异常的,这意味着需要我们自己手动来处理异常. 因为异常分为Unchecked Exception和checked Exception,我们分别来讨论. 处理Unchecked Exception Unchecked exc

  • 关于Java 中的 Lambda 表达式

    这篇文章我们将讨论关于Java 中的 Lambda 表达式,Lambda 表达式是 Java 涉足函数式编程的过程.它接受参数并将其应用于表达式或代码块.以下是语法的基本示例: (parameter1, parameter2) => expression 或者 (parameter1, parameter2) => {code block} Lambda 表达式非常有限,如果它不是 void,则必须立即返回一个值.他们不能使用诸如 if 或 for 之类的关键字来保持简单性.如果需要更多行代码

  • Java中的反射,枚举及lambda表达式的使用详解

    目录 一.反射 1.1定义 1.2用途 1.3反射基本信息 1.4与反射相关的类 1.5Class类(反射机制的起源) 1.6Class类中的相关方法 1.7获得Class对象的三种方式 1.8反射的使用 1.9反射优点和缺点 二.枚举 2.1Enum类的常用方法 2.2枚举的优点和缺点 三.Lambda表达式 3.1Lambda表达式的语法及基本使用 3.2函数式接口 3.3变量捕获 总结 一.反射 1.1 定义 Java的反射(reflection)机制是在运行状态中,对于任意一个类,都能够

随机推荐