1

Can someone please explain me the meaning of the signature of this class -

org.apache.logging.log4j.core.appender

Class RollingFileAppender.Builder<B extends RollingFileAppender.Builder<B>>

as documented - Log4j2 apidocs

I read it as Builder for type B, where B extends the Builder for type B, which confuses me like anything.

Also, this is a static class and it has a constructor as well. Is this normal? Can somebody please share a reference that can help me understand and use this in program?

PS - About what I am doing here - I am working on modifying log4j code to make it compatible with log4j2, in which there is a RollingFileAppender created using RollingFileAppender constructor in a util class.

Chesser
  • 455
  • 3
  • 9
  • SO question [Contructor inside static inner class](https://stackoverflow.com/questions/29260297/constructor-inside-inner-static-class-in-java) answers my second part around the constructor for this class. – Chesser May 08 '23 at 07:47
  • Possibly relevant: https://stackoverflow.com/questions/19588449/self-bounded-generics – Bohemian May 08 '23 at 08:15

2 Answers2

5

Builders usually have a fluent API. I.e. their building methods return the builder class to be able to directly call other building methods. For example, the class in question - RollingFileAppender.Builder - can be used like this:

var appender = builder.withAppend(true).withLocking(true).build();

That means that the building methods (the ones that are prefixed with with in this case) must return the same builder instance. But ... If you design a builder class for being subclassed, you have a problem. What type shall the building method return?

Consider the following classes:

abstract class Builder {
    abstract Object build();
    Builder withProperty(String property) { return this; }
}
class ChildBuilder extends Builder {
    Object build() { return new Object(); }
    ChildBuilder withChildProperty(String childProperty) { return this; }
}

The actual return type of the build method does not matter for this explanation. The problem now is ... you can't do the following:

var obj = new ChildBuilder()
    .withProperty("property")
    .withChildProperty("child property") // Compiler error here!
    .build();

The return type of withProperty is Builder, and this type does not know about the building method withChildProperty. So you are breaking the fluent API.

The solution: A parameterized builder class. The type parameter B in the following example is intended to express the concept of a self type.

For our example, you do the following:

abstract class Builder<B extends Builder<B>> {
    abstract Object build();
    B withProperty(String property) { return this; } // Note the return type here.
}
final class ChildBuilder extends Builder<ChildBuilder> {
    Object build() { return new Object(); }
    ChildBuilder withChildProperty(String childProperty) { return this; }
}

Now you can do the code as I mentioned above.

Seelenvirtuose
  • 20,273
  • 6
  • 37
  • 66
  • 1
    A good explanation that buries the lede: "B is intended to be 'self-type'". Without that context it's a little hard to follow the otherwise excellent explanation as to _why_ a 'self type' is needed for subclassable builders. – rzwitserloot May 08 '23 at 10:38
1

This is known as the curiously recurring template pattern.

As far as I know, RollingFileAppender.Builder doesn't have any subclasses, so this isn't technically needed for RollingFileAppender.Builder to work. It could have been declared as a non-generic type:

public static class RollingFileAppender.Builder extends 
    AbstractOutputStreamAppender.Builder<RollingFileAppender.Builder>

and its builder methods, which all return a B (e.g. withAdvertise), can just as well return RollingFileAppender.Builder.

However, consider the case where someone is trying to write a subclass of this non-generic class:

public class FooBuilder extends RollingFileAppender.Builder {
    public FooBuilder withFoo(String foo) {
        // ...
        return this;
    }
}

// Let's try using MyBuilder:
new FooBuilder()
    .withAdvertise(false) // let's call one of the methods in RollingFileAppender.Builder first...
    .withFoo("Foo") // and now we try to call my subclass's method
    .build()

The call withFoo would not compile. This is because in our hypothetical, non-generic RollingFileAppender.Builder, withAdvertise returns RollingFileAppender.Builder, not MyBuilder.

Making RollingFileAppender.Builder generic, and making its builder methods return the generic type parameter B solves this problem. Because then we could write:

// in this pattern,
// you are always expected to put the class that you are declaring as B
public class MyBuilder extends RollingFileAppender.Builder<MyBuilder>

Whatever methods declared in RollingFileAppender.Builder that returns a B will now return a MyBuilder.

new MyBuilder()
    .withAdvertise(false) // this now returns a MyBuilder
    .withFoo("Foo") // and this now works
    .build()

It would also be helpful to see the inheritance hierarchy of RollingFileAppender.Builder:

java.lang.Object
    org.apache.logging.log4j.core.filter.AbstractFilterable.Builder<B>
        org.apache.logging.log4j.core.appender.AbstractAppender.Builder<B>
            org.apache.logging.log4j.core.appender.AbstractOutputStreamAppender.Builder<B>
                org.apache.logging.log4j.core.appender.RollingFileAppender.Builder<B>

As you can see, this class inherits a lot of builder methods. If none of the classes followed this curiously recurring template pattern, you would not be able to call methods declared in RollingFileAppender.Builder after using one of the methods inherited from its superclasses.

new RollingFileAppender.Builder()
    .setImmediateFlush(true) // this is declared in AbstractOutputStreamAppender.Builder
    // so the above would have returned an AbstractOutputStreamAppender.Builder
    .withAdvertise(true); // now you wouldn't be able to do this

To summarise, this B is simply a way for all the builder methods to return whatever Builder subclass that you are using, rather than the class that declared them. Although it is not needed for RollingFileAppender.Builder since it has no subclasses, the authors still declared it like that, presumably to follow the pattern.


As for why static classes can have constructors, static classes just means that you don't need an instance of the outer class (RollingFileAppender) in order to instantiate the nested class. It does not prevent declaring constructors.

Sweeper
  • 213,210
  • 22
  • 193
  • 313