Constructor Initialization

1,092 views
Skip to first unread message

Francis Lalonde

unread,
Sep 20, 2016, 10:29:30 AM9/20/16
to Project Lombok
It would be nice if @XArgsConstructor and @Builder allowed for a standard post-field assignment initialization method.

Something like this would be great :

@RequiredArgsConstructor
@AfterConstructor("start")
class Resource {
    private final String filename;
    private File file;
   
    public void start() {
        file = new File(filename);
    }
}

Without this, when writing resource-owning components, I often myself rewriting otherwise-lomboked constructors just to add a resource initialization block after all the fields have been assigned.

class Resource {
    private final String filename;
    private File file;
   
    public void Resource(String filename) {
        this.filename = filename;
        // initializer code below
        file = new File(filename);
    }
}

My example is short, but this gets tiresome quickly when the class has many fields or defines multiple constructors with @NonNull and other Lombok-provided niceties :).

I've also used @Getter(lazy = true) to get call the initializer on first use, but this feels like a patch as it makes initialization non-deterministic, requires all internal methods to use the getter and introduces unneeded overhead (albeit slight).

Yet another alternative is to put the onus of initialization on the calling code, which will be the source of errors when caller neglects to call the initializer method after new Resource() or Resource.builder().build().

This would be similar to the javax.annotation.PostConstruct mechanism.

What do you think?

Francis

Francis Lalonde

unread,
Sep 20, 2016, 10:40:40 AM9/20/16
to Project Lombok
A possibly less-intrusive variation on this would be to have the initialization mechanism be available only when using @Builder. Having an @Builder(initializerMethod="start") would solve the use case nicely. The builder would call build(), followed by the initializer, if specified.

Martin Grajcar

unread,
Sep 20, 2016, 11:16:56 AM9/20/16
to project...@googlegroups.com
This old proposal of mine would help:

@RequiredArgsConstructor

class Resource {
    private final String filename;

    @InConstructor
    private final File file = new File(filename);
}


Unfortunately, it's only a proposal. Making the field final is usually what I want.

In your case, it could be better to drop filename as it's contained in file. Using @AllArgsConstructor(access=PRIVATE) and adding a static factory method is a possible workaround you didn't mention. Alternatively, you could write a delegating constructor.


On Tue, Sep 20, 2016 at 4:40 PM, Francis Lalonde <frala...@gmail.com> wrote:
A possibly less-intrusive variation on this would be to have the initialization mechanism be available only when using @Builder. Having an @Builder(initializerMethod="start") would solve the use case nicely. The builder would call build(), followed by the initializer, if specified.

--
You received this message because you are subscribed to the Google Groups "Project Lombok" group.
To unsubscribe from this group and stop receiving emails from it, send an email to project-lombok+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Francis Lalonde

unread,
Sep 20, 2016, 12:31:01 PM9/20/16
to Project Lombok

In your case, it could be better to drop filename as it's contained in file. Using @AllArgsConstructor(access=PRIVATE) and adding a static factory method is a possible workaround you didn't mention. Alternatively, you could write a delegating constructor.


I may have formulated my proposition backwards...What I'd really like is to keep @Builder and @XArgsConstructor functionality. Writing constructors and factory methods is so 2009...

Also my proposal would not only cover resource initialization, but would allow call external methods too. Like if you need to register the new object in a global map, or clear a cache in response to this new object being built.
 

Martin Grajcar

unread,
Sep 20, 2016, 1:20:10 PM9/20/16
to project...@googlegroups.com
On Tue, Sep 20, 2016 at 6:31 PM, Francis Lalonde <frala...@gmail.com> wrote:

In your case, it could be better to drop filename as it's contained in file. Using @AllArgsConstructor(access=PRIVATE) and adding a static factory method is a possible workaround you didn't mention. Alternatively, you could write a delegating constructor.


I may have formulated my proposition backwards...What I'd really like is to keep @Builder and @XArgsConstructor functionality. Writing constructors and factory methods is so 2009...

It's my fault: I commented on a way how to adapt the generated constructor to a better constructor, but I forgot that you don't want any.
 
Also my proposal would not only cover resource initialization, but would allow call external methods too. Like if you need to register the new object in a global map, or clear a cache in response to this new object being built.

I'm afraid, writing the Builder.build() method manually is the easiest way to achieve this. If Lombok could help with this, I'd suggest it to do the work in the constructor instead as its more general. It could look like this:

@RequiredArgsConstructor
class Resource {
    private final String filename;

    @InConstructor
    private final File file = new File(filename);

    @InConstructor
    private registerMeInAGlobalMap() {
        ...
    }
}

This would be a straightforward extension: Allow the annotation on parameterless methods, too, and append the method to every constructor (generated or not). It partly overlaps with the @Validator proposal.

I'd love to use both of them, but unfortunately, hacking Lombok is hard (I've tried once with some limited success) and there isn't enough demand.

Francis Lalonde

unread,
Sep 20, 2016, 5:19:13 PM9/20/16
to Project Lombok
Having @InConstructor on any () method would definitely work. I've once hacked my way in Lombok to add varargs (...) setters (or constructor, I dont remember). It worked, but it seems my approach was naive. This doesn't look that complicated either, but I'm sure there'd be subtleties.

