1. 语法

首先我们要知道如何写Lambda表达式,或者说怎么样才能写出有效的Lambda表达式,这就需要了解其语法。

Lambda表达式由三部分组成:

  1. 参数列表

  2. 箭头

  3. 主体

两种风格,分别是:

  1. 表达式-风格

    (parameters) -> expression

  2. 块-风格

    (parameters) -> { statements; }

依据上面的风格介绍,来试着判断下面给出的示例是否有效:

() -> {}() -> "Apple"() -> { return "Apple"; }(Integer i) -> return "Apple" + i(String s) -> { "Apple"; }

解析:(1)是块风格,没有语句;(2)是表达式风格,一个字符串表达式;(3)是块风格,有花括号和返回语句;(4)非有效,写了返回语句,但缺少花括号,补上花括号和分号,为块风格,而去掉return则为表达式风格;(5)非有效,"Apple"是一个字符串表达式,不是一个语句,加上return,或者去掉分号和花括号。

2. 函数式接口

Lambda表达式写好了,我们要知道哪里能用Lambda表达式。已知Lambda表达式可看作是匿名内部类的实现,那对于匿名内部类来说最重要的是类所实现的接口,而Lambda表达式是否可用于所有接口?答案“不是的”,Lambda表达式对接口有一定的要求,必须是函数式接口

所谓的函数式接口指的是只定义一个抽象方法的接口

例如:

public interface Comparator<T> {int compare(T o1, T o2);
}public interface Runnable {void run();
}public interface Callable<V> {V call() throws Exception;
}

可见上面三个接口都只有一个抽象方法,但是三个方法的签名都不一样,这要求Lambda表达式与实现接口的方法签名要一致。下面用函数描述符来表示上述三个方法的签名,箭头前面是方法的入参类型,后面是返回类型。

  1. compare:(T, T) -> int,两个泛型T类型的入参,返回int类型

    Lambda表达式:(Apple a1, Apple a2) -> a1.getWeight - a2.getWeight

  2. run:() -> void,无入参,无返回值

    Lambda表达式:() -> { System.out.println("Hi"); }

  3. call:() -> V,无入参,返回一个泛型V类型的对象

    Lambda表达式:() -> new Apple()

看call方法的示例,你是否会疑惑,new Apple()是一个语句,为什么没有花括号和分号,是不是非有效的。你需要记住这是合法的,这是一个特殊的规定,不需要用括号环绕返回值为void的单行方法调用

3. 常用的函数式接口

下面介绍在Java中内置的常用Lambda表达式:

3.1 Predicate

public interface Predicate<T> {boolean test(T t);
}

test:T -> boolean,接收一个泛型T对象,返回一个boolean。

适用场景:表示一个涉及类型T的布尔表达式。

// 判断空白字符串
Predicate<String> blankStrPredicate = s -> s != null && s.trim().length() == 0;
blankStrPredicate.test("  "); // true
// 判断苹果重量是否大于150
Predicate<Apple> heavyApplePredicate = a -> a.getWeight() > 150;
heavyApplePredicate.test(new Apple(100)); // false

注意,参数部分缺少了参数类型,是因为可根据上下文推断出Lambda表达式的参数类型,所以可以省略不写。比如这里因为将Lambda表达式赋值给一个Predicate类型的变量,又因为函数描述符为(T) -> boolean,则可推断出参数T的实际类型为String。而且当只有一个参数时,可以将括号也省略。

3.2 Consumer

public interface Consumer<T> {void accept(T t);
}

accept:T -> void,接收一个泛型T对象,无返回值(void)。

适用场景:访问类型T的对象,对其执行某些操作。

// 打印苹果重量
Consumer<Apple> appleWeighter = a -> System.out.println("The apple weights " + a.getWeight() + " grams");
appleWeighter.accept(new Apple(200));
// The apple weights 200 grams

3.3 Supplier

public interface Supplier<T> {T get();
}

