欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

提防Java中的函数式编程!

程序员文章站 2022-07-14 20:32:43
...

这对函数式编程并不会造成太大的影响,这真棒。 这是关于某些实践的警告,您很可能会将其应用于您的代码,而这完全是错误的!

高阶函数对于函数式编程是必不可少的,因此,谈论它们将帮助您成为聚会中的焦点。

如果您正在编写JavaScript,那么您就一直在这样做。 例如:

setTimeout(function() {
    alert('10 Seconds passed');
}, 10000);

上面的setTimeout()函数是一个高阶函数。 它是一个使用匿名函数作为参数的函数。 10秒钟后,它将调用作为参数传递的函数。

我们可以编写另一个简单的高阶函数来提供上述功能:

var message = function(text) {
    return function() {
        alert(text);
    }
};

setTimeout(message('10 Seconds passed'), 10000);

如果执行上述操作,将执行message() ,并返回一个匿名函数,该函数将提醒您传递给message()的参数文本

在函数式编程中,以上是常见的做法。 从高阶函数返回的函数将捕获外部作用域,并在调用时能够对该作用域起作用。

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

出于同样的原因。 从高阶“函数”(方法)返回的“函数”(lambda)将捕获外部作用域,并在调用时能够对该作用域起作用。

这里给出了最简单的示例:

class Test {
    public static void main(String[] args) {
        Runnable runnable = runnable();
        runnable.run(); // Breakpoint here
    }

    static Runnable runnable() {
        return () -> {
            System.out.println("Hello");
        };
    }
}

在上述逻辑中,如果在执行runnable.run()调用的地方放置一个断点,则可以在堆栈上看到无害的lambda实例。 一个简单的生成类,支持功能接口的实现:

提防Java中的函数式编程!

现在,让我们将此示例转换为普通的Enterprise™应用程序(请注意注释 ),我们已对其进行了简化,以适合此博客文章:

class Test {
    public static void main(String[] args) {
        Runnable runnable = new EnterpriseBean()
            .runnable();
        runnable.run(); // Breakpoint here
    }
}

@ImportantDeclaration
@NoMoreXML({
    @CoolNewValidationStuff("Annotations"),
    @CoolNewValidationStuff("Rock")
})
class EnterpriseBean {
    Object[] enterpriseStateObject = 
        new Object[100_000_000];

    Runnable runnable() {
        return () -> {
            System.out.println("Hello");
        };
    }
}

断点仍在同一位置。 我们在堆栈上看到了什么?

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

提防Java中的函数式编程!

精细。 当然。 让我们添加一些其他日志记录,仅用于调试

class Test {
    public static void main(String[] args) {
        Runnable runnable = new EnterpriseBean()
            .runnable();
        runnable.run(); // Breakpoint here
    }
}

@ImportantDeclaration
@NoMoreXML({
    @CoolNewValidationStuff("Annotations"),
    @CoolNewValidationStuff("Rock")
})
class EnterpriseBean {
    Object[] enterpriseStateObject = 
        new Object[100_000_000];

    Runnable runnable() {
        return () -> {
            // Some harmless debugging here
            System.out.println("Hello from: " + this);
        };
    }
}

哎呀!

突然, this引用的“无害”之处迫使Java编译器将EnterpriseBean™的封闭实例包含在返回的Runnable类中:

提防Java中的函数式编程!

随之而来的是沉重的enterpriseStateObject ,现在不再可以对其进行垃圾回收,直到调用站点释放无害的Runnable小对象为止。

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

确实不是。 Java 8没有一流的功能,没关系。 通过名义上的SAM类型支持lambda表达式的想法非常狡猾,因为它允许升级和lambda-y-fy Java生态系统中的所有现有库,而无需对其进行更改。

而且,在匿名课堂上,整个故事也就不足为奇了。 自从旧的Swing 1.0样式好的ActionListener等以来,以下编码样式已通过匿名类泄漏了内部状态。

class Test {
    public static void main(String[] args) {
        Runnable runnable = new EnterpriseBean()
            .runnable();
        runnable.run();
    }
}

@ImportantDeclaration
@NoMoreXML({
    @CoolNewValidationStuff("Annotations"),
    @CoolNewValidationStuff("Rock")
})
class EnterpriseBean {
    Object[] enterpriseStateObject = 
        new Object[100_000_000];

    Runnable runnable() {
        return new Runnable() {
            @Override
            public void run() {
                System.out.println("Hello from " + this);
            }
        };
    }
}

什么是新的? lambda样式将鼓励在各处使用Java中的高阶函数。 这通常是好的。 但是只有当高阶函数是静态方法时,其结果类型才不会包含任何状态。

但是,通过以上示例,我们可以看到,在不久的将来,当我们开始接受Java 8的功能样式编程时,将通过一些内存泄漏和问题进行调试。

因此,请小心,并遵循以下规则:

(“纯”)高阶函数必须是Java中的静态方法!

进一步阅读

封闭实例之前已引起问题。 了解过去二十年来可怕的双花括号反模式如何在Java开发人员中造成痛苦。

翻译自: https://www.javacodegeeks.com/2015/11/beware-of-functional-programming-in-java.html