Named Parameter idiom in Java

Viewed 74867

How to implement Named Parameter idiom in Java? (especially for constructors)

I am looking for an Objective-C like syntax and not like the one used in JavaBeans.

A small code example would be fine.

20 Answers

The best Java idiom I've seem for simulating keyword arguments in constructors is the Builder pattern, described in Effective Java 2nd Edition.

The basic idea is to have a Builder class that has setters (but usually not getters) for the different constructor parameters. There's also a build() method. The Builder class is often a (static) nested class of the class that it's used to build. The outer class's constructor is often private.

The end result looks something like:

public class Foo {
  public static class Builder {
    public Foo build() {
      return new Foo(this);
    }

    public Builder setSize(int size) {
      this.size = size;
      return this;
    }

    public Builder setColor(Color color) {
      this.color = color;
      return this;
    }

    public Builder setName(String name) {
      this.name = name;
      return this;
    }

    // you can set defaults for these here
    private int size;
    private Color color;
    private String name;
  }

  public static Builder builder() {
      return new Builder();
  }

  private Foo(Builder builder) {
    size = builder.size;
    color = builder.color;
    name = builder.name;
  }

  private final int size;
  private final Color color;
  private final String name;

  // The rest of Foo goes here...
}

To create an instance of Foo you then write something like:

Foo foo = Foo.builder()
    .setColor(red)
    .setName("Fred")
    .setSize(42)
    .build();

The main caveats are:

  1. Setting up the pattern is pretty verbose (as you can see). Probably not worth it except for classes you plan on instantiating in many places.
  2. There's no compile-time checking that all of the parameters have been specified exactly once. You can add runtime checks, or you can use this only for optional parameters and make required parameters normal parameters to either Foo or the Builder's constructor. (People generally don't worry about the case where the same parameter is being set multiple times.)

You may also want to check out this blog post (not by me).

This is worth of mentioning:

Foo foo = new Foo() {{
    color = red;
    name = "Fred";
    size = 42;
}};

the so called double-brace initializer. It is actually an anonymous class with instance initializer.

You could also try to follow advice from here.

int value;
int location;
boolean overwrite;
doIt(value=13, location=47, overwrite=true);

It's verbose on the call site, but overall gives the lowest overhead.

Java does not support Objective-C-like named parameters for constructors or method arguments. Furthermore, this is really not the Java way of doing things. In java, the typical pattern is verbosely named classes and members. Classes and variables should be nouns and method named should be verbs. I suppose you could get creative and deviate from the Java naming conventions and emulate the Objective-C paradigm in a hacky way but this wouldn't be particularly appreciated by the average Java developer charged with maintaining your code. When working in any language, it behooves you to stick to the conventions of the language and community, especially when working on a team.

I feel like the "comment-workaround" deserves it's own answer (hidden in existing answers and mentioned in comments here).

someMethod(/* width */ 1024, /* height */ 768);

You could use a usual constructor and static methods that give the arguments a name:

public class Something {

    String name;
    int size; 
    float weight;

    public Something(String name, int size, float weight) {
        this.name = name;
        this.size = size;
        this.weight = weight;
    }

    public static String name(String name) { 
        return name; 
    }

    public static int size(int size) {
        return size;
    }

    public float weight(float weight) {
        return weight;
    }

}

Usage:

import static Something.*;

Something s = new Something(name("pen"), size(20), weight(8.2));

Limitations compared to real named parameters:

  • argument order is relevant
  • variable argument lists are not possible with a single constructor
  • you need a method for every argument
  • not really better than a comment (new Something(/*name*/ "pen", /*size*/ 20, /*weight*/ 8.2))

If you have the choice look at Scala 2.8. http://www.scala-lang.org/node/2075

You can use project Lombok's @Builder annotation to simulate named parameters in Java. This will generate a builder for you which you can use to create new instances of any class (both classes you've written and those coming from external libraries).

This is how to enable it on a class:

@Getter
@Builder
public class User {
    private final Long id;
    private final String name;
}

Afterwards you can use this by:

User userInstance = User.builder()
    .id(1L)
    .name("joe")
    .build();

If you'd like to create such a Builder for a class coming from a library, create an annotated static method like this:

class UserBuilder {
    @Builder(builderMethodName = "builder")
    public static LibraryUser newLibraryUser(Long id, String name) {
        return new LibraryUser(id, name);
    }
  }

This will generate a method named "builder" which can be called by:

LibraryUser user = UserBuilder.builder()
    .id(1L)
    .name("joe")
    .build();

Here is a compiler-checked Builder pattern. Caveats:

  • this can't prevent double assignment of an argument
  • you can't have a nice .build() method
  • one generic parameter per field

So you need something outside the class that will fail if not passed Builder<Yes, Yes, Yes>. See the getSum static method as an example.

class No {}
class Yes {}

class Builder<K1, K2, K3> {
  int arg1, arg2, arg3;

  Builder() {}

  static Builder<No, No, No> make() {
    return new Builder<No, No, No>();
  }