get:() -> T,无入参,返回一个泛型T对象。

适用场景:定义类型T的对象的生产规则。

Consumer<Apple> appleWeighter =(a) -> System.out.println("The apple weights " + a.getWeight() + " grams");
// 生产200克的苹果
Supplier<Apple> heavyAppleSupplier = () -> new Apple(200);
appleWeighter.accept(heavyAppleSupplier.get());

3.4 Function

public interface Function<T, R> {R apply(T t);
}

apply:T -> R,接受一个泛型T对象,返回一个泛型R对象

适用场景:将输入对象转换输出。

double unitPrice = 0.01;
// 计算苹果价格
Function<Apple, Double> priceAppleFunction = a -> a.getWeight() * unitPrice;
priceAppleFunction.apply(new Apple(100)); // 1

这里做个补充,上面这段代码特别的地方在于使用到了外部的局部变量。Lambda表达式使用外部变量有什么要求?对于Lambda表达式所在的主体(类)的实例变量和静态变量,可以无限制使用,但局部变量必须显示声明为final或实际上是final的。

声明为final好理解,什么是实际上是final的,意思就是不能被代码进行修改,比如这里的unitPrice虽然没有声明为final,但后续的代码并没有修改该变量,所以实际上也是final的。感兴趣的读者可以自己试下,对unitPrice进行修改,看下会发生什么。

3.5 Comparator

public interface Comparator<T> {int compare(T o1, T o2);
}

compare:(T, T) -> int,两个泛型T类型的入参,返回int类型

适用场景:比较两个对象

Comparator<Apple> weightComparator = (a1, a2) -> a1.getWeight() - a2.getWeight();
weightComparator.compare(new Apple(100), new Apple(150)); // -1

4. 方法引用

Java还提供了一种更简洁的写法,先上示例:

Function<Apple, Integer> weightor = a -> a.getWeight();

可改写为:

Function<Apple, Integer> weightor = Apple::getWeight;

这种写法被称作方法引用,仅当在Lambda表达式中直接调用了一个方法时可以使用。其写法为目标引用::方法名称

根据目标引用可分为三类:

(1)指向静态方法的方法引用

目标引用为,调用其静态方法,例如:

Function<String, Integer> fun = s -> Integer.parseInt(s);

调用了 Integer 的静态方法 parseInt,可写为:

Function<String, Integer> fun = Integer::parseInt;

(2)指向任意类型实例方法的方法引用

目标引用为实例对象,调用其实例方法,例如:

Function<String, Integer> fun = s -> s.length();

调用了 String 类型实例 s 的 length 方法,可写为:

Function<String, Integer> fun = String::length;

目标引用写实例的类型。和第一种写法相同,只不过第一种的方法是静态方法,这里是实例方法。

(3)指向现存外部对象实例方法的方法引用

目标引用为现存外部对象,调用其实例方法,例如:

String s = "草捏子";
Supplier<Integer> len = () -> s.length();

调用了局部变量 s 的 length 方法,可写为:

String s = "草捏子";
Supplier<Integer> len = s::length;

目标引用写变量名,区别于前两种。

5. 复合Lambda表达式

之前的例子都是使用的单个Lambda表达式,现在我们把多个Lambda表达式组合在一起,构建更复杂一点的表达式。

5.1 比较器复合(Comparator)

我们使用 Comparator 对苹果进行排序,按重量从小到大:

List<Apple> apples = Arrays.asList(new Apple("red", 50), new Apple("red", 100), new Apple("green", 100));
apples.sort(Comparator.comparing(Apple::getWeight));

对 Comparator 的静态方法comparing 简单介绍下,接受一个 Function 类型的参数,返回一个 Comparator 类型的实例,定义如下:

