Search code examples
javaoopdesign-patterns

Java constructor of "immutable class" with many fields with default values?


I have a class with lots of fields. They should basically be set at the constructor phase and never change. Semantically the class then is an immutable one.

public class A{
    final int a;
    final short b;
    final double e;
    final String f;
    final String g;
    //and more
}

The problem is that normally these fields have default values and therefore I do not want to always burden the user with a constructor with all of them. Most time, they just need to set a couple of them. There are a couple of ways to solve this:

  1. I would need lots of constructor with different signature.
  2. Create a bunch of set method of these field and only set those non-default value. But this somehow indicate a different semantics other than immutable nature.
  3. Create a new parameter class that is mutable and use that class as constructor.

None of that is totally satisfactory. Is there any other approach? Thanks. One way


Solution

  • I would use a combination of a parameter class and a fluent builder API for creating the parameter:

    public class A {
        private final int a;
        private final short b;
        private final double e;
        private final String g;
    
        public static class Aparam {
            private int a = 1;
            private short b = 2;
            private double e = 3.141593;
            private String g = "NONE";
    
            public Aparam a(int a) {
                this.a = a;
                return this;
            }
    
            public Aparam b(short b) {
                this.b = b;
                return this;
            }
    
            public Aparam e(double e) {
                this.e = e;
                return this;
            }
    
            public Aparam g(String g) {
                this.g = g;
                return this;
            }
    
            public A build() {
                return new A(this);
            }
        }
    
        public static Aparam a(int a) {
            return new Aparam().a(a);
        }
    
        public static Aparam b(short b) {
            return new Aparam().b(b);
        }
    
        public static Aparam e(double e) {
            return new Aparam().e(e);
        }
    
        public static Aparam g(String g) {
            return new Aparam().g(g);
        }
    
        public static A build() {
            return new Aparam().build();
        }
    
        private A(Aparam p) {
            this.a = p.a;
            this.b = p.b;
            this.e = p.e;
            this.g = p.g;
        }
    
        @Override public String toString() {
            return "{a=" + a + ",b=" + b + ",e=" + e + ",g=" + g + "}";
        }
    }
    

    Then create instances of A like this:

    A a1 = A.build();
    A a2 = A.a(7).e(17.5).build();
    A a3 = A.b((short)42).e(2.218282).g("fluent").build();
    

    Class A is immutable, the parameters are optional, and the interface is fluent.