【问题标题】:What is the depth (or limitation if any) for nesting BiFunction嵌套 BiFunction 的深度(或限制,如果有)是多少
【发布时间】:2015-09-15 07:46:22
【问题描述】:

我一直在玩BiFunction (java.util.function)。我跑了一些例子,我有一个问题。

操作可以嵌套多少次是否有限制 与双功能?是不是就像嵌套一个假设的add(a, b)一样简单 方法多少次都可以?

例如三嵌套theFunction.apply()

public static int methodContainingMethod
         (int a, int b, int c, BiFunction<Integer, Integer, Integer> theFunction) {
    return theFunction.apply(theFunction.apply(theFunction.apply(a,b),c),c),c);
}

四个嵌套theFunction.apply()

return
  theFunction.apply(theFunction.apply(theFunction.apply(theFunction.apply(a,b),c),c),c),c);

on and on...嵌套的数量可以不断增加,我测试过 函数嵌套十多次。

我对需要多少嵌套没有确切的要求……但我很好奇这样的嵌套可以做多少?

【问题讨论】:

    标签: java functional-programming java-8


    【解决方案1】:

    首先,这并不特定于BiFunction。所以你基本上是在问,你可以嵌套多深的方法调用,简单的答案是 Java 编程语言本身并没有指定限制。

    存在可能限制数量的技术限制,但这些是技术限制,而不是规范限制。当技术发展而不改变规范时,它们可能会被取消。

    作为Alain O'Dea has explained,如果最后一条指令应该被异常处理程序覆盖,则方法的代码大小限制为 65535 字节或 65534 字节。此代码大小支持的嵌套方法调用数量取决于某些因素。例如,您使用的是interface,并且接口方法调用使用的字节数比具体的类方法调用(invokevirtual 指令)多,此外,您使用的是BiFunction&lt;Integer, Integer, Integer&gt;,而不是直接的IntBinaryOperator,因此每次调用都涉及对int 需要额外代码的值。

    但是无论如何还有另一个技术限制,编译器实现。当尝试使用更高的嵌套计数编译您的示例时,javac 从命令行运行,在 1500 个嵌套调用处以 stackoverflow 终止,而 Netbeans(使用与 javac 相同的编译器代码)在IDE 开始出现奇怪的行为(我猜,它不能很好地处理编译器/语法荧光笔的堆栈溢出)。

    这表明 IDE 具有更高的堆栈大小或环境设置中的其他差异影响了表达式解析之前的初始堆栈深度。由此得出结论,在实践中没有硬性限制。您可能能够编写代码,一个编译器可以毫无问题地编译,而另一个编译器则退出 - 最大限度地解决这个问题不是一个好主意。

    毕竟,你问题的代码可以写成:

    public static int methodContainingMethod(
        int a, int b, int c, BiFunction<Integer, Integer, Integer> theFunction) {
    
        int value = theFunction.apply(a, b);
        for(int i=0; i<asDeepAsYouWannaGo; i++)
            value=theFunction.apply(value, c);
        return value;
    }
    

    虽然我认为,你的想法更像是:

    public static int methodContainingMethod(
        IntBinaryOperator theFunction, int first, int second, int... rest) {
    
      int value = theFunction.applyAsInt(first, second);
      for(int next: rest) value=theFunction.applyAsInt(value, next);
      return value;
    }
    

    public static OptionalInt methodContainingMethod(
        IntBinaryOperator theFunction, int... arguments) {
    
      return IntStream.of(arguments).reduce(theFunction);
    }
    

    【讨论】:

    • 我喜欢这个答案。它很好地展示了编译器如何无法编译正确的,但由于特定于实现的原因,这是病态的代码。它在通过循环或 Streams API 的迭代应用程序方面呈现的替代方案比在源代码中显式嵌套函数应用程序更清洁、更易于维护。
    • @Alain O'Dea:我再次更改了号码,因为 65534 只是解决异常处理程序问题的建议。由于问题的方法不包含异常处理程序,因此理论上可以使用 65535 个字节。请注意,对于普通 Java 代码,异常处理程序问题无论如何都无关紧要,因为异常处理程序不会覆盖自己,换句话说,您总是可以通过重新排序代码来解决问题,以便一个异常处理程序不被另一个异常处理程序覆盖最后,从而消除了通过异常处理程序覆盖第 65535 条指令的需要。
    • 好眼光!这是有道理的。
    • 请注意还有另一个限制:运行时堆栈深度。这不仅是配置的堆栈大小的函数,而且是调用堆栈中已经存在的其他内容。
    • @Brian Goetz:在这种特殊情况下,运行时堆栈并不重要,因为每次调用都在下一次调用完成之前完成。这不是递归。好吧,除非我们谈论的是编译器的堆栈……
    【解决方案2】:

    我不知道对此的固有表达式级别限制,但实际上它会受到 65534 字节的 JVM 方法大小限制:

    end_pc 是排他性的这一事实是 Java 虚拟机设计中的一个历史错误:如果 Java 虚拟机的方法代码正好是 65535 字节长并且以 1 字节长的指令结束,那么该指令不能由异常处理程序保护。编译器编写者可以通过将任何方法、实例初始化方法或静态初始化程序(任何代码数组的大小)生成的 Java 虚拟机代码的最大大小限制为 65534 字节来解决此错误。

    来源:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.3

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-11-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多