4

Can anyone tell me the (subtle) differences of

version 1:

protected final Logger log = Logger.getLogger(getClass());

vs

version 2:

protected final Logger log = Logger.getLogger(MethodHandles.lookup().lookupClass());

Is version 2 in general faster than version 1?

I guess version 1 uses reflection (on runtime) to determine the current class while version 2 does not need to use reflection, or (is the check done on build time)?

nimo23
  • 5,170
  • 10
  • 46
  • 75

2 Answers2

5

There is no reflection involved in you first case. Object#getClass() is mapped to JVM's native method.

Your second case is not drop-in replacement for Object#getClass(), it is used to lookup method handles.

So subtle difference is, they are used for completely different purposes.

rkosegi
  • 14,165
  • 5
  • 50
  • 83
  • Ok, If found out that using MethodHandles within Interface does not give me the runtime class but the interface class. So it's not a drop-in replacement. Don't know the reason why some prefer MethodHandles instead of getClass() when defining a logger. – nimo23 Jan 26 '21 at 15:24
  • It's definitely not common to use `MethodHandles` to define logger. Do you have any reference to such code? – rkosegi Jan 26 '21 at 15:27
  • For example: https://stackoverflow.com/questions/65117135/passing-methodhandles-lookup-lookupclass-vs-passing-class-to-getlogger-me or https://stackoverflow.com/questions/936684/getting-the-class-name-from-a-static-method-in-java. But they don't explain why I should prefer one over the other (for example, in terms of performance, lookup costs, ..). Maybe you can explain it better:) – nimo23 Jan 26 '21 at 15:29
  • @nimo23 performance wise, there is almost no diff. – Eugene Jan 26 '21 at 21:17
3

These are entirely different things. The documentation of lookupClass, specifically says:

Tells which class is performing the lookup. It is this class against which checks are performed for visibility and access permissions

So it's the class which performs the lookup. It's not necessarily the class where you call MethodHandles.lookup(). What I mean by that is that this:

Class<?> c = MethodHandles.privateLookupIn(String.class, MethodHandles.lookup()).lookupClass();
System.out.println(c); 

will print String.class and not the class where you define this code.

The only "advantage" (besides confusing every reader of this code), is that if you copy/paste that log creation line across various source files, it will use the proper class, if you, by accident, don't edit it (which probably happens).

Also notice that:

protected final Logger log = Logger.getLogger(getClass());

should be a static field, usually, and you can't call getClass if it is.

A JMH test shows that there is no performance gain to obfuscate your code that much:

@State(Scope.Benchmark)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 5, time = 2, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 5, time = 2, timeUnit = TimeUnit.SECONDS)
public class LookupTest {

    private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();

    public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder().include(LookupTest.class.getSimpleName())
                .verbosity(VerboseMode.EXTRA)
                .build();
        new Runner(opt).run();
    }
    
    @Benchmark
    @Fork(3)
    public Class<?> getClassCall() {
        return getClass();
    }
    
    @Benchmark
    @Fork(3)
    public Class<?> methodHandlesInPlaceCall() {
        return MethodHandles.lookup().lookupClass();
    }
    
    @Benchmark
    @Fork(3)
    public Class<?> methodHandlesCall() {
        return LOOKUP.lookupClass();
    }
}

results:

Benchmark                            Mode  Cnt  Score   Error  Units
LookupTest.getClassCall              avgt   15  2.264 ± 0.044  ns/op
LookupTest.methodHandlesCall         avgt   15  2.262 ± 0.030  ns/op
LookupTest.methodHandlesInPlaceCall  avgt   15  4.890 ± 0.783  ns/op
Eugene
  • 117,005
  • 15
  • 201
  • 306
  • Eugene: thanks for the very good eplanation :) By the way, `should be a static field, usually, and you can't call getClass if it is`, In my case, the logger is defined within an abstract class so I need the non-static version. – nimo23 Jan 27 '21 at 10:15
  • 1
    @nimo23 I see, we use lombok for that via `@Slf4j` – Eugene Jan 27 '21 at 13:07