0

0

深入理解Java中的Lambda表达式

高洛峰

高洛峰

发布时间:2017-01-23 15:58:52

|

1560人浏览过

|

来源于php中文网

原创

 java 8 开始出现,带来一个全新特性:使用 lambda 表达式 (jsr-335) 进行函数式编程。今天我们要讨论的是 lambda 的其中一部分:虚拟扩展方法,也叫做公共辩护(defender)方法。该特性可以让你在接口定义中提供方法的默认实现。例如你可以为已有的接口(如 list 和 map)声明一个方法定义,这样其他开发者就无需重新实现这些方法,有点像抽象类,但实际却是接口。当然,java 8 理论上还是兼容已有的库。

虚拟扩展方法为 Java 带来了多重继承的特性,尽管该团队声称与多重继承不同,虚拟扩展方法被限制用于行为继承。或许通过这个特性你可以看到了多重继承的影子。但你还是可以模拟实例状态的继承。我将在接下来的文章详细描述 Java 8 中通过 mixin 混入实现状态的继承。

什么是混入 mixin?

混入是一种组合的抽象类,主要用于多继承上下文中为一个类添加多个服务,多重继承将多个 mixin 组合成你的类。例如,如果你有一个类表示“马”,你可以实例化这个类来创建一个“马”的实例,然后通过继承像“车库”和“花园”来扩展它,使用 Scala 的写法就是:
 
val myHouse = new House with Garage with Garden

从 mixin 继承并不是一个特定的规范,这只是用来将各种功能添加到已有类的方法。在 OOP 中,有了 mixin,你就有通过它来提升类的可读性。

立即学习Java免费学习笔记(深入)”;

例如在 Python 的  socketserver 模块中就有使用 mixin 的方法,在这里,mixin 帮助 4 个基于不同 Socket 的 服务,包括支持多进程的 UDP 和 TCP 服务以及支持多线程的 UDP 和 TCP 服务。

 Java 8 开始出现,带来一个全新特性:使用 Lambda 表达式 (JSR-335) 进行函数式编程。今天我们要讨论的是 Lambda 的其中一部分:虚拟扩展方法,也叫做公共辩护(defender)方法。该特性可以让你在接口定义中提供方法的默认实现。例如你可以为已有的接口(如 List 和 Map)声明一个方法定义,这样其他开发者就无需重新实现这些方法,有点像抽象类,但实际却是接口。当然,Java 8 理论上还是兼容已有的库。
虚拟扩展方法为 Java 带来了多重继承的特性,尽管该团队声称与多重继承不同,虚拟扩展方法被限制用于行为继承。或许通过这个特性你可以看到了多重继承的影子。但你还是可以模拟实例状态的继承。我将在接下来的文章详细描述 Java 8 中通过 mixin 混入实现状态的继承。
什么是混入 mixin?
混入是一种组合的抽象类,主要用于多继承上下文中为一个类添加多个服务,多重继承将多个 mixin 组合成你的类。例如,如果你有一个类表示“马”,你可以实例化这个类来创建一个“马”的实例,然后通过继承像“车库”和“花园”来扩展它,使用 Scala 的写法就是:
 
val myHouse = new House with Garage with Garden
从 mixin 继承并不是一个特定的规范,这只是用来将各种功能添加到已有类的方法。在 OOP 中,有了 mixin,你就有通过它来提升类的可读性。
例如在 Python 的  socketserver 模块中就有使用 mixin 的方法,在这里,mixin 帮助 4 个基于不同 Socket 的 服务,包括支持多进程的 UDP 和 TCP 服务以及支持多线程的 UDP 和 TCP 服务。

什么是虚拟扩展方法?


Java 8 将引入虚拟扩展方法的概念,也叫 public defender method. 让我们姑且把这个概念简化为 VEM。

VEM 旨在为 Java 接口提供默认的方法定义,你可以用它在已有的接口中添加新的方法定义,例如 Java 里的集合 API。这样类似 Hibernate 这样的第三方库无需重复实现这些集合 API 的所有方法,因为已经提供了一些默认方法。

下面是如何在接口中定义方法的示例:

public interface Collection extends Iterable {
  
   Collection filter(Predicate p)
    default { return Collections.filter(this, p); }
  
}

Java 8 对混入的模拟

现在我们来通过 VEM 实现一个混入效果,不过事先警告的是:请不要在工作中使用!

下面的实现不是线程安全的,而且还可能存在内存泄露问题,这取决于你在类中定义的 hashCode 和 equals 方法,这也是另外一个缺点,我将在后面讨论这个问题。

首先我们定义一个接口(模拟状态Bean)并提供方法的默认定义:

public interface SwitchableMixin {
  boolean isActivated() default { return Switchables.isActivated(this); }
  void setActivated(boolean activated) default { Switchables.setActivated(this, activated); }
}

然后我们定义一个工具类,包含一个 Map 实例来保存实例和状态的关联,状态通过工具类中的私有的嵌套类代表:

