Search code examples
javadesign-patternsstandards

Standard pattern of initiating and calling the Java method references


I have a ClassA object that sets method references inside of ClassB1 and ClassB2 objects. ClassB1 and ClassB2 objects will later use this method reference while running their methods. But, sometimes we do not set the method reference:

public class ClassA {
    public ClassA() {
        ClassB1 objB1 = new ClassB1();
        ClassB2 objB2 = new ClassB2();
        objB1.setFuncitonA(this::functionA);
        objB2.setFuncitonA(this::functionA);
        objB1.functionB();
        objB2.functionB();
    }
    public void functionA(Integer x) {
        x *= 2;
    }
}

public class ClassB1 {
    private Integer intObjB = new Integer(2);
    private Consumer<Integer> functionA = null;
    public void functionB() {
        if(functionA != null) {
            functionA.accept(intObjB);
        }
    }
    public void setFuncitonA(Consumer<Integer> functionA) {
        this.functionA = functionA;
    }
}

public class ClassB2 {
    private Integer intObjB = new Integer(2);
    private Consumer<Integer> functionA = this::defaultFunctionA;
    public void functionB() {
        functionA.accept(intObjB);
    }
    public void setFuncitonA(Consumer<Integer> functionA) {
        this.functionA = functionA;
    }
    public void defaultFunctionA(Integer intObj) {
        return;
    }
}

Should it be like in ClassB1 or like in ClassB2, or, does it matter at all? What is the standard pattern of writing such code?


Solution

  • There is nomenclature for this sort of implementation decision: lazily instantiating your field, or eagerly instantiating your field.

    ClassB1 lazily instantiates the functionA consumer. This tells a maintainer (including yourself) that this consumer isn't always necessary for every new instance, and having it null in certain contexts is safe. It does mean that you have to look over your shoulder when you're using it though, as in the case of the null checks.

    ClassB2 eagerly instantiates the functionA consumer. This tells a maintainer (including yourself) that this consumer is required at instantiation time. This means you avoid the silly null check if it's truly something you know at instantiation time (and in this case, it is something you either know or can get).

    The standard pattern then becomes:

    • If you're comfortable checking that the field (or variable) is null before use, then lazily instantiate the field.
    • If you must be sure that the field (or variable) is not null before use, then eagerly instantiate the field.

    There's no hard and fast rule to use or prefer one over the other. This will heavily depend on your use case.