  @SuppressWarnings("unchecked")
  Builder<Yes, K2, K3> arg1(int val) {
    arg1 = val;
    return (Builder<Yes, K2, K3>) this;
  }

  @SuppressWarnings("unchecked")
  Builder<K1, Yes, K3> arg2(int val) {
    arg2 = val;
    return (Builder<K1, Yes, K3>) this;
  }

  @SuppressWarnings("unchecked")
  Builder<K1, K2, Yes> arg3(int val) {
    this.arg3 = val;
    return (Builder<K1, K2, Yes>) this;
  }

  static int getSum(Builder<Yes, Yes, Yes> build) {
    return build.arg1 + build.arg2 + build.arg3;
  }

  public static void main(String[] args) {
    // Compiles!
    int v1 = getSum(make().arg1(44).arg3(22).arg2(11));
    // Builder.java:40: error: incompatible types:
    // Builder<Yes,No,Yes> cannot be converted to Builder<Yes,Yes,Yes>
    int v2 = getSum(make().arg1(44).arg3(22));
    System.out.println("Got: " + v1 + " and " + v2);
  }
}

Caveats explained. Why no build method? The trouble is that it's going to be in the Builder class, and it will be parameterized with K1, K2, K3, etc. As the method itself has to compile, everything it calls must compile. So, generally, we can't put a compilation test in a method of the class itself.

For a similar reason, we can't prevent double assignment using a builder model.

You can imitate named parameters applying this pattern:

public static class CarParameters {
        
    // to make it shorter getters and props are omitted
        
    public ModelParameter setName(String name) {
        this.name = name;
        return new ModelParameter();
    }
    public class ModelParameter {
        public PriceParameter setModel(String model) {
            CarParameters.this.model = model;
            return new PriceParameter();
        }
    }
    public class PriceParameter {
        public YearParameter setPrice(double price) {
            CarParameters.this.price = price;
            return new YearParameter();
        }
    }
    public class YearParameter {
        public ColorParameter setYear(int year) {
            CarParameters.this.year = year;
            return new ColorParameter();
        }
    }
    public class ColorParameter {
        public CarParameters setColor(Color color) {
            CarParameters.this.color = color;
            return new CarParameters();
        }
    }
}

and then you can pass it to your method as this:

factory.create(new CarParameters()
        .setName("Ford")
        .setModel("Focus")
        .setPrice(20000)
        .setYear(2011)
        .setColor(BLUE));

You can read more here https://medium.com/@ivorobioff/named-parameters-in-java-9072862cfc8c

Now that we're all on Java 17 ;-), using records is a super-easy way to imitate this idiom:

public class OrderTemplate() {
    private int tradeSize, limitDistance, backoffDistance;

    public record TradeSize( int value ) {}
    public record LimitDistance( int value ) {}
    public record BackoffDistance( int value ) {}
    public OrderTemplate( TradeSize t, LimitDistance d, BackoffDistance b ) {
      this.tradeSize = t.value();
      this.limitDistance = d.value();
      this.backoffDistance = b.value();
    }
}

Then you can call:

var t = new OrderTemplate( new TradeSize(30), new LimitDistance(182), new BackoffDistance(85) );

Which I've found extremely easy to read and I've completely stopped getting all the int parameters mixed up ("was it size first or distance...").

package org.xxx.lang;

/**
 * A hack to work around the fact that java does not support
 * named parameters in function calls.
 * 
 * Its easy to swap a few String parameters, for example.
 * Some IDEs are better than others than showing the parameter names.
 * This will enforce a compiler error on an inadvertent swap. 
 *
 * @param <T>
 */
public class Datum<T> {

    public final T v;
    
    public Datum(T v) {
        this.v = v;
    }
    
    public T v() {
        return v;
    }
    
    public T value() {
        return v;
    }
    
    public String toString() {
        return v.toString();
    }
}

Example

class Catalog extends Datum<String> {
        public Catalog(String v) {
            super(v);
        }
    }

    class Schema extends Datum<String> {
        public Schema(String v) {
            super(v);
        }
    }
class Meta {
        public void getTables(String catalog, String schema, String tablePattern) {
            // pseudo DatabaseMetaData.getTables();
        }
    }
    
    class MetaChecked {
        public void getTables(Catalog catalog, Schema schema, String tablePattern) {
            // pseudo DatabaseMetaData.getTables();
        }
    }

    @Test
    public void test() {
        Catalog c = new Catalog("test");
        assertEquals("test",c.v);
        assertEquals("test",c.v());
        assertEquals("test",c.value());
        String t = c.v;
        assertEquals("test",t);
    }

    public void uncheckedExample() {
        new Meta().getTables("schema","catalog","%"); 
        new Meta().getTables("catalog","schema","%"); // ooops
    }
    
    public void checkedExample() {
         // new MetaChecked().getTables(new Schema("schema"),new Catalog("catalog"),"%"); // won't compile
          new MetaChecked().getTables(new Catalog("catalog"), new Schema("schema"),"%"); 
    }
Related