public final class Switchables {
  
  private static final Map SWITCH_STATES = new HashMap<>();
  
  public static boolean isActivated(SwitchableMixin device) {
    SwitchableDeviceState state = SWITCH_STATES.get(device);
    return state != null && state.activated;
  }
  
  public static void setActivated(SwitchableMixin device, boolean activated) {
    SwitchableDeviceState state = SWITCH_STATES.get(device);
    if (state == null) {
      state = new SwitchableDeviceState();
      SWITCH_STATES.put(device, state);
    }
    state.activated = activated;
  }
  
  private static class SwitchableDeviceState {
    private boolean activated;
  }
  
}

这里是一个使用用例,突出了状态的继承:

private static class Device {}
  
private static class DeviceA extends Device implements SwitchableMixin {}
  
private static class DeviceB extends Device implements SwitchableMixin {}

“完全不同的东西”

上面的实现跑起来似乎挺正常的,但 Oracle 的 Java 语言架构师 Brian Goetz 向我提出一个疑问说当前实现是无法工作的(假设线程安全和内存泄露问题已解决)

interface FakeBrokenMixin {
  static Map backingMap
    = Collections.synchronizedMap(new WeakHashMap());
  
  String getName() default { return backingMap.get(this); }
  void setName(String name) default { backingMap.put(this, name); }
}
  
interface X extends Runnable, FakeBrokenMixin {}
  
X makeX() { return () -> { System.out.println("X"); }; }
  
  X x1 = makeX();
  X x2 = makeX();
  x1.setName("x1");
  x2.setName("x2");
  
  System.out.println(x1.getName());
  System.out.println(x2.getName());


你猜这段代码执行后会显示什么结果呢?
疑问的解决

第一眼看去,这个实现的代码没有问题。X 是一个只包含一个方法的接口,因为 getName 和 setName 已经有了默认的定义,但 Runable 接口的 run 方法没有定义,因此我们可通过 lambda 表达式来生成 X 的实例,然后提供 run 方法的实现,就像 makeX 那样。因此,你希望这个程序执行后显示的结果是:

x1
x2

如果你删掉 getName 方法的调用,那么执行结果变成:

商达讯网店中英繁系统免费版
商达讯网店中英繁系统免费版

sdxecShop是一款完全开源免费的网上独立建店系统,asp+access,程序经过专业团队开发升级发展了7年,功能和安全性已经达到非常成熟稳定,安装容易,一分钟就可以搭起专业的电子商务网站。 该免费版功能完整和正式版完全一样永久免费,只是正式版提供后续技术支持服务,主要特色功能中英繁版统一后台管理统一数据,淘宝数据表导入,实现网店和淘宝网店数据统一,拓展网店经营策略,提供5种在线支付接口等等

下载
MyTest$1@30ae8764
MyTest$1@123acf34

这两行显示出 makeX 方法的执行来自两个不同的实例,而这时当前 OpenJDK 8 生成的(这里我使用的是 OpenJDK 8 24.0-b07).

不管怎样,当前的 OpenJDK 8 并不能反映最终的 Java 8 的行为,为了解决这个问题,你需要使用特殊参数 -XDlambdaToMethod 来运行 javac 命令,在使用了这个参数后,运行结果变成:

x2
x2

如果不调用 getName 方法,则显示

MyTest$$Lambda$1@5506d4ea
MyTest$$Lambda$1@5506d4ea

每个调用 makeX 方法似乎都是来自相同匿名内部类的一个单例实例,如果观察包含编译后的 java class 文件的目录,会发现并没有一个名为 MyTestClass$$Lambda$1.class 的文件。

因为在编译时,lambda 表达式并没有经过完整的翻译,事实上这个翻译过程是在编译和运行时完成的,javac 编译器将 lambda 表达式变成 JVM 新增的指令 invokedynamic (JSR292)。这个指令包含所有必须的关于在运行时执行 lambda 表达式的元信息。包括要调用的方法名、输入输出类型以及一个名为 bootstrap 的方法。bootstrap 方法用于定义接收此方法调用的实例,一旦 JVM 执行了 invokedynamic 指令,JVM 就会在特定的 bootstrap 上调用 lambda 元工厂方法 (lambda metafactory method)。

再回到刚才那个疑问中,lambda 表达式转成了一个私有的静态方法,() -> { System.out.println("X"); } 被转到了 MyTest:

private static void lambda$0() {
  System.out.println("X");
}

如果你用 javap 反编译器并使用 -private 参数就可以看到这个方法,你也可以使用 -c 参数来查看更加完整的转换。

当你运行程序时,JVM 会调用 lambda metafactory method 来尝试阐释 invokedynamic 指令。在我们的例子中,首次调用 makeX 时,lambda metafactory method 生成一个 X 的实例并动态链接 run 方法到 lambda$0 方法. X 的实例接下来被存储在内存中,当第二次调用 makeX 时就直接从内存中读取这个实例,因此你第二次调用的实例跟第一次是一样的。
修复了吗?有解决办法吗?

