Randgalt / record-builder

Record builder generator for Java records
Apache License 2.0
756 stars 55 forks source link

Default values in builder #34

Closed TrackerSB closed 3 years ago

TrackerSB commented 3 years ago

Currently the fields of a generated builder are initialized with Java default values. Thus if one had a field of type Optional<SomeFancyClass> its initial value would be null whereas Optional.empty() may be a preferable initial value.

Randgalt commented 3 years ago

This is in the same class as other recent discussions/comments. I'm open to doing this but I'd make it configurable. Should the configuration be global i.e. as a -A to javac? What should the default be? Should the option be in the annotation itself - i.e. add an attribute to RecordBuilder like boolean emptyDefaultForOptional()? What should the default for that be?

TrackerSB commented 3 years ago

I can imagine multiple approaches that allow specifying default values for fields if these are wanted:

  1. Introduce a flag emptyDefaultForOptional to the RecordBuilder annotation as you say. This would be pretty straight forward and would solve the case for me but that's not a general solution for the problem of specifying default values of any type.
  2. Introduce annotations for fields like
    public record Foo(
        @DefaultBuilderValue("Optional.empty()")
        Optional<String> bar) {
    }

    Allowing to specify "random init code" for a field as a String would somehow "solve" the problem of defining default values but I expect it to introduce too much complexity and too many problems with type safety.

  3. Introduce init methods
    public record Foo(Optional<String> bar){
        private static Optional<String> initBar(){
            return Optional.empty();
        }
    }

    This would allow to specify default values for any field if wanted with any code and for any type. However it would introduce a dependency on correct naming of methods. It would also introduce a problem with type safety since the return type of the method and type of the field have to match. Additionally it would require access to private methods via reflection. This approach would further not allow to specify that "all Optional fields should be initialized with Optional.empty()". You would have to specify an init method for each field that should default to Optional.empty().

  4. Specify default values via a default record constructor.
    public record Foo(Optional<String> bar) {
        public Foo() {
            bar = Optional.empty();
        }
    }

    Here specifying default values for desired fields would be very compact but it requires a developer to introduce a default constructor if there are any fields that should have a specific default value and requires the generated builder to create a default constructed record and use its values as init values for its own fields.

  5. Avoid introducing any additional feature and point to the following implementation.
    public record Foo(Optional<String> bar) {
        public Foo {
            if( bar == null ) {
                bar = Optional.empty();
            }
        }
    }

    This would not require any changes to RecordBuilder but it is not nice in the sense that the values of the record are internally "corrected" at its creation and therefore would basically disallow certain values for fields instead of introducing default values in the builder for them.

So I think that a general solution for specifying default values would introduce in any case quite some complexity.

Randgalt commented 3 years ago

Note this PR will begin to allow RecordBuilder to support more customizations without sacrificing simplicity for the normal user: https://github.com/Randgalt/record-builder/pull/37