2022-07-14

And now, for something completely different... in Java


Summary

This post presents a coding construct for Java which reduces the total number of lines of code necessary to declare and initialize fields whose values are copied or computed from constructor parameters.

The problem

Let us suppose we want to write a class which exposes a `greet()` method that prints "Hello, <name>!" where <name> has been passed as a constructor parameter. Here is a trivial implementation:

class Greeter1
{
    private final String name;
    public Greeter1( String name ) { this.name = name; }
    public void greet() { System.out.println( "Hello, " + name + "!" ); }
}

Nothing strange here. Now, let us suppose that we also want to pass the greeting to use as a constructor parameter. Here is how the class would now look:

class Greeter2
{
    private final String greeting;
    private final String name;
    public Greeter2( String greeting, String name )
    {
        this.greeting = greeting;
        this.name = name;
    }
    public void greet() { System.out.println( greeting + ", " + name + "!" ); }
}

Nothing strange here either, but notice that both `greeting` and `message` appear four times each: 

  • once as a field definition, 
  • once as a constructor parameter, 
  • once in the assignment from constructor parameter to field, and 
  • once in the actual print statement. 

As we add more variables, it starts getting out of hand. Is there a way to cut down on the amount of unnecessary code?

The solution

The answer is yes. We can do something similar to what Scala does. It is not going to look as terse and elegant as it is in Scala, but it will result in an overall reduction of the total number of lines of code. Here is how to do it:

abstract class Greeter3
{
    public static Greeter3 of( String greeting, String name )
    {
        class Greeter3Implementation extends Greeter3
        {
            public void greet() { System.out.println( greeting + ", " + name + "!" ); }
        }
        return new Greeter3Implementation();
    }
}

Actually, both the `Greeter2` and `Greeter3` that I am showing above are 11 lines of code each, but for every constructor parameter that we add, `Greeter2` becomes larger by 2 lines, while `Greeter3` stays the same. Besides, `Greeter3` is 11 lines long only due to my highly regular and proper Allman-style curly braces, while most of you readers are probably using Egyptian-style curly braces, of which `Greeter3` has more, so your `Greeter3` would be shorter.

An additional benefit

Another cool thing with this construct is that the savings increase as the complexity of what you are trying to do increases.  Suppose we want to create a factory of `Greeter` for a specific greeting and then use that factory to create instances of Greeter for individual names without also having to specify the greeting each time. In traditional Java, it would look like this:

class Greeter4
{
    public static class Factory
    {
        private final String greeting;
        public Factory( String greeting ) { this.greeting = greeting; }
        Greeter4 newGreeter( String name ) { return new Greeter4( greeting, name ); }
    }
    private final String greeting;
    private final String name;
    public Greeter4( String greeting, String name )
    {
        this.greeting = greeting;
        this.name = name;
    }
    public void greet() { System.out.println( greeting + ", " + name + "!" ); }
}

With the idiom I am presenting here, the above mess becomes as simple as this:

abstract class Greeter5
{
    public interface Factory
    {
        Greeter5 newGreeter( String greeting );
    }
    public static Factory newFactory( String greeting )
    {
        return name ->
        {
            class Greeter5Implementation extends Greeter5
            {
                public void greet() { System.out.println( greeting + ", " + name + "!" ); }
            }
            return new Greeter5Implementation();
        };
    }
}

Note how not a single field is declared or initialized anywhere.

And one more additional benefit

Although these savings are nice, they are not the coolest thing about this idiom. I saved the coolest thing for last. The coolest thing about this idiom is that it overcomes Java's inability to access constructor parameters from within field initializers. Let me explain.

Suppose that we would like to keep the `greeting` and `name` fields, but we would also like to add one more field where we will store the message to print, so that we can precompute the message during class initialization instead of computing it on each invocation of `greet()`. In traditional Java, this would be accomplished as follows:

class Greeter6
{
    private final String greeting;
    private final String name;
    private final String message;
    public Greeter6( String greeting, String name )
    {
        this.greeting = greeting;
        this.name = name;
        message = greeting + ", " + name + "!";
    }
    public void greet() { System.out.println( message ); }
}

That's fine, but note that this new field requires two lines: one to declare the field, and one to initialize it. Can we combine these two lines in a single line? Here is where Java greatly disappoints us. The following does not compile:

class Greeter7
{
    private final String greeting;
    private final String name;
    private final String message = greeting + ", " + name + "!"; // error!
    public Greeter7( String greeting, String name )
    {
        this.greeting = greeting;
        this.name = name;
    }
    public void greet() { System.out.println( message ); }
}

This is because in Java, field initializers do not have access to constructor parameters. (And no, it cannot be fixed by moving the definition and initialization of `message` below the constructor.)  

This is not nearly as bad as in C#, where field initializers execute in a static context and therefore do not even have access to `this`, but it is still pretty bad. Luckily, by using the idiom I am presenting here, it is trivial to pre-calculate fields from constructor parameters, because we have no constructor parameters!  Instead, we have parameters of the enclosing static factory method, which are accessible by fields of the enclosed class! So, the desired result can be trivially accomplished as follows:

abstract class Greeter8
{
    public static Greeter8 of( String greeting, String name )
    {
        class Greeter8Implementation extends Greeter8
        {
            private final String message = greeting + ", " + name + "!";
            public void greet() { System.out.println( message ); }
        }
        return new Greeter8Implementation();
    }
}

Consclusion

This idiom replaces the constructor of a class with a static factory method which contains, as a method-local class, the actual class that is being instantiated. 

In doing so, this idiom saves us from having to declare fields and initialize them from constructor parameters. 

The idiom requires a couple of extra lines of (so-called "boilerplate") code, which is inevitable because unlike Scala, from which this idiom was inspired, Java was not originally designed to be used like that. However, these extra lines of code represent an overhead that we only have to suffer once, while the savings multiply as a function of the number of fields that are either passed as or computed from constructor parameters.

No comments:

Post a Comment