Logging from default interface methods

Here you go.

The Logger is private to interface. No one other than this interface and its default methods can access anything inside Test2. And nothing can extend Test2 class.

No one is suggesting you do this ... but it works! And it is a great way to get access to a class logger for the main interface, and possibly a clever way to do something else not entirely crazy.

This is really the same as the LogHolder in the OP question, except that the Test2 class is all private methods and constructor private too, and class is not marked static.

And as an added bonus, it keeps state, statically and per-instance. (Don't do that in a real program, please!)

public class TestRunner {
    public static void main(String[] args) {
        Test test = new Test() {
        };
        test.sayHello("Jane");
        System.out.println("Again");
        test.sayHello("Bob");
    }
}
public interface Test {
    default void sayHello(String name) {
        Logger log = Test2.log;
        Test2 ref = Test2.getMine.apply(this);
        int times = ref.getTimes();
        for (int i = 0; i < times; i++) {
            System.out.println(i + ": Hello " + name);
        }
        log.info("just said hello {} times :)",times);
    }
    final class Test2 {
        private static final Logger log = LoggerFactory.getLogger(Test.class);
        private static final Map lookup = new WeakHashMap();
        private static final Function getMine = (obj) -> {
            return lookup.computeIfAbsent(obj, (it) -> new Test2());
        };
        private int calls = 0;
        private Test2() {
        }
        private void setCalls(int calls) {
            this.calls = calls;
        }
        private int getCalls() {return calls;}
        private int getTimes() {return ++calls;}
    }
}

Starting with JDK 16, you can hide the helper class inside a method:

interface WithTimeout<Action> {

    default void onTimeout(Action timedOutAction) {
        logger().info("Action {} time out ignored.", timedOutAction);
    }

    private static Logger logger() {
        final class LogHolder {
            private static final Logger LOGGER = getLogger(WithTimeout.class);
        }
        return LogHolder.LOGGER;
    }
}

Since JDK 9, interfaces are allowed to have private methods. To utilize this for a lazy initialization, we need to be able to declare a static field in a local class, which is allowed since JDK 16.

For older Java versions, if you don’t want to expose the class LogHolder to the public, don’t make it a member class of the interface. There is almost no benefit in making it a member class, you don’t even save typing as you have to qualify the field access with the name of the holder class anyway, regardless of whether it is a member class or a class within the same package:

public interface WithTimeout<Action> {

    default void onTimeout(Action timedOutAction) {
        LogHolder.LOGGER.info("Action {} time out ignored.", timedOutAction);
    }
}
final class LogHolder { // not public
    static final Logger LOGGER = getLogger(WithTimeout.class);
}

The disadvantage is the visibility within the same package. Of course, there can be only one top level class named LogHolder within a package.