Java8_02_lambda表达式

时间:2023-02-17 23:20:22

一、前言

这一节我们来了解下lambda表达式,主要关注以下几点:

  • 行为参数化
  • 匿名类
  • Lambda 表达式
  • 方法 引用

二、行为参数化

1.概念

行为参数化(behavior parameterization)是用来处理频繁更改的需求的一种软件开发模式,可以将一段代码块当做参数传给另一个方法,稍后执行。这样,这个方法的行为就基于那块代码被参数化了。

也就是说 行为参数化: 让方法接受多种行为( 或战略) 作为参数, 并在内部使用, 来完成不同的行为

2.需求频繁更改的实例

现在,我们来看一个需求不断变化的场景:

我们需要为一个农场主开发一个农场库存程序

2.1 筛选绿苹果

一开始,农场主提出需要一个从列表中筛选出绿苹果的功能。

第一个解决方案可能是这样的:

	public static List<Apple> filterGreenApples(List<Apple> inventory){
List<Apple> result = new ArrayList<>();
for(Apple apple: inventory){
// 筛选处绿苹果
if("green".equals(apple.getColor())){
result.add(apple);
}
}
return result;
}

2.2 将颜色作为参数

但是现在农民改主意了, 他还想要筛选红苹果。 你该怎么做呢? 简单的解决办法就是复制这个方法, 把名字改成 filterRedApples, 然后更改 if 条件来匹配红苹果。 然而, 要是农民想要筛选多种颜色: 浅绿色、 暗红 色、 黄色 等, 这种方法就应付不了了。

一个良好的原则是在编写类似的代码之后,尝试将其抽象化

一种做法是给方法加一个参数,把颜色变成参数,这样就能灵活地适应变了:

	public static List<Apple> filterApplesByColor(List<Apple> inventory, String color){
List<Apple> result = new ArrayList<>();
for(Apple apple: inventory){
if(apple.getColor().equals(color)){
result.add(apple);
}
}
return result;
}

现在, 只要像下面这样调用方法,农民朋友就会满意了:

List< Apple> greenApples = filterApplesByColor( inventory, "green");
List< Apple> redApples = filterApplesByColor( inventory, "red");

2.3 对你能想到的每个属性做筛选

过了几天, 这位 农民又跑回来和你说:“ 要是能区分轻的苹果和重的苹果 就太好了。 重的苹果一般是重量大于150克。”

你 可以 将 颜色 和 重量 结合 为 一个 方法, 称为 filter。 不过 就算 这样, 你 还是 需要 一种 方式 来 区分 想要 筛选 哪个 属性。 你 可以 加上 一个 标志 来 区分 对 颜色 和 重量 的 查询( 但 绝不 要 这样做! 我们 很快 会 解释 为什么)。

一种 把 所有 属性 结合 起来 的 笨拙 尝试 如下 所示:

public static List<Apple> filterApples(List<Apple> inventory,String color, int weight, boolean flag){
List<Apple> result = new ArrayList<>();
for(Apple apple : inventory){
if((flag&&apple.getColor().equals(color)) ||
(!flag && apple.getWeight() > weight)){
result.add(apple);
}
}
return result;
}

你 可以 这么 用( 但 真的 很 笨拙):

List< Apple> greenApples = filterApples( inventory, "green", 0, true);
List< Apple> heavyApples = filterApples( inventory, "", 150, false);

这个 解决 方案 再 差 不 过了:

  • 首先, 客户 端 代码 看上去 糟透 了。 true 和 false 是什么 意思?
  • 此外, 这个 解决 方案 还是 不能 很好 地 应对 变化 的 需求。
    • 如果 这位 农民 要求 你对 苹果 的 不同 属性 做 筛选, 比如 大小、 形状、产地 等, 又 怎么办?
    • 而且, 如果 农民 要求 你 组合 属性, 做 更 复杂 的 查询, 比如 绿色 的 重 苹果, 又 该 怎么办?

      你会 有好 多个 重复 的 filter 方法, 或 一个 巨大 的 非常 复杂 的 方法。

