When implementing the compareTo method required by Comparable interface, I want to use a Comparator generated by Comparator.comparing… convenience method.
For example:
package work.basil.example.threading.multitasking;
import java.util.Comparator;
public record Job(
int id ,
int amt
) implements Comparable < Job >
{
@Override
public int compareTo ( final Job job )
{
return Comparator.comparingInt( Job :: id ).thenComparingInt( Job :: amt ).compare( this , job );
}
}
Will the call to Comparator.comparingInt( … ).thenComparingInt( … ) instantiate a Comparator on each and every execution of this method? Or will either a Java bytecode compiler (javac, etc.) or a JIT compiler (HotSpot, OpenJ9, etc.) optimize for that?
If the compiler does not optimize, I would make a private static final constant, like this:
package work.basil.example.threading.multitasking;
import java.util.Comparator;
public record Job(
int id ,
int amt
) implements Comparable < Job >
{
private static final Comparator < Job > COMPARATOR = Comparator.comparingInt( Job :: id ).thenComparingInt( Job :: amt );
@Override
public int compareTo ( final Job job )
{
return Job.COMPARATOR.compare( this , job );
}
}
Will javac optimize this?
Yes. No. Mu. Depends on what you mean by 'optimize'.
Javac is on rails: It has to do precisely what the java spec says, no more, and no less, and the java spec includes very little optimization. The java spec pretty much spells out 'this java code turns into that bytecode, exactly, and you aren't allowed to deviate or you're not a java compiler'. Unlike, say, C compilers which will spend a ton of time analysing code flow and working with the configured targeted platform to introduce zany levels of optimization such as loop unrolls, rewriting
ifs to follow annotated branch predictions, inlining methods, straight up eliminating whole swaths of code because that code path as per static analysis can't ever be touched (and in the#ifdefheavy C lang world, that's a very important optimization done by the compiler!)So what does javac do here?
Loads of tutorials, and in some ways even the java spec itself, suggests that this:
is just syntax sugar for:
and for most intents and purposes it's a fine way to explain it to those who know what anonymous inner class literals are (that's
new Type() {...}- the syntax used here), but this is incorrect specifically in the context of Basil's question.Because the anonymous inner class literal is as per spec required to be compiled to code that instantiates an object, whereas the spec of lambdas and method refs explicitly does not require this, and indeed, goes out of its way to indicate that the 'identity' of the 'object' that a lambda expression or method ref becomes is not to be treated as relevant in any way because javac/the JVM reserves every right to make that useless.
And indeed, that is exactly what it does. Let's compile your first snippet and then run
javap -c -vto have a look at what it is compiled to:The crucial thing to notice in that output is that there isn't a single
newbytecode instruction (newmakes new objects) in that entire block. Contrast to replacing e.g.Job::idwith the longhand form of:and if we
javap -c -vthat we get quite different results:Note how here
newis happening,class Job$1refers to the anonymous inner class that is being created here (thenew ToIntFunction<Job>() { .. })code, its constructor is called (this constructor does nothing, and javac knows this, but as javac is on rails, it is as per spec required to insert thatinvokespecial #18instruction, eventhough that constructor is a no-op... it's actually not quite a no-op, it takes as argument theJob, as it's a non-static inner class. javac knows this is not used. Again, immaterial,javachas to output all this unneeded cruft, spec requires it).Going back to the 'nice' code without the
new, all that happens is a bunch of indy invocations (indy is short forInvokeDynamicand was quite a humongous addition to the JVM/class file format). These are invoking the method '#0:applyAsInt', which is a method that takes no arguments (()), and returns an instance ofToIntFunction(that's what theLjava/util/function/ToIntFunction;represents). This 'makes a value of type ToIntFunction, but doesn't, itself, do this work, instead it calls some nebulous thing with InDy to make this value.So what's going on here? We need to scroll all the way to the end of
javap's output for more insights:This is where the magic happens. These 'bootstrap methods' seem utterly nuts, what the heck is LambdaMetaFactory, what's MethodHandles$Lookup, why in the blazes is there a mention of an inner class, when compiling this code results in just a single
classfile instead of the usualJob$Inner.classfile we expect to see when we wrote an actual inner class (and which, indeed, shows up if we write the anonymous inner class literal and compile it, then we get aJob$1.class)?These are optimizations were introduced in Java8 (actually,
invokedynamicas a bytecode instruction was introduced in OpenJDK7, butjavacdidn't generate InDy instructions until java8). Explaining exactly how it works is quite a lot and requires a complete dive into what indy does, but, to summarize for this specific question: Effectively all this hoopla does precisely what you want: It makes the 'cost' of that one-liner identical to having a separate constant.Therefore, the conclusion is simple: No, you do NOT need to rewrite your simple snippet into that second form; do not introduce a constant, it will not be any faster. If anything, it'll be slower.
InvokeDynamic
These bootstrap methods are invoked 'as needed', but in this case, essentially only once ever, the results of them are laced to the relevant
invokedynamicinstruction so that the bootstrap method doesn't have to be run every time. It's as near as 'I made a constant to avoid every invocation of mycompareTomethod making objects and running a bunch of code' as can be, notably including that it does not create any garbage, not even short-lived garbage.I recommend this article on oracle's java magazine about InvokeDynamic if you want to know exactly how it works. It also explains what bootstrap methods are and how InDy uses them.