--
You received this message because you are subscribed to the Google
Groups group for http://projectlombok.org/
To post to this group, send email to project...@googlegroups.com
To unsubscribe from this group, send email to
project-lombo...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/project-lombok?hl=en
Isn't "with" used for creating a new instance having all but one field
the same as the original? I'm sure I saw it somewhere (a sort of
immutable builder).
> I think "with" is a pretty common naming convention. I don't know
> what the annotation would the called. @Wither? That just sounds
> wrong. Naming aside, I don't think this should be a builder-specific
> feature. Certainly a builder could make use of it though.
IMHO, a builder and the fluent syntax are quite different things,
although their potential use overlaps a lot.
To Reiner: How should the planned lombok builder actually work?
- Where do you put the annotation?
- - Using @Builder for creating an inner class can't be used for third
party classes.
- - Using @BuilderFor(SomeClass.class) as an annotation on an existing
(possibly empty) class is more general, but requires resolution and a
bit more writing.
I'd prefer @BuilderFor, since it allows to specify defaults in the
Builder's default constructor. An alternative could be to define the
defaults using a special annotation on the fields (simple assigning them
makes no sense, since it'd fix the value in case of final fields, unless
some black magic would be used). Another possibility would be to use a
Prototype.
- Should the builder be field or constructor based?
- - For a field based builder there's no way how to set the final fields
besides a constructor (or using Unsafe, serialization, or something even
more crazy), so it doesn't make much sense.
- - A constructor based builder looks better, but requires to specify
which constructor should be used (this is a problem in case of
@BuilderFor). The constructor based @Builder annotation could be placed
on the constructor directly. Unless the constructor gets generated, too.
Can you provide an example how the builder should work. IIRC, there was
no discussion concerning it yet.
Regards, Maaartin.
Not really Reinier speaking here ;). but I'd like to give a reply. As
in earlier threads, we have stated that there are a lot of things
people would like to see in an eventual @Builder annotation, your
pointing out quit a lot of the requests (annotations on the class
itself, or one pointing to a 3rd party class, fluent getter/setters,
default values, list interpretation). Some of these require a modicum
of resolution, some can 'just work' based on the parsed source, just
like the @Data annotation. What I'm saying is that this is not yet a
closed book, we'd really like to hear your opinion, but we don't think
we're going to get to a proposal before we've released the 0.10.0.
>
> - Where do you put the annotation?
> - - Using @Builder for creating an inner class can't be used for third
> party classes.
> - - Using @BuilderFor(SomeClass.class) as an annotation on an existing
> (possibly empty) class is more general, but requires resolution and a
> bit more writing.
I'd hope we'll get both options in, as both have their equal merits.
>
> I'd prefer @BuilderFor, since it allows to specify defaults in the
> Builder's default constructor. An alternative could be to define the
> defaults using a special annotation on the fields (simple assigning them
> makes no sense, since it'd fix the value in case of final fields, unless
> some black magic would be used). Another possibility would be to use a
> Prototype.
>
> - Should the builder be field or constructor based?
> - - For a field based builder there's no way how to set the final fields
> besides a constructor (or using Unsafe, serialization, or something even
> more crazy), so it doesn't make much sense.
> - - A constructor based builder looks better, but requires to specify
> which constructor should be used (this is a problem in case of
> @BuilderFor). The constructor based @Builder annotation could be placed
> on the constructor directly. Unless the constructor gets generated, too.
I'm not sure about this, but I see two main use cases, either
'upgrading' the existing classes/libraries to get a more fluent/less
verbose builder construct or the case in which your creating a new
system. It would seem to me that the @Builder as you state it would be
used in the second case, in which your probably always going to
generate the constructors, but they'll be private. There are other use
cases, but I think this is the most likely one (correct me if I'm
wrong) and I'd rather let lombok have an annotation that supports
making great builders, than one that supports both having a
constructor *and* a builder... at least, initially. Of course, the
@BuilderFor on 3rd classes is another story entirely.
>
> Can you provide an example how the builder should work. IIRC, there was
> no discussion concerning it yet.
>
> Regards, Maaartin.
>
> --
> You received this message because you are subscribed to the Google
> Groups group for http://projectlombok.org/
BTW, since we're in topic. Did you already discuss how a 'wither' with
immutable stuff could be implemented? To me it doesn't seem easy. I
mean: technically you should clone-everything-but the field you're
changing. But how the clone should be performed? Deep or shallow? If you
think of a simple javabean shallow seems the proper solution, but not
always. Sometimes the builder has e.g. a reference to a context that of
course shouldn't be cloned (in my case, I've a personal pattern which is
a 'finder', that is a builder that instead of creating objects from
scratch, it retrieves them from a data source: hence the need of having
a reference to that context).
--
Fabrizio Giudici - Java Architect, Project Manager
Tidalwave s.a.s. - "We make Java work. Everywhere."
java.net/blog/fabriziogiudici - www.tidalwave.it/people
Fabrizio...@tidalwave.it
--
A "wither" is a nice thing. Done manually it involves a copy and paste
of a long constructor call. It's not much work, but looks really funny:
public Something withA(int a) {
return new Something(a, b, c, d, e, f, g);
}
public Something withB(int b) {
return new Something(a, b, c, d, e, f, g);
}
> BTW, since we're in topic. Did you already discuss how a 'wither' with
> immutable stuff could be implemented? To me it doesn't seem easy. I
> mean: technically you should clone-everything-but the field you're
> changing. But how the clone should be performed? Deep or shallow?
IMHO, most of the time you want a shallow copy:
- The fields may be themselves immutable, what the builder can't know.
- The builder has no idea, how to clone my SuperHyperFancyMap, anyway.
- The fields may not cloneable at all, what the builder can't know.
- IMHO, a shallow copy is what most people expect.
A deep copy could be optional. It'd nice if you could provide a Cloner
object to do it for you. But it gets complicated.
However, when you need a deep copy in your "withers", you can do it in
the constructor like in
public Something withC(char[] c) {
return new Something(a, b, c, d, e, f, g);
}
private Something(int a, int b, char[] c, ....) {
this.a = a;
this.b = b;
this.c = c.clone();
....
}
> If you
> think of a simple javabean shallow seems the proper solution, but not
> always. Sometimes the builder has e.g. a reference to a context that of
> course shouldn't be cloned (in my case, I've a personal pattern which is
> a 'finder', that is a builder that instead of creating objects from
> scratch, it retrieves them from a data source: hence the need of having
> a reference to that context).
So shallow cloning your builder (so it receives a reference to the same
context) is the right thing, isn't it?
Regards, Maaartin.
I strongly disagree that a shallow copy is ok. Indeed, cloning almost
everything but lists and maps just leads to a mutable object that people
believe it's immutable. For instance, it would break some of my code.
But I agree that the clone must be performed in the constructor. So,
it's ok for me to have an annotation for a "wither" that just calls the
constructor. Now, we have to find a way to tell the constructor that we
want a deep copy (which BTW it's a feature by itself, today I have to
override Lombok auto-generated constructors in such cases). So, I'd say
we need a parameter in the constructor annotations to specify we want to
clone parameters. I'd say:
1. of course, no problems for everything is scalar
2. for references, clone() must be called (it's always present, so it's
not a Lombok problem to check for it; eventually, Lombok could emit a
warning if the object doesn't implement Cloneable)
3. references to classes annotated with the standard Java annotation
@Immutable won't be cloned
There's still a remainder point, that as per my previous example there
are still cases of references to mutable objects that one wants to not
clone. We need a way to tell this to Lombok. I'm unsure whether to
specify a list of non-cloned fields in the Lombok constructor
annotations, or whether we introduce another annotation such as
@SharedReference to mark these fields.
You're actually right, for public ctors of immutable classes a
"defensive copy" is a must for mutable args. I've been lucky enough to
have no such args.
> For instance, it would break some of my code.
> But I agree that the clone must be performed in the constructor. So,
> it's ok for me to have an annotation for a "wither" that just calls the
> constructor. Now, we have to find a way to tell the constructor that we
> want a deep copy (which BTW it's a feature by itself, today I have to
> override Lombok auto-generated constructors in such cases). So, I'd say
> we need a parameter in the constructor annotations to specify we want to
> clone parameters. I'd say:
Agreed.
> 1. of course, no problems for everything is scalar
No problems for scalars, primitive wrappers, enums, String-s, Class-es,
BigInteger-s, and dozens of other classes.
> 2. for references, clone() must be called (it's always present, so it's
> not a Lombok problem to check for it; eventually, Lombok could emit a
> warning if the object doesn't implement Cloneable)
Calling clone() nearly always is strange. If the clone() method uses
Object.clone() and Cloneable is not implemented, you'll get an
exception. If the object has an usable clone() method and does NOT
implement Cloneable, it's a strange design. Have you ever seen it?
> 3. references to classes annotated with the standard Java annotation
> @Immutable won't be cloned
I've never seen it used. No JDK6 class uses it, not even Guava does
(there are two private classes there annotated with it, that's all).
With calling clone() being the default your constructors would nearly
always throw. You mentioned no way how to make it work e.g. for
com.google.common.collect.ImmutableMap, which is immutable, but not
annotated as such. I'm afraid, not cloning must be the default for
everything except for cloneables.
Neither I see how you want to handle e.g. a StringBuilder, which is
trivial to clone manually, but doesn't implement Cloneable.
> There's still a remainder point, that as per my previous example there
> are still cases of references to mutable objects that one wants to not
> clone. We need a way to tell this to Lombok. I'm unsure whether to
> specify a list of non-cloned fields in the Lombok constructor
> annotations, or whether we introduce another annotation such as
> @SharedReference to mark these fields.
Here, the situation is probably the same as with @EqualsAndHashCode.
This is a very simple decision in comparison to all the other problems.
--
For what concerns annotations in fields, as per Reiner's remark, I
understand it's not an elegant way. In any case, it seems that the point
we're making that moving the cloning issues back to the constructor we
can simply define the behaviour of a whiter, thus it could be
implemented independently of the clone stuff.
Using LinkedHashMap.clone() you get an Object (shame for JDK), which in
fact is a LinkedHashMap.
> @Immutable is very tricky. What does it even mean? Is File immutable?
If File is not immutable, then String isn't either, as there's nothing
you can do with File but not with String (which is a strange API design).
File must be immutable, as it's just an immutable handle identifying
some other object. You could even use primitives for identifying files
(e.g. by numbering files in a directory) and primitives are immutable,
aren't they?
> What about an instance of a Logger?
From practical POV it's immutable, since no sane program cares about its
state. But I agree that @Immutable is very tricky.
> From a practical standpoint, cloning everything is usually not the right
> solution, and even if it was, calling .clone() to do so usually isn't
> the right solution either.
Agreed. Moreover, clone() was never meant as deepClone().
> Introducing a bevy of annotations that go on fields to tell lombok what
> to do is just trading one kind of boilerplate for another. Roel and I
> both strongly feel this is a bad idea as its tantamount to introducing a
> DSL, and that's not what lombok is for.
I can't imagine how annotations could exactly describe what the lombok's
user needs. There are just too many possibilities. For example, you may
want to do no cloning in the package-private ctor and push it to the
public factory method.
(continued)
On 11-03-09 14:43, Reinier Zwitserloot wrote:
> Also, its often the type of the referent and not of the variable that
> has immutability info.
>
> For example, List isn't Immutable, but guava's ImmutableList is.
To make it worse: Depending on what it contains.
> Also, any non-final class cannot be treated as @Immutable if we're
> looking at field types.
You could treat all classes annotated with @Immutable as immutable, just
take it as the implementor's responsibility.
> ... but looking at referent types means introducing lots of reflection,
> which just sounds like a bad idea (but that's gut instinct, possibly
> that's the answer).
Reflection means you want to do it all at runtime, right? This has some
implications:
- A "universal cloning code" is fairly long )not complicated, but needs
to handle a lot of cases, even when restricted to the JDK).
- You surely don't want to blow up the code by copying the beast to each
constructor.
- This means that you either have to introduce a runtime dependency on
lombok whenever this feature gets used (which would be IMHO acceptable)
or emit the beast class in the way APT processors do (which would be
IMHO nicer).
However, you could not handle user's classes, thus possibly missing the
more important part. So instead of incorporating a complicated cloner
into lombok you could let the user do most of the work like follows:
@RequiredArgsConstructor(cloner=com.example.MyCloner.class)
class MyClass {
private final Object a, b;
private final int n;
}
->
class MyClass {
private final Object a, b;
private final int n;
public MyClass(Object a, Object b, int n) {
com.example.MyCloner $cloner = new com.example.MyCloner();
this.a = $cloner.clone(a);
this.a = $cloner.clone(a);
this.n = n;
}
}
There's some magic involved, but not much:
- Requiring a no-args ctor is quite common.
- Requiring a method "T clone(T)" is about the same as requiring
"close()" for @Cleanup.
This looks like shifting all the work to runtime, but not really. It
could look like
public class MyCloner {
MyImmutable clone(MyImmutable x) {
return x;
}
....
<T> T clone(T x) { // fallback method
return ReflectionMagic.clone(x);
}
}
I think there's neither much magic nor much work involved and it should
satisfy all Fabrizio's needs (and not only his).
> Well, that's a pity for JDK 6 and Guava :-)
I'd suppose, for JDK6 it'll take some years (or centuries) till they
start to use it. For Guava, an ImmutableList<String> is @Immutable, but
ImmutableList<Object> is not. I had a look at a couple of other classes
and there's nothing where it could be put on.
Maybe the definition of @Immutable is too restrictive. Maybe it should
be allowed on non-final classes and delegate the responsibility to the
subclass implementer. Actually, when you want, you may mutate everything
using reflection, Unsafe, or JNI, so relaxing @Immutable this way makes
sense. This way CharMatcher could be @Immutable, too. Not so much gained.
Restricting the subclasses to be @Immutable by contract may be a
problem, too. What if you really need a mutable subclass? What if you
need some caching? Are you allowed to do the caching in a thread-safe way?
> But I use them, as for the
> other sisters from Concurrency In Practice. They are part of JSR 305 and
> have a standard javax package. They are also well defined semantically,
> in fact FindBugs can automatically check whether they are used in a
> consistent way. That's why I suggest that Lombok can take advantage of
> them - if one use them in a inconsistent way, it's a problem of that
> guy, as if it implemented equals() in a broken way.
The missing @Immutable on someone's classes is not a their problem but
ours. For JDK there's a simple solution: hardwiring the information into
lombok. For third party classes it's harder.
> For what concerns annotations in fields, as per Reiner's remark, I
> understand it's not an elegant way. In any case, it seems that the point
> we're making that moving the cloning issues back to the constructor we
> can simply define the behaviour of a whiter, thus it could be
> implemented independently of the clone stuff.
I'm afraid, it's too complicated to be handled automatically.
But I said previously that this is very independent from the wither
stuff, if we just decide that the wither calls a constructor. The wither
indeed has only to call the constructor and then it's up to the
programmer to eventually provide it in the good way, as Reiner's said.
I know this is getting a bit off-topic, but immutability is a kinda pet topic
of mine.
The difference between File and String is that there are methods of File, where
the same instance can return different results at different times. Granted the
method results depend on the state of some other thing (the underlying
filesystem) and iirc File instances themselves don't have any state to mutate,
but I think a 'turtles all the way down' kind of immutability is the most
accepted definition. Files and loggers fall into a side-effect-free distinction
that's subtly different enough to not be considered immutable.
For Files, it's only the reference that's immutable i.e. a File instance will
always point to the same place on the filesystem, but the state of the instance
itself is mutable, since isEmpty() can return true for one call, then false
for a subsequent call.
I'd like to hear your thoughts on that, unless it's getting too off-topic for
this list, in which case I apologise :)
Regards,
Graham
I'm assuming you mean a wither in the sense of chainable setter, not in
sense of the thing creating a new immutable instance with one field
replaced.
IMHO, it'll always be useful, since you can use it on existing
instances, too. OTOH, it'd be most useful on beans, where it's
incompatible with the beanspec. Couldn't you change the beanspec? :D
> Also, @Wither is not a name I'm comfortable with. How about a parameter
> on @Setter? A 'normal' (beanspec compatible) setter can't coexist with a
> wither.
It'd be nice if the chainability could be specified on the whole class
even in case you don't want setters on each field like
@Setter(chainable=true, value=AccessLevel.NONE)
class C {
@Setter int a;
/* no setter */ int b;
}
This is a bit strange, since it means that the Setter.chainable on the
whole class should be "inherited" by the field, but Setter.value should
not. OTOH, I can't imagine why anybody would like to combine chainable
and non-chainable setters in a single class.
--
As I mentioned before, withX () as method name is not an option as it implies creating a new object that is a clone except for this one field.
It can be setX() or just x().
As I mentioned before, withX () as method name is not an option as it implies creating a new object that is a clone except for this one field.
Can you come up with a better name? Maybe simply removing the prefix
altogether and using
class C {
private int x;
C x(int x) {
this.x = x;
}
}
is the way to go?
Btw., Guice doesn't care about the setters returning anything. In fact
it doesn't care about setters at all, just give it a method like
@Inject
boolean doSomething(@Named("limit") int limit, MyFancyThing thing);
and it'll call it.
Regards, Maaartin.
Yes, just x(val) is the frontrunner right now.
* thanks to Allan for the discussion about File (im)mutability. I think
that the discussion about immutability is interesting, even though not
pertinent here over a certain threshold... I think I'm going to post
something at the JavaPosse :-)
* "withers" are anyway useful independently of builders, so let's go;
I'm rather perplexed with the name thing:
* I agree that 'wither' is not good for anything else than a discussion
* I agree that current plain setters must be kept as void
* I disagree about the rest of the points :o)
In particular, I don't understand why it's so important to prevent
people from creating a setter which is not void (apart from backward
compatibility, of course). Yes, there's a precise *documented
convention* about setters to return void. But there's also a precise
*language specification* that mandates some rules about checked
exceptions and these are happily violated by @SneakyThrows. Smells like
a double standard, right? :-)
On the other side, there's no *documented convention* about withXXX() to
return a clone. It's just a convention used by some people. I must add
that javax.persistence.Query is a standard Java API and its setters
return this. So I don't see the point for Lombok being more picky
(pickier?) than the Java runtime itself. Let the programmers decide. My
proposal:
@Setter(chainable=true, prefix="with")
chainable defaults to false and prefix to "set", so we're backward
compatible. This makes it possible for people to use chainable setters
(chainable=true, prefix="set"), withers (chainable=true, prefix="with"),
or even simple methods such as x() (chainable=true, prefix="").
Of course, style=something in place of chainable=true is fine for me all
the way.
It looks nice, but aren't there frameworks requiring the "set" prefix
while ignoring the return type? I don't recall any, but I think they
exist as there's no sane reason to care about the return type. Unlike
generating non-void setters, whenever you see something like "setX(x)"
you may IMHO assume it's a setter without breaking anything.
(continued)
On 11-03-13 16:12, Fabrizio Giudici wrote:
> My points:
>
> * thanks to Allan for the discussion about File (im)mutability. I think
> that the discussion about immutability is interesting, even though not
> pertinent here over a certain threshold... I think I'm going to post
> something at the JavaPosse :-)
> * "withers" are anyway useful independently of builders, so let's go;
>
> I'm rather perplexed with the name thing:
>
> * I agree that 'wither' is not good for anything else than a discussion
> * I agree that current plain setters must be kept as void
> * I disagree about the rest of the points :o)
>
> In particular, I don't understand why it's so important to prevent
> people from creating a setter which is not void (apart from backward
> compatibility, of course). Yes, there's a precise *documented
> convention* about setters to return void. But there's also a precise
> *language specification* that mandates some rules about checked
> exceptions and these are happily violated by @SneakyThrows. Smells like
> a double standard, right? :-)
Not really, @SneakyThrows is not an implementation of something in a way
violating some rules, it's a feature meant to break the rules. Nobody
using @SneakyThrows can complain about it working wrongly or in a
non-standard fashion, since by it's very use you commit to breaking the
rules.
> On the other side, there's no *documented convention* about withXXX() to
> return a clone. It's just a convention used by some people. I must add
> that javax.persistence.Query is a standard Java API and its setters
> return this. So I don't see the point for Lombok being more picky
> (pickier?) than the Java runtime itself. Let the programmers decide. My
> proposal:
Good point!
> @Setter(chainable=true, prefix="with")
>
> chainable defaults to false and prefix to "set", so we're backward
> compatible.
Not exactly, because of the strange set/is convention. I'd reserve the
prefix "set" for using "set" systematically and use "-" (or whatever)
for the set/is obsession.
> This makes it possible for people to use chainable setters
> (chainable=true, prefix="set"), withers (chainable=true, prefix="with"),
> or even simple methods such as x() (chainable=true, prefix="").
The downside is that people may use it in a non-consequent way ending
with some strange mixture of differently named setters in different
classes. OTOH, this could happen without lombok, too (albeit it's less
probable, since forgetting the args to @Setter is easier than writing a
setter differently). Some global option would be nice...
> Of course, style=something in place of chainable=true is fine for me all
> the way.
Regards, Maaartin.
I think it can be resolved by changing the syntax of the annotation
attributes. I mean, it's a sort of challenge to keep the syntax short
(*), but the conceptual point is that Lombok should let the programmer
decide how to (ab)use setters and withers, leaving at least design
principles out of the scenario.
(*) Too bad they didn't allowed multiple same-class annotations on the
same syntatical element. We could just have:
@Setter @Setter(chainable=true, prefix="with")
private int x;
There's an easy solution. Let
@Setter(chainable=true, prefix="", standard=true)
generate the fancy setter in addition to the conforming one. But three
options are not nice and generating two non-chainable setters makes no
sense, so maybe something like
enum SetterStyle {DEFAULT, CHAINABLE, BOTH}
together with
public @interface Setter {
SetterStyle setterStyle() default SetterStyle.DEFAULT;
String prefix default "-";
...
}
could be the way to go.
@Setter =>
void setX(int x);
@Setter(setterStyle=CHAINABLE) =>
Something x(int x);
@Setter(setterStyle=BOTH) =>
void setX(int x);
Something x(int x);
@Setter(setterStyle=CHAINABLE, prefix="with") =>
Something withX(int x);
@Setter(setterStyle=BOTH, prefix="with") =>
void setX(int x);
Something withX(int x);
@Setter(setterStyle=BOTH, prefix="set") =>
error
Unfortunately, it's bit verbose; as Fabrizio said,
@Setter @Setter(chainable=true, prefix="with")
would be much nicer.
I would in no case use the "with" prefix as default, as there may be a
"real wither" one day returning a new instance of an immutable type.
--
Thank you, I see, I misspelled his name anyway, sorry for it.
> in earlier threads, we have stated that there are a lot of things
> people would like to see in an eventual @Builder annotation, your
> pointing out quit a lot of the requests (annotations on the class
> itself, or one pointing to a 3rd party class, fluent getter/setters,
> default values, list interpretation). Some of these require a modicum
> of resolution, some can 'just work' based on the parsed source, just
> like the @Data annotation. What I'm saying is that this is not yet a
> closed book, we'd really like to hear your opinion, but we don't think
> we're going to get to a proposal before we've released the 0.10.0.
>
>>
>> - Where do you put the annotation?
>> - - Using @Builder for creating an inner class can't be used for third
>> party classes.
>> - - Using @BuilderFor(SomeClass.class) as an annotation on an existing
>> (possibly empty) class is more general, but requires resolution and a
>> bit more writing.
>
> I'd hope we'll get both options in, as both have their equal merits.
Thank you for this info. I hope too.
>> - Should the builder be field or constructor based?
>> - - For a field based builder there's no way how to set the final fields
>> besides a constructor (or using Unsafe, serialization, or something even
>> more crazy), so it doesn't make much sense.
>> - - A constructor based builder looks better, but requires to specify
>> which constructor should be used (this is a problem in case of
>> @BuilderFor). The constructor based @Builder annotation could be placed
>> on the constructor directly. Unless the constructor gets generated, too.
>
> I'm not sure about this, but I see two main use cases, either
> 'upgrading' the existing classes/libraries to get a more fluent/less
> verbose builder construct or the case in which your creating a new
> system. It would seem to me that the @Builder as you state it would be
> used in the second case, in which your probably always going to
> generate the constructors, but they'll be private. There are other use
> cases, but I think this is the most likely one (correct me if I'm
> wrong) and I'd rather let lombok have an annotation that supports
> making great builders, than one that supports both having a
> constructor *and* a builder... at least, initially. Of course, the
> @BuilderFor on 3rd classes is another story entirely.
IMHO, one of the most important uses of a builder is to replace long
argument list of constructors. Actually, it could be used to replace all
long argument lists. For other methods it's not as important as for
ctors, but thinking about a *builder for an argument list* instead of
for an object may itself be fruitful.
I had a look at some ctors which could make use of a builder:
Thread(ThreadGroup group, Runnable target, String name, long stackSize)
There exist 8 out of possible 16 ctors, which is surely sufficient, just
not very nice. With all argument types being different there's no
confusion with the overloading. In any case a builder would be much nicer.
For e.g. Font and Dialog there are multiple ctors, but it all is too
confused to be worth digging in deeply.
An interesting and complex example of a (sort of) builder is the
MapMaker, which contains about 10 settable fields (or such planned to
settable in the future like e.g. keyEquivalence). There are some tests
in each method which may make the automatic generation of such a builder
complicated. For example, it tests that the evictionListener is not null
and that the maximumSize is non-negative. IMHO, the former could do
Lombok using an annotation, but there's no standard annotation for the
latter. While non-negativity is quite a common requirement, if you
started to create a corresponding annotation, the question is where to
stop. Maybe something like
@NotNull MapEvictionListener evictionListener;
@CheckedBy(MyNonNegativityChecker.class) int maximumSize;
could work. Somehow. Otherwise the checks had to be postponed to the
ctor which wouldn't be fail-fast.
There are also checks for not specifying a value twice, which may
capture some errors but limit the usability a bit. This should be
probably optional in a general builder.
There are also some checks for combined conditions like not specifying
both expireAfterAccess and expireAfterWrite. I don't think this
could/should be automated at all. I wonder if there's a way for
combining such checks with lombok-generated code.
Regards, Maaartin.
IMHO, one of the most important uses of a builder is to replace long
argument list of constructors. Actually, it could be used to replace all
long argument lists. For other methods it's not as important as for
ctors, but thinking about a *builder for an argument list* instead of
for an object may itself be fruitful.
@NotNull MapEvictionListener evictionListener;
@CheckedBy(MyNonNegativityChecker.class) int maximumSize;
could work. Somehow. Otherwise the checks had to be postponed to the
ctor which wouldn't be fail-fast.
There are also checks for not specifying a value twice, which may
capture some errors but limit the usability a bit. This should be
probably optional in a general builder.
There are also some checks for combined conditions like not specifying
both expireAfterAccess and expireAfterWrite. I don't think this
could/should be automated at all. I wonder if there's a way for
combining such checks with lombok-generated code.