2.4 利用策略模式,根据抽象筛选

让我 们 后退 一步 来看 看 更高 层次 的 抽象。

一种 可能 的 解决 方案 是对 你的 选择 标准 建模: 你 考虑 的 是 苹果, 需要 根据 Apple 的 某些 属性( 比如 它是 绿色 的 吗? 重量 超过 150 克 吗?) 来 返回 一个 boolean 值。 我们 把 它 称为 谓词( 即 一个 返回 boolean 值 的 函数)。

Java8_02_lambda表达式

如上图:让我 们 定义 一个 接口 来 对 选择 标准 建模:

public interface ApplePredicate{
boolean test(Apple apple);
}

现在 你就 可 以用 ApplePredicate 的 多个 实现 代表 不同 的 选择 标准 了,

//select only heavy apple
public class AppleHeavyWeightPredicate implements ApplePredicate{
public boolean test(Apple apple){
return apple.getWeight() > 150;
}
} //select only green apple
public class AppleGreenColorPredicate implements ApplePredicate{
public boolean test(Apple apple){
return "green".equals(apple.getColor);
}
} //select red and heavy apple
public static class AppleRedAndHeavyPredicate implements ApplePredicate{
public boolean test(Apple apple){
return "red".equals(apple.getColor())
&& apple.getWeight() > 150;
}
}

你 需要 filterApples 方法 接受 ApplePredicate 对象, 对 Apple 做 条件 测试。 这就 是 行为参数化: 让方法接受多种行为( 或战略) 作为参数, 并在内部使用, 来完成不同的行为

利用 ApplePredicate 改过 之后, filter 方法 看起来 是 这样 的:


public static List<Apple> filter(List<Apple> inventory, ApplePredicate p){
List<Apple> result = new ArrayList<>();
for(Apple apple : inventory){
if(p.test(apple)){
result.add(apple);
}
}
return result;
}

这时就能 参数化 filterApples 的行为, 并可以通过传递不同的筛选策略 来满足不同的筛选需求。

List<Apple> greenApples2 = filter(inventory, new AppleColorPredicate());

List<Apple> heavyApples = filter(inventory, new AppleWeightPredicate());

List<Apple> redAndHeavyApples = filter(inventory, new AppleRedAndHeavyPredicate());

