Идиома именованного параметра в Java

Как реализовать идиому именованного параметра в Java? (особенно для строителей)

Я ищу синтаксис Objective-C, а не тот, который используется в JavaBeans.

небольшой пример кода был бы в порядке.

спасибо.

17 ответов


лучшая идиома Java, которую я вижу для моделирования аргументов ключевых слов в конструкторах, - это шаблон Builder, описанный в эффективное Java 2nd Edition.

основная идея состоит в том, чтобы иметь класс Builder, который имеет сеттеры (но обычно не геттеры) для различных параметров конструктора. Есть также build() метод. Класс Builder часто является (статическим) вложенным классом класса, который он используется для построения. Конструктор внешнего класса часто частный.

конечный результат выглядит примерно так:

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...
}

чтобы создать экземпляр Foo вы затем напишите что-то вроде:

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

основные предостережения:

  1. настройка шаблона довольно многословна (как вы можете видеть). Вероятно, не стоит, за исключением классов, которые вы планируете создавать во многих местах.
  2. нет времени компиляции, проверяя, что все параметры были указаны ровно один раз. Вы можете добавить проверки во время выполнения, или вы можете использовать это только для необязательных параметров и сделать необходимые параметры нормальными параметрами для Foo или конструктора построителя. (Люди обычно не беспокоятся о случае, когда один и тот же параметр устанавливается несколько раз.)

вы также можете проверить этот блог (не мной).


Это стоит отметить:

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

так называемый двойной-скобки инициализатор. На самом деле это анонимный класс с инициализатором экземпляра.


вы также можете попробовать следовать советам отсюда: http://www.artima.com/weblogs/viewpost.jsp?thread=118828

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

это многословно на сайте вызова, но в целом дает самые низкие накладные расходы.


стиль Java 8:

public class Person {
    String name;
    int age;

    private Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    static PersonWaitingForName create() {
        return name -> age -> new Person(name, age);
    }

    static interface PersonWaitingForName {
        PersonWaitingForAge name(String name);
    }

    static interface PersonWaitingForAge {
        Person age(int age);
    }

    public static void main(String[] args) {

        Person charlotte = Person.create()
            .name("Charlotte")
            .age(25);

    }
}
  • именованные параметры
  • исправить порядок аргументов
  • статическая проверка -> нет безымянного человека можно
  • трудно переключить аргументы одного и того же типа случайно (как это возможно в конструкторах телескопа)

Если вы используете Java 6, Вы можете использовать переменные параметры и статичных гораздо лучше результат. Подробности этого можно найти в:

http://zinzel.blogspot.com/2010/07/creating-methods-with-named-parameters.html

короче говоря, у вас может быть что-то вроде:

go();
go(min(0));
go(min(0), max(100));
go(max(100), min(0));
go(prompt("Enter a value"), min(0), max(100));

вот небольшая вариация метода, приведенного в эффективной Java Джошуа Блоха. Здесь я попытался сделать клиентский код более читаемым (или, возможно, более DSLish).

/**
 * Actual class for which we want to implement a 
 * named-parameter pseudo-constructor
 */
class Window{
    protected int x, y, width, height;
    protected boolean isResizable;
    protected String title;

    public void show(){
        // Show the window
        System.out.printf("Window \"%s\" set visible.%n",title);
    }

    /**
     * This class is only used to set the parameter values
     */
    static class HavingProperties extends Window{

        public HavingProperties x(int value){
            this.x=value;
            return this;
        }

        public HavingProperties y(int value){
            this.y=value;
            return this;
        }

        public HavingProperties width(int value){
            this.width=value;
            return this;
        }

        public HavingProperties height(int value){
            this.height=value;
            return this;
        }

        public HavingProperties resizable(boolean value){
            this.isResizable=value;
            return this;
        }

        public HavingProperties title(String value){
            this.title=value;
            return this;
        }
    }
}

public class NamedParameterIdiomInAction {
    public static void main(String... args){
        Window window=new Window.HavingProperties().x(10).y(10).width(100).
                height(100).resizable(true).title("My App");
        window.show();
    }
}