目前尚无这个问题直接的修复或者是解决办法。尽管 Oracle 的 Java 8 计划默认激活-XDlambdaToMethod 参数,因为这个参数并不是 JVM 规范的一部分,因此不同供应商和 JVM 的实现是不同的。对一个 lambda 表达式而言,你唯一能期望的就是在类中实现你的接口方法。


其他的方法

到此为止,尽管我们对 mixin 的模仿并不能兼容 Java 8,但还是可能通过多继承和委派为已有的类添加多个服务。这个方法就是 virtual field pattern (虚拟字段模式).

所以来看看我们的 Switchable.

interface Switchable {  boolean isActive();
  void setActive(boolean active);
}

我们需要一个基于 Switchable 的接口,并提供一个附加的抽象方法返回 Switchable 的实现。集成的方法包含默认的定义,它们使用 getter 来转换到 Switchable 实现的调用:

public interface SwitchableView extends Switchable {
  Switchable getSwitchable();
  
  
  boolean isActive() default { return getSwitchable().isActive(); }
  void setActive(boolean active) default { getSwitchable().setActive(active); }
}

接下来,我们创建一个完整的 Switchable 实现:

public class SwitchableImpl implements Switchable {
  
  
  private boolean active;
  
  
  @Override
  public boolean isActive() {
    return active;
  }
  
  
  @Override
  public void setActive(boolean active) {
    this.active = active;
  }
}

   

这里是我们使用虚拟字段模式的例子:

public class Device {}
  
  
public class DeviceA extends Device implements SwitchableView {
  private Switchable switchable = new SwitchableImpl();
  
  
  @Override
  public Switchable getSwitchable() {
    return switchable;
  }
}
  
  
public class DeviceB extends Device implements SwitchableView {
  private Switchable switchable = new SwitchableImpl();
  
  
  @Override
  public Switchable getSwitchable() {
    return switchable;
  }
}

结论

在这篇文章中,我们使用了两种方法通过 Java 8 的虚拟扩展方法为类增加多个服务。第一个方法使用一个 Map 来存储实例状态,这个方法很危险,因为不是线程安全而且存在内存泄露问题,这完全依赖于不同的 JVM 对 Java 语言的实现。另外一个方法是使用虚拟字段模式,通过一个抽象的 getter 来返回最终的实现实例。第二种方法更加独立而且更加安全。

虚拟扩展方法是 Java 的新特性,本文主要介绍的是多重继承的实现,详细你会有更深入的研究以及应用于其他方面,别忘了跟大家分享。 

更多深入理解Java中的Lambda表达式相关文章请关注PHP中文网!

相关文章

java速学教程(入门到精通)
java速学教程(入门到精通)

java怎么学习?java怎么入门?java在哪学?java怎么学才快?不用担心,这里为大家提供了java速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
Word 字间距调整方法汇总
Word 字间距调整方法汇总

本专题整合了Word字间距调整方法,阅读下面的文章了解更详细操作。

2

2025.12.24

任务管理器教程
任务管理器教程

本专题整合了任务管理器相关教程,阅读下面的文章了解更多详细操作。

2

2025.12.24

AppleID格式
AppleID格式

本专题整合了AppleID相关内容,阅读专题下面的文章了解更多详细教程。

0

2025.12.24

csgo视频观看入口合集
csgo视频观看入口合集

本专题整合了csgo观看入口合集,阅读下面的文章了知道更多入口地址。

29

2025.12.24

yandex外贸入口合集
yandex外贸入口合集

本专题汇总了yandex外贸入口地址,阅读下面的文章了解更多内容。

58

2025.12.24

添加脚注通用方法
添加脚注通用方法

本专题整合了添加脚注方法合集,阅读专题下面的文章了解更多内容。

1

2025.12.24

重启电脑教程汇总
重启电脑教程汇总

本专题整合了重启电脑操作教程,阅读下面的文章了解更多详细教程。

3

2025.12.24

纸张尺寸汇总
纸张尺寸汇总

本专题整合了纸张尺寸相关内容,阅读专题下面的文章了解更多内容。

5

2025.12.24

Java Spring Boot 微服务实战
Java Spring Boot 微服务实战

本专题深入讲解 Java Spring Boot 在微服务架构中的应用,内容涵盖服务注册与发现、REST API开发、配置中心、负载均衡、熔断与限流、日志与监控。通过实际项目案例(如电商订单系统),帮助开发者掌握 从单体应用迁移到高可用微服务系统的完整流程与实战能力。

1

2025.12.24

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Kotlin 教程
Kotlin 教程

共23课时 | 2万人学习

C# 教程
C# 教程

共94课时 | 5.2万人学习

Java 教程
Java 教程

共578课时 | 37万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号