这时我们完成了一件很酷的事: filterApples 方法的行为取决于你通过ApplePredicate 对象传递的代码。 换句话说, 你把 filterApples 方法的行为参数化了`

这时,如果看完后面的lambda再来看这里,就能知道: lambda内部的实现肯定也是使用策略模式来实现行为参数化的

不过这里有一个缺陷:

由于 该 filterApples 方法 只能 接受 对象, 所以 你 必须 把 代码 包裹 在 ApplePredicate 对象 里。 你的 做法 就 类似于 在 内联“ 传递 代码”, 因为 你是 通过 一个 实现 了 test 方法 的 对象 来 传递 布尔 表达式 的。

而通过使用lambda则可以解决这个问题。

三、匿名类

目前,当要把新的行为传递给 filterApples 方法的时候, 你不得不声明几个实现 ApplePredicate 接口的类, 然后实例化好几个只会提到 一次的 ApplePredicate 对象。

使用匿名类,可以避免这个问题。

1. 匿名类定义

使用匿名类能简化代码,能让你同时声明并实例化它[1]

匿名类定义格式:

new 父类构造器(参数列表)| 实现接口()
{
//匿名内部类的类体部分
}

2. 匿名类的用法:

首先得有一个给定的抽象类或者接口,然后我们通过匿名类去实现它。

3. 匿名类特点

  • 它必须继承一个类或者实现一个接口,而不能显示的使用extends或者implements,没有父类。
  • 匿名类没有构造方法。通过new<父类名> 创建对象,匿名类定义与创建对象是同时进行的。
  • 匿名类只能一次性的创建,并有父类句柄持有。
  • 匿名类不需要初始化,只有默认的构造方法

匿名内部类还有如下两条规则:

  • 匿名内部类不能是抽象类,因为系统在创建匿名内部类的时候,会立即创建内部类的对象。因此不允许将匿名内部类定义成抽象类。
  • 匿名内部类不能定义构造器,因为匿名内部类没有类名,所以无法定义构造器,但匿名内部类可以定义实例初始化块,通过实例初始化块来完成构造器需要完成的事情。

4. 匿名类使用示例

以下代码来自官方文档,展示了通过匿名类来实现接口的用法。

public class HelloWorldAnonymousClasses {

    interface HelloWorld {
public void greet();
public void greetSomeone(String someone);
} public void sayHello() { class EnglishGreeting implements HelloWorld {
String name = "world";
public void greet() {
greetSomeone("world");
}
public void greetSomeone(String someone) {
name = someone;
System.out.println("Hello " + name);
}
} HelloWorld englishGreeting = new EnglishGreeting(); //匿名类实现接口
HelloWorld frenchGreeting = new HelloWorld() {
String name = "tout le monde";
public void greet() {
greetSomeone("tout le monde");
}
public void greetSomeone(String someone) {
name = someone;
System.out.println("Salut " + name);
}
}; //匿名类实现接口
HelloWorld spanishGreeting = new HelloWorld() {
String name = "mundo";
public void greet() {
greetSomeone("mundo");
}
public void greetSomeone(String someone) {
name = someone;
System.out.println("Hola, " + name);
}
};
englishGreeting.greet();
frenchGreeting.greetSomeone("Fred");
spanishGreeting.greet();
} public static void main(String... args) {
HelloWorldAnonymousClasses myApp =
new HelloWorldAnonymousClasses();
myApp.sayHello();
}
}

5. 用匿名类改造农场示例

当有新的规则时,我们可以使用匿名类来实现 ApplePredicate 接口,来指定相应的过滤规则。

List<Apple> redApples2 = filter(inventory, new ApplePredicate() {
public boolean test(Apple a){
return a.getColor().equals("red");
}
});

四、Lambda 表达式

我们在前面通过匿名类进一步优化了我们的代码,尽管如此,还是有一些啰嗦。

若是我们使用lambda表达式的话,就会更简洁:

List< Apple> result = filterApples( inventory, (Apple apple) -> "red". equals( apple. getColor()));

在前面,我们了解了利用行为参数化来传递代码有助于应对不断变化的需求。 它允许你定义一个代码块来表示一个行为, 然后传递它。这样,我们就可以编写更为灵活且可重复使用的代码了。

1. 函数式接口与函数描述符

(1)函数式接口

函数式接口就是只定义一个抽象方法的接口。

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

(2)函数描述符

函数式接口的抽象方法的签名基本上就是 Lambda 表达式的签名。 我们将这种 抽象方法叫作函数描述符。

请注意这个概念:

函数描述符就是 Lambda 表达式的签名

2. lambda定义

定义:

可以把 Lambda 表达式 理解为 简洁地表示可传递的匿名函数的一种方式: 它没有名称, 但它有参数列表函数主体返回类型, 可能还有一个可以抛出的异常列表

注意:

(1)Lambda 表达式允许你直接内联, 为函数式接口的抽象方法提供实现, 并且将整个表达式作为函数式接口的一个实例

(2)可以将lambda表达式看作匿名类功能

(3)它其实就是为函数式接口生成了一个实例。

基本语法如下:

(parameters) -> expression

(parameters) -> { statements; }

3.lambda示例

// 1.函数式接口
@FunctionalInterface
public interface Predicate< T>{
//2.函数描述符
boolean test( T t);
} //过滤方法
public static < T> List< T> filter( List< T> list, Predicate< T> p) {
List< T> results = new ArrayList<>();
for( T s: list){
if( p. test( s)){
results. add( s); } } return results;
} // lambda表达式与函数描述符对应
Predicate< String> nonEmptyStringPredicate = (String s) -> !s. isEmpty(); List< String> nonEmpty = filter( listOfStrings, nonEmptyStringPredicate);

4.使用范围

可以在函数式接口上使用 Lambda 表达式。

5.类型推断

Lambda 的类型是从使用 Lambda 的上下文推断出来的。

上下文( 比如,接受它传递的方法的参数, 或 接受它的值的局部变量)中Lambda表达式需要的类型称为目标类型。

//不用类型推断
Comparator< Apple> c = (Apple a1, Apple a2) -> a1. getWeight(). compareTo( a2. getWeight()); //使用类型推断
Comparator< Apple> c = (a1, a2) -> a1. getWeight(). compareTo( a2. getWeight());

五、方法引用

(1)方法引用可以被看作仅仅调用特定方法的 Lambda 的一种快捷写法。

(2)它的基本思想是, 如果一个 Lambda代表的只是“ 直接调用这个方法”, 那最好还是用名称来调用它, 而不是去描述如何调用它。

(3)方法引用就是让你根据已有的方法实现来创建 Lambda 表达式

(4)你可以把方法引用看作针对仅仅涉及单一方法的 Lambda 的语法糖

1. 格式

目标引用 :: 方法名

2.三类方法引用

方法引用主要有三类:

  • (1) 指向静态方法的方法引用( 例如 Integer 的 parseInt 方法, 写作 Integer:: parseInt)。
  • (2) 指向任意类型实例方法的方法引用( 例如 String 的 length 方法, 写作 String:: length)。
  • (3) 指向现有对象的实例方法的方法引用( 假设你有一个局部变量 expensiveTransaction 用于存放 Transaction 类型的对象, 它支持实例方法 getValue, 那么你就可以写 expensiveTransaction:: getValue)。

类似于 String:: length 的 第二 种 方法 引用 的 思想 就是 你在 引用 一个 对象 的 方法, 而这 个 对象 本身 是 Lambda 的 一个 参数。 例如, Lambda 表达式( String s) -> s. toUppeCase() 可以 写作 String:: toUpperCase。

但 第三 种 方法 引用 指的 是, 你在 Lambda 中 调用 一个 已经 存在 的 外部 对象 中的 方法。 例如, Lambda 表达式()-> expensiveTransaction. getValue() 可以 写作 expensiveTransaction:: getValue。

Java8_02_lambda表达式

3.示例

Java8_02_lambda表达式

六、参考资料

  1. Anonymous Classes - The Java™ Tutorials

    Java匿名对象和匿名类总结

Java8_02_lambda表达式的更多相关文章

  1. 【&period;net 深呼吸】细说CodeDom(2):表达式、语句

    在上一篇文章中,老周厚着脸皮给大伙介绍了代码文档的基本结构,以及一些代码对象与CodeDom类型的对应关系. 在评论中老周看到有朋友提到了 Emit,那老周就顺便提一下.严格上说,Emit并不是针对代 ...

  2. 你知道C&num;中的Lambda表达式的演化过程吗?

    那得从很久很久以前说起了,记得那个时候... 懵懂的记得从前有个叫委托的东西是那么的高深难懂. 委托的使用 例一: 什么是委托? 个人理解:用来传递方法的类型.(用来传递数字的类型有int.float ...

  3. 再讲IQueryable&lt&semi;T&gt&semi;,揭开表达式树的神秘面纱

    接上篇<先说IEnumerable,我们每天用的foreach你真的懂它吗?> 最近园子里定制自己的orm那是一个风生水起,感觉不整个自己的orm都不好意思继续混博客园了(开个玩笑).那么 ...

  4. Linq表达式、Lambda表达式你更喜欢哪个?

    什么是Linq表达式?什么是Lambda表达式? 如图: 由此可见Linq表达式和Lambda表达式并没有什么可比性. 那与Lambda表达式相关的整条语句称作什么呢?在微软并没有给出官方的命名,在& ...

  5. 背后的故事之 - 快乐的Lambda表达式(一)

    快乐的Lambda表达式(二) 自从Lambda随.NET Framework3.5出现在.NET开发者眼前以来,它已经给我们带来了太多的欣喜.它优雅,对开发者更友好,能提高开发效率,天啊!它还有可能 ...

  6. Kotlin的Lambda表达式以及它们怎样简化Android开发&lpar;KAD 07&rpar;

    作者:Antonio Leiva 时间:Jan 5, 2017 原文链接:https://antonioleiva.com/lambdas-kotlin/ 由于Lambda表达式允许更简单的方式建模式 ...

  7. SQL Server-表表达式基础回顾(二十四)

    前言 从这一节开始我们开始进入表表达式章节的学习,Microsoft SQL Server支持4种类型的表表达式:派生表.公用表表达式(CTE).视图.内嵌表值函数(TVF).简短的内容,深入的理解, ...

  8. 立即执行函数表达式(IIFE)

    原文地址:benalman.com/news/2010/11/immediately-invoked-function-expression/ 译者:nzbin 也许你还没有注意到,我是一个对术语比较 ...

  9. javascript:逆波兰式表示法计算表达式结果

    逆波兰式表示法,是由栈做基础的表达式,举个例子: 5 1 2 + 4 * + 3 -  等价于   5 + ((1 + 2) * 4) - 3 原理:依次将5 1 2 压入栈中, 这时遇到了运算符 + ...

随机推荐

  1. EF错误记录

    纯属个人记录错误使用: 1.EntityType“area”未定义键.请为该 EntityType 定义键. 产生原因: 1.命名空间引用错误,可能命名重复导致引用错误 2.实体类无法识别主键或者未设 ...

  2. HBase 的存储结构

    HBase 的存储结构 2016-10-17 杜亦舒 HBase 中的表常常是超级大表,这么大的表,在 HBase 中是如何存储的呢?HBase 会对表按行进行切分,划分为多个区域块儿,每个块儿名为  ...

  3. 【Cocos2d-x】源代码分析之 2d&sol;ui&sol;Widget

    从今天開始 咱也模仿 红孩儿这些大牛分析源代码 ,因为水平有限 不正确之处欢迎狂喷.哈哈. #ifndef __UIWIDGET_H__ #define __UIWIDGET_H__ #include ...

  4. springboot情操陶冶-初识springboot

    前言:springboot由于其轻便和去配置化等的特性已经被广泛应用,基于时代潮流以及不被鄙视,笔者于是开辟此篇开始认识springboot 前话 springboot是基于spring而开发的轻量级 ...

  5. C&num;:关于C&num;4中IEnumerable&lt&semi;out T&gt&semi;的理解

    IEnumerable<out T>这个接口非常常见,它是最基础的泛型集合接口,表示可迭代的项的序列. 但是奇怪的是为什么泛型参数要带一个“out”? 经过一番资料查阅后,发现此“out” ...

  6. 线程同步-SpinWait

    这次将描述如何不适用内核模式的方式来使线程等待.SpinWait,它是一个混合同步构造,被设计为使用用户模式等待一段时间,然后切换到内核模式以节省CPU时间. 代码Demo: using System ...

  7. python settings &colon;RROR 1130&colon; Host &&num;39&semi;XXXXXX&&num;39&semi; is not allowed to connect to this MySQL server

    pymysql.err.InternalError: (1130, u"Host '127.0.0.1' is not allowed to connect to this MySQL se ...

  8. apache提示没有设置 max-age or expires解决办法

    大家看到这个就应该知道只要设置 max-age or expires就行了.下面说的方法是在设置 apache下的方法: 产生要开启 代码如下 复制代码 LoadModule headers_modu ...

  9. HSSFWorkBooK用法 —Excel表的导出和设置

    HSSFWorkBooK用法 —Excel表的导出和设置 2013年02月21日 ⁄ 综合 ⁄ 共 9248字 ⁄ 字号 小 中 大 ⁄ 评论关闭 public ActionResult excelP ...

  10. 【树】Lowest Common Ancestor of a Binary Tree(递归)

    题目: Given a binary tree, find the lowest common ancestor (LCA) of two given nodes in the tree. Accor ...