обратите внимание, что с помощью этого варианта вы также можете дать значимые имена своим псевдо-конструкторам.


Java не поддерживает Objective-C-подобные именованные параметры для конструкторов или аргументов метода. Кроме того, это действительно не способ Java делать вещи. В java типичный шаблон имеет многословные имена классов и членов. Классы и переменные должны быть существительными, а метод с именем-глаголами. Я предполагаю, что вы могли бы получить творческий подход и отклониться от соглашений об именах Java и эмулировать парадигму Objective-C хакерским способом, но это не было бы особенно оценено средним Java developer отвечает за поддержание вашего кода. Когда вы работаете на любом языке, вам следует придерживаться конвенций языка и сообщества, особенно при работе в команде.


насчет

public class Tiger {
String myColor;
int    myLegs;

public Tiger color(String s)
{
    myColor = s;
    return this;
}

public Tiger legs(int i)
{
    myLegs = i;
    return this;
}
}

Tiger t = new Tiger().legs(4).color("striped");

Я хотел бы отметить, что этот стиль рассматривает как именованный параметр и свойства без get и set префикс, который имеет другой язык. Его не обычный в Java realm, но его проще, не трудно понять, особенно если вы обрабатывали другие языки.

public class Person {
   String name;
   int age;

   // name property
   // getter
   public String name() { return name; }

   // setter
   public Person name(String val)  { 
    name = val;
    return this;
   }

   // age property
   // getter
   public int age() { return age; }

   // setter
   public Person age(int val) {
     age = val;
     return this;
   }

   public static void main(String[] args) {

      // Addresses named parameter

      Person jacobi = new Person().name("Jacobi").age(3);

      // Addresses property style

      System.out.println(jacobi.name());
      System.out.println(jacobi.age());

      //...

      jacobi.name("Lemuel Jacobi");
      jacobi.age(4);

      System.out.println(jacobi.name());
      System.out.println(jacobi.age());
   }

}

вы можете использовать обычный конструктор и статические методы, которые дают аргументам имя:

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;
    }

}

использование:

import static Something.*;

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

ограничения по сравнению с реальными именованными параметрами:

  • порядок аргументов имеет отношение
  • списки переменных аргументов невозможны с помощью одного конструктора
  • вам нужен метод для каждого аргумента
  • не очень лучше, чем комментарий (Новый Что-то(/*name*/ "pen", /*size*/ 20, /*weight*/ 8.2))

Если у вас есть выбор, посмотрите на Scala 2.8. http://www.scala-lang.org/node/2075


идиома, поддерживаемая Карг библиотека может быть стоит подумать:

class Example {

    private static final Keyword<String> GREETING = Keyword.newKeyword();
    private static final Keyword<String> NAME = Keyword.newKeyword();

    public void greet(KeywordArgument...argArray) {
        KeywordArguments args = KeywordArguments.of(argArray);
        String greeting = GREETING.from(args, "Hello");
        String name = NAME.from(args, "World");
        System.out.println(String.format("%s, %s!", greeting, name));
    }

    public void sayHello() {
        greet();
    }

