在路上

 找回密码
 立即注册
在路上 站点首页 学习 查看内容

在Java中应用函数式编程请小心!

2017-2-7 13:41| 发布者: zhangjf| 查看: 440| 评论: 0

摘要: 这并不是要对令人畏惧的函数式编程进行谴责,而是对编程中很容易发生的一些错误进行警醒。 高阶函数 是函数式编程的关键,因此,谈论它们会帮助你在派对上成为关注的焦点。 如果你正在写 JavaScript ,实际上一直在 ...

这并不是要对令人畏惧的函数式编程进行谴责,而是对编程中很容易发生的一些错误进行警醒。

高阶函数 是函数式编程的关键,因此,谈论它们会帮助你在派对上成为关注的焦点。

如果你正在写 JavaScript ,实际上一直在做的就是高阶函数。例如:

  1. setTimeout(function() {
  2. alert('10 Seconds passed');
  3. }, 10000);
复制代码

上面的 setTimeout() 函数就是一个高阶函数。它的参数是一个匿名函数。10秒后,它将会用这个匿名函数作为参数来调用。

我们可以编写另一个简单的高阶函数,作为结果提供给上面的函数:

  1. var message = function(text) {
  2. return function() {
  3. alert(text);
  4. }
  5. };
  6. setTimeout(message('10 Seconds passed'), 10000);
复制代码

如果运行上面的程序,将会执行message() 函数,并返回一个匿名函数,这个匿名函数将会输出传递给 message() 函数的text参数。

在函数式编程中,上面是很常见的做法。由高阶函数返回的函数被调用时,将会捕捉外部作用域,并且能够在这个作用域上进行操作。

为什么在 Java 中这种做法很危险?

出于同样的原因。高阶函数(方法)返回的函数(lambda函数)被调用的时候,会捕捉外部作用域,并且能够在这个作用域上进行操作。

这里给出一个最简单的例子:

  1. class Test {
  2. public static void main(String[] args) {
  3. Runnable runnable = runnable();
  4. runnable.run(); // Breakpoint here
  5. }
  6. static Runnable runnable() {
  7. return () -> {
  8. System.out.println("Hello");
  9. };
  10. }
  11. }
复制代码

在上面的逻辑中,如果在 runnable.run() 方法调用处做一个断点,可以看到在堆栈上无害的lambda实例。生成的一个简单类,提供了函数式接口的实现:

现在,把这个例子转换成普通的企业应用程序( 注意注解 )。为了方便博客的书写,我们做了很大的简化:

  1. class Test {
  2. public static void main(String[] args) {
  3. Runnable runnable = new EnterpriseBean()
  4. .runnable();
  5. runnable.run(); // Breakpoint here
  6. }
  7. }
  8. @ImportantDeclaration
  9. @NoMoreXML({
  10. @CoolNewValidationStuff("Annotations"),
  11. @CoolNewValidationStuff("Rock")
  12. })
  13. class EnterpriseBean {
  14. Object[] enterpriseStateObject =
  15. new Object[100_000_000];
  16. Runnable runnable() {
  17. return () -> {
  18. System.out.println("Hello");
  19. };
  20. }
  21. }
复制代码

断点仍放在原来的地方。那么我们在堆栈中又能看到什么呢?

仍然是一个无害的lambda实例:

好吧,为了调试,我们现在添加一些日志:

  1. class Test {
  2. public static void main(String[] args) {
  3. Runnable runnable = new EnterpriseBean()
  4. .runnable();
  5. runnable.run(); // Breakpoint here
  6. }
  7. }
  8. @ImportantDeclaration
  9. @NoMoreXML({
  10. @CoolNewValidationStuff("Annotations"),
  11. @CoolNewValidationStuff("Rock")
  12. })
  13. class EnterpriseBean {
  14. Object[] enterpriseStateObject =
  15. new Object[100_000_000];
  16. Runnable runnable() {
  17. return () -> {
  18. // Some harmless debugging here
  19. System.out.println("Hello from: " + this);
  20. };
  21. }
  22. }
复制代码

哦哦!

意外地,一个毫无影响的 this 引用强迫Java 编译器封装返回的Runnable 类中 EnterpriseBean 的外部类实例:

而且,同时返回的还有 enterpriseStateObject 。这个对象现在不会被垃圾回收,直到调用点释放 Runnable后才会释放。

好吧,现在这并不是什么新鲜事,不是吗?

确实,这并不是什么新鲜事。Java 8 没有一流的函数,没关系。通过虚拟的 SAM 类型支持匿名表达式的想法就相当的巧妙,因为在 Java系统中它允许对所有现有的库更新和lambda-y-fy而并不改变它们。

而且,用一个匿名类,这整件事情就不会那么令人惊讶了。由于良好的Swing 1.0风格ActionListener等,下面的编码风格通过内部类已经暴露了内部的状态。

  1. class Test {
  2. public static void main(String[] args) {
  3. Runnable runnable = new EnterpriseBean()
  4. .runnable();
  5. runnable.run();
  6. }
  7. }
  8. @ImportantDeclaration
  9. @NoMoreXML({
  10. @CoolNewValidationStuff("Annotations"),
  11. @CoolNewValidationStuff("Rock")
  12. })
  13. class EnterpriseBean {
  14. Object[] enterpriseStateObject =
  15. new Object[100_000_000];
  16. Runnable runnable() {
  17. return new Runnable() {
  18. @Override
  19. public void run() {
  20. System.out.println("Hello from " + EnterpriseBean.this);
  21. }
  22. };
  23. }
  24. }
复制代码

这里有什么心东西?匿名风格会鼓励在 Java 中所有地方使用高阶函数。一般情况下是挺好的。但是,仅仅当高阶函数是一个静态方法时,其产生的类型不会封装任何状态。

然而,通过上面的例子

我们可以看到,在不久的将来,当我们开始拥抱 Java 8 函数式编程风格时,我们在调试过程中将会时不时碰到一些内存泄露等问题。

所以,请谨慎使用并遵循以下规则:

  1. (“Pure”) Higher order functions MUST be static methods in Java!
复制代码
延伸阅读

以前封闭实例(enclosing instance)会出现一些问题。可以解 了 一下可怕的 双花括号反模式 在前20年是怎么给java开发者带来痛苦和磨难的。

原文链接: jaxenter 翻译:ImportNew.com -santhy

译文链接:[]

来自: http://www.importnew.com/17292.html

最新评论

小黑屋|在路上 ( 蜀ICP备15035742号-1 

;

GMT+8, 2025-7-9 06:23

Copyright 2015-2025 djqfx

返回顶部