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


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

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

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

спасибо.

17 59

17 ответов:

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

основная идея состоит в том, чтобы иметь класс 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-like для конструкторов или аргументов метода. Кроме того, это действительно не способ Java делать вещи. В java типичный шаблон имеет многословные имена классов и членов. Классы и переменные должны быть существительными, а именованные методы-глаголами. Я полагаю, что вы могли бы проявить творческий подход и отклониться от соглашений об именах Java и эмулировать парадигму Objective-C хакерским способом, но это не было бы особенно оценено средним Разработчик Java отвечает за поддержание вашего кода. При работе на любом языке, это обязывает вас придерживаться конвенций языка и сообщества, особенно при работе в команде.

а как же

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 best practices" (как и все, что использует $ символ).

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

плюсы:

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

плюсы:

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

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

в моем случае я хотел использовать именованные параметры в перечислении Java, поэтому шаблон builder не будет работать, потому что экземпляры enum не могут быть созданы другими классами. Я придумал подход, похожий на @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. Это имеет то преимущество, что это может быть повторно и является потокобезопасным.

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

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

С другой стороны, если вам нужно построить объекты с изменением параметров, это имеет тихие некоторые накладные расходы. (но эй, вы можете комбинировать статическое / динамическое поколение с пользовательским 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 делает его более подробным, я добавил еще один метод с использованием лямбды. Я добавил некоторые из других подходов строителя для полнота.

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

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();

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

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