4

I have been playing with BiFunction (java.util.function). I ran some examples and and I have one question.

Is there a limitation on how many times the operation can be nested with BiFunction? Is it as simple as nesting a hypothetical add(a, b) method as many times as one wants?

e.g. three nested 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);
}

four nested theFunction.apply()

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

on and on... The number of nesting can go up and on, I tested with nesting the function for over ten times.

I don't have exact requirement as to how many nesting is needed... but I am curious as to how many nesting of such can be done?

Tagir Valeev
  • 97,161
  • 19
  • 222
  • 334
Nirmal
  • 1,229
  • 1
  • 15
  • 31

2 Answers2

6

First of all, that’s not specific to BiFunction in any way. So you are basically asking, how deep you can nest method invocations and the simple answer is that the Java programming language does not specify a limitation per se.

There are technical limitations which may restrict the number but these are, well, technical limitations, not limitation by specification. They might be lifted when the technology evolves without changes in the specification.

As Alain O'Dea has explained, a method’s code size is limited to 65535 bytes or to 65534 bytes if the last instruction ought to be covered by an exception handler. The amount of nested method calls supported by this code size depends on some factors. E.g., you are using an interface and interface method invocation use more bytes than concrete class method invocations (invokevirtual instructions), further, you are using BiFunction<Integer, Integer, Integer> rather than the straight-forward IntBinaryOperator so every invocation involves boxing of the int values which requires additional code.

But there is another technical limitation anyway, the compiler implementation. When trying to compile your example with a higher nesting count, javac ran from the command line terminated with a stackoverflow at 1500 nested invocation, while Netbeans (using the same compiler code as javac) managed to compile 2000 nested invocations before the IDE started to exhibit strange behavior (I guess, it doesn’t handle stack overflows of the compiler/ syntax highlighter very well).

That suggests that the IDE had a higher stack size or other differences in the environmental setup affected the initial stack depth before the expression got parsed. This leads to the conclusion that there is no hard limit in practice. You may be able to write code which one compiler manages to compile without problems whereas another one bails out— not a good idea to max this out.

After all, the equivalent of your question’s code could be written as:

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;
}

though I think, what you have in mind, is more like:

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;
}

or

public static OptionalInt methodContainingMethod(
    IntBinaryOperator theFunction, int... arguments) {

  return IntStream.of(arguments).reduce(theFunction);
}
Community
  • 1
  • 1
Holger
  • 285,553
  • 42
  • 434
  • 765
  • 3
    I like this answer. It is a good demonstration of how compilers can fail to compile correct, but pathological code for implementation-specific reasons. The alternatives it presents in terms of iterative application via loops or Streams APIs are cleaner and more maintainable than explicitly nesting the function applications in source code. – Alain O'Dea Jun 29 '15 at 12:12
  • 2
    @Alain O'Dea: I changed the number again, as the 65534 is only an advise for solving the exception handler problem. Since the method of the question doesn’t contain exception handlers, it could use 65535 bytes in theory. Note that for ordinary Java code the exception handler problem isn’t relevant anyway as exception handlers don’t cover themselves, in other words, you could always workaround the problem by reordering the code so that one exception handler not being covered by another one is at the end, thus eliminating the need to cover the 65535th instruction by an exception handler. – Holger Jun 29 '15 at 12:31
  • 1
    Good eye! That makes sense. – Alain O'Dea Jun 29 '15 at 14:00
  • 1
    Note also that there's another limitation: runtime stack depth. And this is a function not only of the stack size as configured, but what else is already on your call stack. – Brian Goetz Jun 29 '15 at 14:59
  • 2
    @Brian Goetz: in this special case, the runtime stack doesn’t matter as every invocation completes before the next one is made. It’s not a recursion. Well, unless we’re talking about the stack of the compiler… – Holger Jun 29 '15 at 15:37
4

I don't know of an inherent expression-level limit to this, but it will in practice be contrained by the JVM method size limit of 65534 bytes:

The fact that end_pc is exclusive is a historical mistake in the design of the Java Virtual Machine: if the Java Virtual Machine code for a method is exactly 65535 bytes long and ends with an instruction that is 1 byte long, then that instruction cannot be protected by an exception handler. A compiler writer can work around this bug by limiting the maximum size of the generated Java Virtual Machine code for any method, instance initialization method, or static initializer (the size of any code array) to 65534 bytes.

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

Alain O'Dea
  • 21,033
  • 1
  • 58
  • 84