public static <T, U extends Comparable<? super U>> Comparator<T> comparing(Function<? super T, ? extends U> keyExtractor)
{Objects.requireNonNull(keyExtractor);return (Comparator<T> & Serializable)(c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
}

通过使用compareTo,实现了重量从小到大的排序,那想按重量从大到小排序,怎么办呢?可以使用 Comparator 的 reversed 方法

apples.sort(Comparator.comparing(Apple::getWeight).reversed());

reversed 的实现如下:

default Comparator<T> reversed() {return Collections.reverseOrder(this);
}

使用工具类得到一个反序的 Comparator。你可能会好奇Comparator 作为一个接口,reversed 方法可以有具体的实现,接口的实例方法应该都是抽象方法,那它还是一个有效的函数式接口吗,或者说还是一个有效的接口吗?

回想下第二节的内容,函数式接口是只定义一个抽象方法的接口。Comparator的抽象方法只有一个 compare,其他是具体方法,所以是合法的函数式接口。那么接口中为什么能定义具体方法呢?Java8 之前是不支持的,但在 Java8 中引入了 default 关键字

当在接口中用default声明一个方法时,允许它是一个具体方法。这样的好处在于,我们可以在Lambda表达式之后直接跟上一个具体方法,对Lambda表达式增强,实现更复杂的功能。在后文介绍的用于复合表达式的方法都是接口中的 default 方法。

下面我们试着实现更复杂的排序,在按重量从大到小排序后,按颜色排序:

apples.sort(Comparator.comparing(Apple::getWeight).reversed());
apples.sort(Comparator.comparing(Apple::getColor));

先后用两个Comparator。而使用 Comparator 的 thenComparing 方法可以继续连接一个 Comparator,从而构建更复杂的排序:

apples.sort(Comparator.comparing(Apple::getWeight).reversed().thenComparing(Apple::getColor));

5.2 谓词复合(Predicate)

Predicate 的 test 方法 (T) -> boolean返回一个布尔表达式。类似 Java 在为布尔表达式提供的与或非,Predicate中也有对应的方法 andornegate。例如:

// 重的苹果
Predicate<Apple> heavyApple = a -> a.getWeight() > 100;
// 红的苹果
Predicate<Apple> redApple = a -> a.getColor().equals("red");// 轻的苹果
Predicate<Apple> lightApple = heavyApple.negate();
// 不红的苹果
Predicate<Apple> nonRedApple = redApple.negate();
// 重且红的苹果
Predicate<Apple> heavyAndRedApple = heavyApple.and(redApple);
// 重或红的苹果
Predicate<Apple> heavyOrRedApple = heavyApple.or(redApple);

5.3 函数复合(Function)

Function (T) -> R,对输入做映射。我们通过将多个Function进行组合,实现将一个Function的输出作为另一个Function的输入,是不是有管道的感觉。下面请看具体的方法。

andThen方法a.andThen(b),将先执行a,再执行b。

Function<Integer, Integer> f = x -> x + 1;
Function<Integer, Integer> g = x -> x * 2;
Function<Integer, Integer> h = f.andThen(g);
int result = h.apply(1); // 4

compose方法a.compose(b),将先执行b,再执行a。

Function<Integer, Integer> f = x -> x + 1;
Function<Integer, Integer> g = x -> x * 2;
Function<Integer, Integer> h = f.compose(g);
int result = h.apply(1); // 3

系统学习Lambda表达式相关推荐

  1. JAVA学习——Lambda表达式是干什么用的?

    今天又一次接触到了Lambda表达式,之前的博客只知道其形不明白其意,这次终于了解比较深刻了.总而言之Lambda表达式就是用来简化代码的,是JDK8的新特性,不用Lambda表达式一样可以写,只是稍 ...

  2. 深入理解Java Lambda表达式,匿名函数,闭包

    前言 对于Lambda表达式一直是知其然不知其所以然,为了搞清楚什么是Lambda表达式,以及Lambda表达式的用法和作用,本文应运而生当做学习笔记分享出来,欢迎指正交流. 什么是Lambda 让我 ...

  3. Java8 新特性lambda表达式(一)初始

    本篇参考Richard Warburton的 java8 Lambdas :Functional Programming for the Masses 学习lambda表达式之前,需要知道什么是函数式 ...

  4. 1.Lambda表达式(新手写的!新手写的!新手写的!)(未完成)

    作为19届毕业的学C#的学生,我开始了找工作的经历,前段时间面试了几家公司,由于自己在大学的基础知识学的不是很扎实,所以没有应聘上.现在写文章整理知识,增加自己的知识,顺便记录自己的成长史吧,不对的请 ...

  5. 深入浅出 Java 8 Lambda 表达式

    摘要:此篇文章主要介绍 Java8 Lambda 表达式产生的背景和用法,以及 Lambda 表达式与匿名类的不同等.本文系 OneAPM 工程师编译整理. Java 是一流的面向对象语言,除了部分简 ...

  6. c++的lambda表达式捕获this_贯穿 C++ 11 与 C++ 17 的 Lambda 到底是个什么?

    本文将详解Lambda函数从定义到学习和使用,涉及一些不为人知的事情,如LIFE-立即调用的函数表达式,Lambda的类型.相信你已经起了兴趣,那就开始阅读吧. 作者 | Vishal Chovati ...

  7. LinQ—Lambda表达式

    概述 本篇博客主要解说lambda表达式,在这里将它的来龙去脉,主要是从托付,匿名函数这些方面过度讲的,当然,在讲托付和匿名函数的时候,主要是从Lambda的角度出发讲的,可能它们还具有其他的一些作用 ...

  8. java8 Lambda表达式的应用(函数式接口、lambda表达式,方法引用及Stream API)

    之前写了一篇博客简单介绍了一下java 8发布新增的一些特性功能,java 8在2014年发布,距今也不少年了,但是lambda表达式使用并不熟练,现在一边学习,一边记录一下. 目录 一.Lambda ...

  9. lambda表达式 拉姆达

    lambda表达式 拉姆达 学习这个之前,需要明确 有一种接口叫做函数式接口,只允许接口里面只有一个函数 可以使用 @FunctionalInterface进行注解 如果这个接口里面定义了两个方法,就 ...

最新文章

  1. 查看目标网站--结构
  2. Access SQL中Left Join、Right Join和Inner Join的使用
  3. 763. Partition Labels 划分字母区间
  4. 非文学翻译理论与实践_北外“欧洲非通用语文学翻译与研究中心”正式成立
  5. php过去mysql数据表是空_PHP向mysql中写数据,在phpmyadmin中为空,直接打印有数据?...
  6. MySQL DQL语言的笔记
  7. requests cookie
  8. 双“11”搞促销?用贪心算法盘它
  9. rocketmq下单支付场景
  10. User-Agent 汇总
  11. 高等数学(第七版)同济大学 习题2-1 个人解答
  12. JAVA第11章枚举与泛型总结
  13. 英语四级单选测试软件,英语四级报告单选纸质还是电子?选择电子版报告单可能更好...
  14. ios 系统状态栏样式修改_iOS 导航栏颜色和状态栏颜色修改
  15. python纳甲装卦
  16. 武林大会之国产数据库风云榜-2021年11月
  17. 指纹锁—AS608指纹模块
  18. 耳机麦克风正常,但是部分PC应用不能语音的问题!
  19. 管理和维护数据完整性
  20. 学历和专业对程序员找工作很重要吗?

热门文章

  1. eclipse导出jar包
  2. vue源码-对于「计算属性」的理解
  3. OpenGL 与 GLSL 版本号
  4. 《数据库原理与应用(第3版)》——1.4 数据库系统的组成
  5. iOS sql的简单封装
  6. HDMI_VGA_CBVS同时显示
  7. 1.在VS2010中文版中开发WP7程序
  8. oracle创建表空间.创建用户.创建表
  9. IT经理世界:专注莫如史玉柱
  10. 五十八种网络故障及其解决办法