Generic fluent Builder in Java -


i'm aware there've been similar questions. haven't seen answer question though.

i'll present want simplified code. have complex object, of values generic:

public static class someobject<t, s> {     public int number;     public t singlegeneric;     public list<s> listgeneric;      public someobject(int number, t singlegeneric, list<s> listgeneric) {         this.number = number;         this.singlegeneric = singlegeneric;         this.listgeneric = listgeneric;     } } 

i'd construct fluent builder syntax. i'd make elegant though. wish worked that:

someobject<string, integer> works = new builder() // not generic yet!     .withnumber(4)       // , here "lifted";      // since it's set on integer type list     .withlist(new arraylist<integer>())       // , decision go string type single value     // made here:     .withtyped("something")       // we've gathered type info along way     .create(); 

no unsafe cast warnings, , no need specify generic types upfront (at top, builder constructed).

instead, let type info flow in explicitly, further down chain - along withlist , withtyped calls.

now, elegant way achieve it?

i'm aware of common tricks, such use of recursive generics, toyed while , couldn't figure out how applies use case.

below mundane verbose solution works in sense of satisfying requirements, @ cost of great verbosity - introduces 4 builders (unrelated in terms of inheritance), representing 4 possible combinations of t , s types being defined or not.

it work, that's hardly version proud of, , unmaintainable if expected more generic parameters two.

public static class builder  {     private int number;      public builder withnumber(int number) {         this.number = number;         return this;     }      public <t> typedbuilder<t> withtyped(t t) {         return new typedbuilder<t>()                 .withnumber(this.number)                 .withtyped(t);     }      public <s> typedlistbuilder<s> withlist(list<s> list) {         return new typedlistbuilder<s>()                 .withnumber(number)                 .withlist(list);     } }  public static class typedlistbuilder<s> {     private int number;     private list<s> list;      public typedlistbuilder<s> withlist(list<s> list) {         this.list = list;         return this;     }      public <t> typedbothbuilder<t, s> withtyped(t t) {         return new typedbothbuilder<t, s>()                 .withlist(list)                 .withnumber(number)                 .withtyped(t);     }      public typedlistbuilder<s> withnumber(int number) {         this.number = number;         return this;     } }  public static class typedbothbuilder<t, s> {     private int number;     private list<s> list;     private t typed;      public typedbothbuilder<t, s> withlist(list<s> list) {         this.list = list;         return this;     }      public typedbothbuilder<t, s> withtyped(t t) {         this.typed = t;         return this;     }      public typedbothbuilder<t, s> withnumber(int number) {         this.number = number;         return this;     }      public someobject<t, s> create() {         return new someobject<>(number, typed, list);     } }  public static class typedbuilder<t> {     private int number;     private t typed;      private builder builder = new builder();      public typedbuilder<t> withnumber(int value) {         this.number = value;         return this;     }      public typedbuilder<t> withtyped(t t) {         typed = t;         return this;     }      public <s> typedbothbuilder<t, s> withlist(list<s> list) {         return new typedbothbuilder<t, s>()                 .withnumber(number)                 .withtyped(typed)                 .withlist(list);     } } 

is there more clever technique apply?

okay, more traditional step-builder approach this.

unfortunately, because we're mixing generic , non-generic methods, have redeclare lot of methods. don't think there's nice way around this.

the basic idea just: define each step on interface, implement them on private class. can generic interfaces inheriting raw types. it's ugly, works.

