inheriting @Accessors(chain = true)

2,577 views
Skip to first unread message

Marius Kruger

unread,
Jul 11, 2012, 8:34:00 PM7/11/12
to project...@googlegroups.com
Hi awesome lombok devs :)

Given the following:
@Data
@Accessors(chain = true)
public class Parent {
  int id;
}

@Data
@Accessors(chain = true)
public class Child extends Parent {
  String name;
}

I would have liked to be able to do the following:
Child c = new Child().setId(1).setName("foo");
(but it doesn't work since setId returns a Parent)

Solutions I can come up with: 
A) Lombok must override setId in the Child but return Child

B) Lombok must automagically cast to whatever subclass the result is assigned to:
   @SuppressWarnings("unchecked")
    public <T extends Parent> T setId(int id){
        this.id = id;
        return (T)this;
    }
   but then I think you can't mix them around so this is probably a bad idea.


thanks for listening.
--
✝ Marius

Maaartin G

unread,
Jul 12, 2012, 6:54:16 PM7/12/12
to project...@googlegroups.com
On Thursday, July 12, 2012 2:34:00 AM UTC+2, Marius Kruger wrote:
Hi awesome lombok devs :)

Given the following:
@Data
@Accessors(chain = true)
public class Parent {
  int id;
}

@Data
@Accessors(chain = true)
public class Child extends Parent {
  String name;
}

I would have liked to be able to do the following:
Child c = new Child().setId(1).setName("foo");
(but it doesn't work since setId returns a Parent)

Solutions I can come up with: 
A) Lombok must override setId in the Child but return Child

I surely can't speak for the devs, but I'm afraid that it's not possible as lombok at the time it runs knows nothing about the superclass. If it knew, then calling the proper super constructor would be probably the first thing to do.
 
B) Lombok must automagically cast to whatever subclass the result is assigned to:
   @SuppressWarnings("unchecked")
    public <T extends Parent> T setId(int id){
        this.id = id;
        return (T)this;
    }
   but then I think you can't mix them around so this is probably a bad idea.

This is most probably a bad hack allowing to do a lot of incorrect things and get an unexpected CCE. With java lacking self-type there's no sane solution (things like `Enum<E extends Enum<E>>` are way over the edge).
 
As a workaround, I'd suggest doing instead of

Child c = new Child().setId(1).setName("foo");

something like

Child c = new Child();
c.setName("foo").setId(1);

or

Child c = new Child().setName("foo");
c.setId(1);

In each chain you must start with the fields downmost in the hierarchy. Even the most complicated example can get written with two chains only.

Marius Kruger

unread,
Sep 22, 2012, 5:32:38 AM9/22/12
to project...@googlegroups.com
On 13 July 2012 00:54, Maaartin G <graj...@seznam.cz> wrote:
On Thursday, July 12, 2012 2:34:00 AM UTC+2, Marius Kruger wrote:
Given the following:
@Data
@Accessors(chain = true)
public class Parent {
  int id;
}

@Data
@Accessors(chain = true)
public class Child extends Parent {
  String name;
}