    public void sayGoodbye() {
        greet(GREETING.of("Goodbye");
    }

    public void campItUp() {
        greet(NAME.of("Sailor");
    }
}

используя лямбды Java 8, вы можете приблизиться к реальные именованные параметры.

foo($ -> {$.foo = -10; $.bar = "hello"; $.array = new int[]{1, 2, 3, 4};});

обратите внимание, что это, вероятно, нарушает пару десятков " лучших практик java "(как и все, что использует $ символ).

public class Main {
  public static void main(String[] args) {
    // Usage
    foo($ -> {$.foo = -10; $.bar = "hello"; $.array = new int[]{1, 2, 3, 4};});
    // Compare to roughly "equivalent" python call
    // foo(foo = -10, bar = "hello", array = [1, 2, 3, 4])
  }

  // Your parameter holder
  public static class $foo {
    private $foo() {}

    public int foo = 2;
    public String bar = "test";
    public int[] array = new int[]{};
  }

  // Some boilerplate logic
  public static void foo(Consumer<$foo> c) {
    $foo foo = new $foo();
    c.accept(foo);
    foo_impl(foo);
  }

  // Method with named parameters
  private static void foo_impl($foo par) {
    // Do something with your parameters
    System.out.println("foo: " + par.foo + ", bar: " + par.bar + ", array: " + Arrays.toString(par.array));
  }
}

плюсы:

  • значительно короче, чем любой шаблон строителя, который я видел до сих пор
  • работает как для методов, так и для конструкторов
  • полностью тип safe
  • это выглядит очень близко к фактическим именованным параметрам на других языках программирования
  • это примерно так же безопасно ,как ваш типичный шаблон строителя (может устанавливать параметры несколько раз)

плюсы:

  • ваш босс, вероятно, линчевать вас за это
  • труднее сказать, что происходит

любое решение на Java, вероятно, будет довольно многословным, но стоит упомянуть, что такие инструменты, как Google AutoValues и Immutables будет генерировать классы builder для вас автоматически, используя обработку аннотаций времени компиляции JDK.

для моего случая я хотел, чтобы именованные параметры использовались в перечислении Java, поэтому шаблон построителя не работал, потому что экземпляры перечисления не могут быть созданы другими классами. Я придумал подход, похожий на @deamon's ответ, но добавляет проверку времени компиляции порядка параметров (за счет большего количества кода)

вот:
Person p = new Person( age(16), weight(100), heightInches(65) );

и реализации:

class Person {
  static class TypedContainer<T> {
    T val;
    TypedContainer(T val) { this.val = val; }
  }
  static Age age(int age) { return new Age(age); }
  static class Age extends TypedContainer<Integer> {
    Age(Integer age) { super(age); }
  }
  static Weight weight(int weight) { return new Weight(weight); }
  static class Weight extends TypedContainer<Integer> {
    Weight(Integer weight) { super(weight); }
  }
  static Height heightInches(int height) { return new Height(height); }
  static class Height extends TypedContainer<Integer> {
    Height(Integer height) { super(height); }
  }

  private final int age;
  private final int weight;
  private final int height;

  Person(Age age, Weight weight, Height height) {
    this.age = age.val;
    this.weight = weight.val;
    this.height = height.val;
  }
  public int getAge() { return age; }
  public int getWeight() { return weight; }
  public int getHeight() { return height; }
}

Это вариант Builder шаблон, как описано Лоуренсом выше.

Я нахожу, что использую это много (в подходящих местах).

основная разница в том, что в этом случае застройщик immuatable. Это имеет то преимущество, что это может быть повторно и потокобезопасен.

Так что вы можете использовать это, чтобы сделать один конструктор по умолчанию и после этого в различных местах где вам нужно оно вы можете установить его и создайте свой объект.

Это имеет смысл, если вы строите один и тот же объект снова и снова, потому что тогда вы можете сделать строитель статическим и не беспокоиться об изменении его настроек.

С другой стороны, если вам нужно строить объекты с изменением параметров, это имеет тихие некоторые накладные расходы. (но эй, вы можете объединить статическое / динамическое поколение с пользовательским build методов)

вот пример кода:

public class Car {

    public enum Color { white, red, green, blue, black };

    private final String brand;
    private final String name;
    private final Color color;
    private final int speed;

    private Car( CarBuilder builder ){
        this.brand = builder.brand;
        this.color = builder.color;
        this.speed = builder.speed;
        this.name = builder.name;
    }

    public static CarBuilder with() {
        return DEFAULT;
    }

    private static final CarBuilder DEFAULT = new CarBuilder(
            null, null, Color.white, 130
    );

    public static class CarBuilder {

        final String brand;
        final String name;
        final Color color;
        final int speed;

        private CarBuilder( String brand, String name, Color color, int speed ) {
            this.brand = brand;
            this.name = name;
            this.color = color;
            this.speed = speed;
        }
        public CarBuilder brand( String newBrand ) {
            return new CarBuilder( newBrand, name, color, speed );
        }
        public CarBuilder name( String newName ) {
            return new CarBuilder( brand, newName, color, speed );
        }
        public CarBuilder color( Color newColor ) {
            return new CarBuilder( brand, name, newColor, speed );
        }
        public CarBuilder speed( int newSpeed ) {
            return new CarBuilder( brand, name, color, newSpeed );
        }
        public Car build() {
            return new Car( this );
        }
    }

    public static void main( String [] args ) {

        Car porsche = Car.with()
                .brand( "Porsche" )
                .name( "Carrera" )
                .color( Color.red )
                .speed( 270 )
                .build()
                ;

        // -- or with one default builder

        CarBuilder ASSEMBLY_LINE = Car.with()
                .brand( "Jeep" )
                .name( "Cherokee" )
                .color( Color.green )
                .speed( 180 )
                ;

        for( ;; ) ASSEMBLY_LINE.build();

        // -- or with custom default builder:

        CarBuilder MERCEDES = Car.with()
                .brand( "Mercedes" )
                .color( Color.black )
                ;

        Car c230 = MERCEDES.name( "C230" ).speed( 180 ).build(),
            clk = MERCEDES.name( "CLK" ).speed( 240 ).build();

    }
}

@irreputable придумал хорошее решение. Однако-это может оставить экземпляр класса в недопустимом состоянии, так как проверка и проверка согласованности не произойдет. Поэтому я предпочитаю комбинировать это с решением Builder, избегая создания дополнительного подкласса, хотя он все равно будет подклассом класса builder. Кроме того, поскольку дополнительный класс builder делает его более подробным, я добавил еще один метод с использованием лямбды. Я добавил некоторые из других подходов builder для полнота.

начиная с класса следующим образом:

public class Foo {
  static public class Builder {
    public int size;
    public Color color;
    public String name;
    public Builder() { size = 0; color = Color.RED; name = null; }
    private Builder self() { return this; }

    public Builder size(int size) {this.size = size; return self();}
    public Builder color(Color color) {this.color = color; return self();}
    public Builder name(String name) {this.name = name; return self();}

    public Foo build() {return new Foo(this);}
  }

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

  public Foo(Builder b) {
    this.size = b.size;
    this.color = b.color;
    this.name = b.name;
  }

  public Foo(java.util.function.Consumer<Builder> bc) {
    Builder b = new Builder();
    bc.accept(b);
    this.size = b.size;
    this.color = b.color;
    this.name = b.name;
  }

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

  public int getSize() { return this.size; }
  public Color getColor() { return this.color; }  
  public String getName() { return this.name; }  

}

затем, используя это, применяя различные методы:

Foo m1 = new Foo(
  new Foo.Builder ()
  .size(1)
  .color(BLUE)
  .name("Fred")
);

Foo m2 = new Foo.Builder()
  .size(1)
  .color(BLUE)
  .name("Fred")
  .build();

Foo m3 = Foo.with()
  .size(1)
  .color(BLUE)
  .name("Fred")
  .build();

Foo m4 = new Foo(
  new Foo.Builder() {{
    size = 1;
    color = BLUE;
    name = "Fred";
  }}
);

Foo m5 = new Foo(
  (b)->{
    b.size = 1;
    b.color = BLUE;
    b.name = "Fred";
  }
);

похоже, что отчасти это полный отрыв от того, что @LaurenceGonsalves уже опубликовал, но вы увидите небольшую разницу в выбранной конвенции.

интересно, если JLS когда-либо будет реализовывать именованные параметры, как они это сделают? Будут ли они распространяться на один из существующих идиом, предоставляя поддержку короткой формы за это? Также как Scala поддерживает именованные параметры?

Хммм-достаточно для исследования, и, возможно, новый вопрос.


вы можете использовать проект Ломбока @Builder аннотация для имитации именованных параметров в Java. Это создаст для вас конструктор, который вы можете использовать для создания новых экземпляров любого класса (как написанных, так и из внешних библиотек).

вот как включить его в классе:

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

затем вы можете использовать это:

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

если вы хотите создать такой конструктор для класса из библиотеки, создать аннотированный статический метод, подобный этому:

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

это создаст метод с именем "builder" , который может быть вызван:

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

Я чувствую, что" comment-workaround " заслуживает собственного ответа (скрытого в существующих ответах и упомянутого в комментариях здесь).

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