public interface numberstep {     numberstep withnumber(int number); } public interface neitherdonestep extends numberstep {     @override neitherdonestep withnumber(int number);     <t> typedonestep<t> withtyped(t type);     <s> listdonestep<s> withlist(list<s> list); } public interface typedonestep<t> extends numberstep {     @override typedonestep<t> withnumber(int number);     typedonestep<t> withtyped(t type);     <s> bothdonestep<t, s> withlist(list<s> list); } public interface listdonestep<s> extends numberstep {     @override listdonestep<s> withnumber(int number);     <t> bothdonestep<t, s> withtyped(t type);     listdonestep<s> withlist(list<s> list); } public interface bothdonestep<t, s> extends numberstep {     @override bothdonestep<t, s> withnumber(int number);     bothdonestep<t, s> withtyped(t type);     bothdonestep<t, s> withlist(list<s> list);     someobject<t, s> create(); } @suppresswarnings({"rawtypes","unchecked"}) private static final class builderimpl implements neitherdonestep, typedonestep, listdonestep, bothdonestep {     private final int number;     private final object typed;     private final list list;      private builderimpl(int number, object typed, list list) {         this.number = number;         this.typed  = typed;         this.list   = list;     }      @override     public builderimpl withnumber(int number) {         return new builderimpl(number, this.typed, this.list);     }      @override     public builderimpl withtyped(object typed) {         // return 'this' @ risk of heap pollution         return new builderimpl(this.number, typed, this.list);     }      @override     public builderimpl withlist(list list) {         // return 'this' @ risk of heap pollution         return new builderimpl(this.number, this.typed, list);     }      @override     public someobject create() {         return new someobject(number, typed, list);     } }  // static factory public static neitherdonestep builder() {     return new builderimpl(0, null, null); } 

since don't want people accessing ugly implementation, make private , make go through static method.

otherwise works pretty same own idea:

someobject<string, integer> works =     someobject.builder()         .withnumber(4)         .withlist(new arraylist<integer>())         .withtyped("something")         .create(); 

// return 'this' @ risk of heap pollution

what about? okay, there's problem in general here, , it's this:

neitherdonestep step = someobject.builder(); bothdonestep<string, integer> both =     step.withtyped("abc")         .withlist(arrays.aslist(123)); // setting 'typed' integer when // set string step.withtyped(123); someobject<string, integer> oops = both.create(); 

if didn't create copies, we'd have 123 masquerading around string.

(if you're using builder fluent set of calls, can't happen.)

although don't need make copy withnumber, went step , made builder immutable. we're creating more objects have there isn't solution. if going use builder in correct manner, make mutable , return this.


since we're interested in novel generic solutions, here builder implementation in single class.

the difference here don't retain types of typed , list if invoke either of setters second time. isn't drawback per se, it's different guess. means can this:

someobject<long, string> =     someobject.builder()         .withtype( new integer(1) )         .withlist( arrays.aslist("abc","def") )         .withtype( new long(1l) ) // <-- changing t here         .create(); 
public static class onebuilder<t, s> {     private final int number;     private final t typed;     private final list<s> list;      private onebuilder(int number, t typed, list<s> list) {         this.number = number;         this.typed  = typed;         this.list   = list;     }      public onebuilder<t, s> withnumber(int number) {         return new onebuilder<t, s>(number, this.typed, this.list);     }      public <tr> onebuilder<tr, s> withtyped(tr typed) {         // return 'this' @ risk of heap pollution         return new onebuilder<tr, s>(this.number, typed, this.list);     }      public <sr> onebuilder<t, sr> withlist(list<sr> list) {         // return 'this' @ risk of heap pollution         return new onebuilder<t, sr>(this.number, this.typed, list);     }      public someobject<t, s> create() {         return new someobject<t, s>(number, typed, list);     } }  // side note, // return e.g. <?, ?> here if wanted restrict // return type of create() in case // calls immediately. // type arguments specify here whatever // want create() return before withtyped(...) , // withlist(...) each called @ least once. public static onebuilder<object, object> builder() {     return new onebuilder<object, object>(0, null, null); } 

same thing creating copies , heap pollution.


now we're getting really novel. idea here can "disable" each method causing capture conversion error.

it's little complicated explain, basic idea is:

  • each method somehow depends on type variable declared on class.
  • "disable" method having return type set type variable ?.
  • this causes capture conversion error if attempt invoke method on return value.

the difference between example , previous example if try call setter second time, compiler error:

someobject<long, string> =     someobject.builder()         .withtype( new integer(1) )         .withlist( arrays.aslist("abc","def") )         .withtype( new long(1l) ) // <-- compiler error here         .create(); 

thus, can call each setter once.

the 2 major downsides here you:

  • can't call setters second time legitimate reasons
  • and can call setters second time null literal.

i think it's pretty interesting proof-of-concept, if it's little impractical.

public static class onebuilder<t, s, tcap, scap> {     private final int number;     private final t typed;     private final list<s> list;      private onebuilder(int number, t typed, list<s> list) {         this.number = number;         this.typed  = typed;         this.list   = list;     }      public onebuilder<t, s, tcap, scap> withnumber(int number) {         return new onebuilder<t, s, tcap, scap>(number, this.typed, this.list);     }      public <tr extends tcap> onebuilder<tr, s, ?, scap> withtyped(tr typed) {         // return 'this' @ risk of heap pollution         return new onebuilder<tr, s, tcap, scap>(this.number, typed, this.list);     }      public <sr extends scap> onebuilder<t, sr, tcap, ?> withlist(list<sr> list) {         // return 'this' @ risk of heap pollution         return new onebuilder<t, sr, tcap, scap>(this.number, this.typed, list);     }      public someobject<t, s> create() {         return new someobject<t, s>(number, typed, list);     } }  // same thing previous example, // return <?, ?, object, object> if wanted // restrict return type of create() in case // called immediately. // (the type arguments tcap , scap should stay // object because initial bound of tr , sr.) public static onebuilder<object, object, object, object> builder() {     return new onebuilder<object, object, object, object>(0, null, null); } 

again, same thing creating copies , heap pollution.


anyway, hope gives ideas sink teeth in to. : )

if you're interested in sort of thing, recommend learning code generation annotation processing, because can generate things easier writing them hand. talked in comments, writing things hand becomes unrealistic pretty quickly.


Comments

Popular posts from this blog

html - Styling progress bar with inline style -

java - Oracle Sql developer error: could not install some modules -

How to use autoclose brackets in Jupyter notebook? -