I would have liked to be able to do the following:
Child c = new Child().setId(1).setName("foo");
(but it doesn't work since setId returns a Parent)
 
...
 
As a workaround, I'd suggest doing instead of

Child c = new Child().setId(1).setName("foo");

something like
...
Child c = new Child().setName("foo");
c.setId(1);

In each chain you must start with the fields downmost in the hierarchy. Even the most complicated example can get written with two chains only.

I hit this now again :( 
Since it now becomes 3 lines(X), I'm tempted to just use an allargs constructor (Y). 
But to get the nice semantics, I guess I'll have to manually write those 2 methods myself (Z). Lombok is making me lazy.

(X) InstanceFilter filter = new InstanceFilter().instanceClass(instance.getClass());
filter.fieldName(uniqueFieldPerParent).value(filterValue);
someMethod(true, filter);

(Y) someMethod(true, new InstanceFilter(instance.getClass(),
      uniqueFieldPerParent, filterValue));

(Z) someMethod(true, new InstanceFilter()
                            .instanceClass(instance.getClass())
                            .fieldName(uniqueFieldPerParent)
                            .value(filterValue));

--
✝ Marius

Reinier Zwitserloot

unread,
Sep 27, 2012, 6:29:53 AM9/27/12
to project...@googlegroups.com
Well, @Delegate shows that (A) we can do this, and (B) boy, be prepared for a small army of bug reports if we try, because it's far less stable.

Is it such a big itch? you can hack it together with generics, too, but this is also not exactly optimal:

public class <T extends Self<T>> Self<T> {
    public T someMethodThatReturnsSelf() {
        return (T) this; //yeah, this ugly and generates warnings, but it does work.
    }
}

On Thursday, July 12, 2012 2:34:00 AM UTC+2, Marius Kruger wrote:

Andreas Scharf

unread,
Aug 21, 2020, 9:43:43 AM8/21/20
to Project Lombok
Couldn't the problem also be solved by allowing the @Getter annotation to override parents setters or just create setters for parent fileds?
Then the @accessor should be able to pick them up and work as intended (just return this). It should always be possible to return the child class instead of the parent class in an overridden method.

Thorsten Glaser

unread,
Aug 21, 2020, 12:41:34 PM8/21/20
to Project Lombok
> > On Thursday, July 12, 2012 2:34:00 AM UTC+2, Marius Kruger wrote:

> >> B) Lombok must automagically cast to whatever subclass the result is
> >> assigned to:

> >> but then I think you can't mix them around so this is probably a bad
> >> idea.

Why, what is wrong with this? (taken from an actual project)

public <T extends BaseResponse> T setResponseStatus(final EzResponseCode status) {
responseStatus = status;

@SuppressWarnings("unchecked") final T that = (T) this;
return that;
}

This has worked for me so far…

bye,
//mirabilos
--
tarent solutions GmbH
Rochusstraße 2-4, D-53123 Bonn • http://www.tarent.de/
Tel: +49 228 54881-393 • Fax: +49 228 54881-235
HRB 5168 (AG Bonn) • USt-ID (VAT): DE122264941
Geschäftsführer: Dr. Stefan Barth, Kai Ebenrett, Boris Esser, Alexander Steeg

*************************************************

Mit unserem Consulting bieten wir Unternehmen maßgeschneiderte Angebote in
Form von Beratung, Trainings sowie Workshops in den Bereichen
Softwaretechnologie, IT Strategie und Architektur, Innovation und Umsetzung
sowie Agile Organisation.

Besuchen Sie uns auf https://www.tarent.de/consulting .
Wir freuen uns auf Ihren Kontakt.

*************************************************

yawkat

unread,
Aug 21, 2020, 12:48:57 PM8/21/20
to Project Lombok
> Why, what is wrong with this? (taken from an actual project)

This kills type inference and forces you to go "bottom to top" when setting fields: https://javap.yawk.at/#SgKHoy

- Jonas

Thorsten Glaser

unread,
Aug 21, 2020, 12:54:52 PM8/21/20
to Project Lombok
On Fri, 21 Aug 2020, yawkat wrote:

> > Why, what is wrong with this? (taken from an actual project)
>
> This kills type inference and forces you to go "bottom to top" when setting
> fields: https://javap.yawk.at/#SgKHoy

Ah, thanks for the example. But isn’t the cast to T precisely
supposed to make the new B().x().y() work? I mean, the return
value of new B().x() “knows” it is a B, not an A…

yawkat

unread,
Aug 21, 2020, 1:02:47 PM8/21/20
to Project Lombok
You have to think like the compiler in that case. The compiler only knows the code in the current method, and the signatures of other methods. It sees a method <T extends A> T x() on an object that it knows is of the type B, but it can't make any further guesses as to T. It can't "forward-infer" from the fact that the original object is B, because how is it to know the x returns the same object and not, say, C extends A, which is entirely valid according to the type bounds? It also can't "backwards-infer" from the method call y() because (1) java doesn't do that for instance calls and (2) it doesn't actually know enough about y to determine B.y is meant (maybe there is a C.y in a class file the compiler hasn't opened yet?).

Because of this, the compiler is forced to make a conservative guess and infer the lower bound of T, which is A.

- Jonas

Thorsten Glaser

unread,
Aug 21, 2020, 1:24:48 PM8/21/20
to Project Lombok
On Fri, 21 Aug 2020, yawkat wrote:

> You have to think like the compiler in that case. The compiler only knows
> the code in the current method, and the signatures of other methods. It
> sees a method <T extends A> T x() on an object that it knows is of the type
> B, but it can't make any further guesses as to T. It can't "forward-infer"

Why not? At the time x() is called, T is known to be B, not A.

> from the fact that the original object is B, because how is it to know the
> x returns the same object and not, say, C extends A, which is entirely
> valid according to the type bounds?

Huh, why?

If I call…

static <T extends A> T foo(final T bar);

… as foo(new B()) then it return type T is the same T as its argument
type T was, i.e. B, not A.

Unless Java™ is… severely lacking in type-safety…

yawkat

unread,
Aug 21, 2020, 1:31:40 PM8/21/20
to Project Lombok
For foo(new B()) you are giving the inference something to work with – there is nothing that foo could return (without unchecked casts) that would not be assignable to B. Not so with new B().x(), because the this type B has no relationship to T (in fact, the only thing that x can return without an unchecked cast is null, so the compiler just gives up).
Reply all
Reply to author
Forward
0 new messages