Consider the following code:
Optional<Method> method = Arrays.stream(clazz.getMethods()).filter(m -> m.getName().equals("hello")).findFirst();
if (method.isPresent()) {
method.get().invoke(instance);
}
When stepping (debugging) into the invoke
method above in IntelliJ IDEA, I find myself in method java.lang.reflect.Method.invoke
. However for the following (slightly changed) code:
Optional<Method> method = Arrays.stream(clazz.getMethods()).filter(m -> m.getName().equals("hello")).findFirst();
if (method.isPresent()) {
Method m = method.get();
m.invoke(instance);
}
I will be lead straight to the <instance>.hello
method (which is what I actually want). The only difference is that method.get().invoke(instance)
is split into two calls.
Can someone explain this strange different behavior?
The difference happens because of how the debugger handles inlining.
In the first example, method.get().invoke(instance)
is treated as one compound operation, so the debugger stops at Method.invoke
. In the second example, splitting the call lets the debugger associate m.invoke(instance)
directly with the target method (hello), so it steps into hello as expected.
You can read this content about method inlining: What is method inlining?
The compiler can optimize performance by replacing a function call with the actual code of the function itself, reducing the cost of calling the function at runtime.