Search code examples
javagenericsdesign-patternstype-inferencebuilder

java type inference in builder pattern


i want to create a parameterized (generic) class MyType<T> and its builder. builder will have some methods that ignore the type T and some methods that use the type. but it seems like using this pattern i have to repeat the T declaration:

                     how to omit this declaration?
                                   |
                                   V
MyType<String> myType = Builder.<String>of()
                               .method1(argument_can_be_of_any_type)
                               .method2(argument_must_be_of_type_T = String)
                               .build();

is it possible to use some java syntax tricks or other design patterns to omit the second type declaration and make the API more user friendly? like for example:

List<String> aList = Lists.emptyList();

or

List<String> aList = new LinkedList<>();   

i don't want to enforce any order of methods in the builder


Solution

  • I've reworked things a bit since my original answer didn't work as advertised. (Thanks to shmosel for spotting the problem, and to Daniel Pryden for the suggested solution.)

    /* contents of Box.java */
    public class Box<T>
    {
        private T contents;
        private Object data;
    
        protected Box(T contents, Object data) {
            this.contents = contents;
            this.data = data;
        }
    
        public static BoxBuilder builder() {
            return new BoxBuilder();
        }
    
        public T getContents() {
            return contents;
        }
    }
    
    /* contents of BoxBuilder.java */
    public class BoxBuilder
    {
        private Object data;
    
        public BoxBuilder withAnything(Object o) {
            this.data = o;
            return this;
        }
    
        // Infers new type from argument
        public <T> TypedBoxBuilder<T> withBoxContent(T contents) {
            TypedBoxBuilder<T> builder = new TypedBoxBuilder<T>();
            builder.setData(data);
            builder.setContents(contents);
    
            return builder;
        }
    }
    
    /* contents of TypedBoxBuilder.java */
    public class TypedBoxBuilder<T>
    {
        private T contents;
        private Object data;
    
        public TypedBoxBuilder() {
        }
    
        public TypedBoxBuilder<T> withAnything(Object data) {
            this.data = data;
            return this;
        }
    
        public TypedBoxBuilder<T> withContents(T contents) {
            this.contents = contents;
            return this;
        }
    
        public Box<T> build() {
            return new Box<T>(contents, data);
        }
    
        public void setContents(T contents) {
            this.contents = contents;
        }
    
        public void setData(Object data) {
            this.data = data;
        }
    }
    

    And here's the client code:

    Box<String> box = Box.builder() // Returns BoxBuilder
            .withBoxContent("FOO") // Returns TypedBoxBuilder<String>
            .withAnything(42) // Returns TypedBoxBuilder<String>
            .build(); // Returns Box<String>
    String c = box.getContents();
    

    And this works as well:

    Box<String> box = Box.builder() // Returns BoxBuilder
            .withAnything(42) // Returns BoxBuilder
            .withBoxContent("FOO") // Returns TypedBoxBuilder<String>
            .build(); // Returns Box<String>
    String c = box.getContents();