How would you go about writing a custom build() method when the whole Builder is generated behind the scenes?

Martin Grajcar

unread,
Sep 20, 2016, 10:56:50 PM9/20/16
to project...@googlegroups.com
On Tue, Sep 20, 2016 at 11:19 PM, Francis Lalonde <frala...@gmail.com> wrote:
Having @InConstructor on any () method would definitely work. I've once hacked my way in Lombok to add varargs (...) setters (or constructor, I dont remember). It worked, but it seems my approach was naive. This doesn't look that complicated either, but I'm sure there'd be subtleties.

And so am I, especially, when the method call should be added also to manually written constructors containing return statements. We could argue that it's not needed as you could do it easily manually and then I can't see any problems for the method annotation.

The field annotation looks more complicated.
 
How would you go about writing a custom build() method when the whole Builder is generated behind the scenes?

IIRC, you can write an incomplete static inner class and let lombok complete it.

Unfortunately, you can not write an incomplete method like

public static class Builder {
    public Resource build() {
        doSomeConsistencyChecks();
        Resource result = new lombok.Placeholder().call();
        result.registerMeInAGlobalMap();
        return result;
    }
}

where the placeholder call would be replaced by the code lombok normally generates. No idea if this could be useful enough.

FrankFurther

unread,
Sep 21, 2016, 8:15:31 AM9/21/16
to Project Lombok

IIRC, you can write an incomplete static inner class and let lombok complete it.

public static class Builder {
 

That solves another of my issues, where I wanted to have additional alternate setters with automatic type conversion (varargs to list, etc.). Now it looks like it need to also define each field I want my custom setters to set, so I end up doing half the work (yes I am lazy) but it's still pretty neat, Thanks!

Since the Constructor extenders look hard, maybe we could use that public static class Builder mechanism to allow for build() extension instead. Since build() is entirely generated, it should be straightforward to add a check for a manually defined post-build trigger method such as

  public static class Builder {
    @AfterBuild
    void init(Resource newlyBuilt)
{

which build()would call before returning. So you'd need to use @Builder to get the functionality, but you'd sidestep all problems that come with Constructor manipulation.

FrankFurther

unread,
Sep 21, 2016, 8:21:43 AM9/21/16
to Project Lombok

 Now it looks like it need to also define each field I want my custom setters to set, so I end up doing half the work (yes I am lazy).


Scratch that, Lombok puts the fields in for me, I just need to supply the static builder class and my custom methods, Laziness wins again.

Martin Grajcar

unread,
Sep 21, 2016, 8:41:32 AM9/21/16
to project...@googlegroups.com
Does it? I guess injecting a method call into a constructor is exactly as complicated as injecting it into the Builder.

I guess, @BeforeBuild could be useful, too, e.g., for setting values to @NonNull fields, when omitted in the builder like

@Builder
class Resource {
    @NonNull private final String firstName;
    @NonNull private final String title;

  public static class Builder {
    @BeforeBuild
    void beforeBuild() {
        if (title == null) title = "";
    }
}

Resource.builder().firstName("M").build();

I guess, this could be also done by declaring
private String title = "";
in the Builder (it's not exactly equivalent).

FrankFurther

unread,
Sep 21, 2016, 8:44:50 AM9/21/16
to Project Lombok
Ok, I found a way. I don't if this will be satifying to you.


@Builder(buildMethodName="unsafeBuild")
public class Resource {

    public void init() {
        // after constructor code goes here
    }

    public static class ResourceBuilder {              
        /**
         * Initializing builder supplementing the Lombok one
         * @return
         */
        public JmxPublisher build() {
            JmxPublisher resource = unsafeBuild();
            resource.init();
            return
resource;
        }
    }

 
This is a variation of the "unsafeBuilder/builder" static factory method I was already using with @Builder to provide safe default values for my builders :

@Builder(builderMethodName = "unsafeBuilder")
public class Resource

    public static ResourceBuilder builder() {
        return unsafeBuilder()
                .setData(SAFE_DEFAULT);
    }

Which I am pretty comfortable with, even if it means the client can still call unsafeBuilder (or now, unsafeBuild) directly, they'd have to be a bit dumb to do so. Although often the client is me, so it's still a possibility.

So maybe these patterns should simply be documented better? We need a Lombook.
Message has been deleted
Message has been deleted

Martin Grajcar

unread,
Sep 21, 2016, 9:42:39 AM9/21/16
to project...@googlegroups.com
On Wed, Sep 21, 2016 at 2:44 PM, FrankFurther <frala...@gmail.com> wrote:
Ok, I found a way. I don't if this will be satifying to you.

I rarely use builder, but I'm glad it works for you.

Which I am pretty comfortable with, even if it means the client can still call unsafeBuilder (or now, unsafeBuild) directly, they'd have to be a bit dumb to do so. Although often the client is me, so it's still a possibility.

:D Having a possibility to change the visibility of unsafeBuild() to private would make it perfect. It makes out @BeforeBuild and @AfterBuild unnecessary.

So maybe these patterns should simply be documented better? We need a Lombook.

Yes, we do.

My bad that I didn't come up with this as I was proposing the delegating constructor... it's a simple delegation, too.
Reply all
Reply to author
Forward
0 new messages