Nazwany idiom parametru w Javie

Jak zaimplementować idiom nazwanych parametrów w Javie? (szczególnie dla konstruktorów)

Szukam składni podobnej do Objective-C, a nie do tej używanej w JavaBeans.

Mały przykład kodu byłby w porządku.

Dzięki.
 59
Author: Red Hyena, 2010-01-01

17 answers

Najlepszym idiomem Javy, jakim wydaje mi się symulowanie argumentów słów kluczowych w konstruktorach, jest wzorzec Builder, opisany w Effective Java 2nd Edition.

Podstawową ideą jest posiadanie klasy konstruktora, która ma settery (ale zazwyczaj nie gettery) dla różnych parametrów konstruktora. Istnieje również metoda build(). Klasa Builder jest często (statyczną) zagnieżdżoną klasą klasy, której używa do budowania. Konstruktor klasy zewnętrznej jest często prywatny.

Efekt końcowy wygląda coś w stylu:

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

Aby utworzyć instancję Foo należy napisać coś w stylu:

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

Główne zastrzeżenia to:

  1. ustawienie wzorca jest dość gadatliwe(jak widać). Prawdopodobnie nie warto z wyjątkiem zajęć, które planujesz utworzyć w wielu miejscach.
  2. nie ma sprawdzania w czasie kompilacji, czy wszystkie parametry zostały podane dokładnie raz. Można dodać kontrole runtime, lub można użyć tego tylko dla opcjonalnych parametrów i zrobić wymagane parametry normalne parametry dla Foo lub konstruktora. (Ludzie na ogół nie martwią się o przypadek, w którym ten sam parametr jest ustawiany wiele razy.)

Możesz również sprawdzić ten wpis na blogu (nie przeze mnie).

 79
Author: Laurence Gonsalves,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-01-01 06:54:31

Warto o tym wspomnieć:

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

Tzw. inicjalizator dwuskładnikowy . Jest to w rzeczywistości anonimowa klasa z inicjalizatorem instancji.

 57
Author: irreputable,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-03-25 19:59:17

Możesz również spróbować postępować zgodnie z zaleceniami tutaj: http://www.artima.com/weblogs/viewpost.jsp?thread=118828

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

Jest gadatliwy na stronie połączeń, ale ogólnie daje najniższe koszty.

 15
Author: rkj,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-03-05 17:47:41

Java 8 style:

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

    }
}
  • nazwane parametry
  • ustalanie kolejności argumentów
  • Kontrola statyczna -> bezimienna osoba możliwa
  • trudno przełączyć argumenty tego samego typu przez przypadek (jak to jest możliwe w konstruktorach teleskopowych)
 14
Author: Alex,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-07-23 15:07:01

Jeśli używasz Java 6, możesz użyć zmiennych parametry i zaimportować statyczne, aby uzyskać znacznie lepszy wynik. Szczegóły znajdują się w:

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

W skrócie, można mieć coś takiego:

go();
go(min(0));
go(min(0), max(100));
go(max(100), min(0));
go(prompt("Enter a value"), min(0), max(100));
 8
Author: R Casha,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-12-08 08:47:15

Oto mała odmiana techniki podanej w efektywnej Javie Joshuy Blocha. Tutaj podjąłem próbę uczynienia kodu klienta bardziej czytelnym (a może bardziej 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();
    }
}

Zwróć uwagę, że dzięki tej odmianie możesz również nadać znaczące nazwy swoim pseudo-konstruktorom.

 7
Author: missingfaktor,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-01-01 21:31:14

Java nie obsługuje nazwanych parametrów podobnych do Objective-C dla konstruktorów lub argumentów metod. Co więcej, nie jest to sposób działania w Javie. W Javie typowym wzorcem są słownie nazwane klasy i członkowie. Klasy i zmienne powinny być rzeczownikami, a metody nazwane powinny być czasownikami. Przypuszczam, że mógłbyś być kreatywny i odbiegać od konwencji nazewnictwa Javy i naśladować paradygmat Objective-C w hakerski sposób, ale nie byłoby to szczególnie doceniane przez przeciętnego Programista Java odpowiedzialny za utrzymanie kodu. Pracując w dowolnym języku, warto trzymać się konwencji języka i społeczności, szczególnie podczas pracy w zespole.

 6
Author: Asaph,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-01-01 06:36:19

A co z

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");
 6
Author: user564819,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-03-24 02:48:27

Chciałbym zwrócić uwagę, że ten styl odnosi się zarówno do nazwanego parametru , jak i właściwości bez prefiksu get i set, które mają prefiks w innym języku. To nie jest konwencjonalne w Javie, ale prostsze, nietrudne do zrozumienia, szczególnie jeśli zajmowałeś się innymi językami.

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

}
 3
Author: LEM Adane,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-11-23 02:55:39

Możesz użyć zwykłego konstruktora i statycznych metod, które nadają argumentom nazwę:

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

}

Użycie:

import static Something.*;

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

Ograniczenia w porównaniu do rzeczywistych nazwanych parametrów:

  • kolejność argumentów jest istotna
  • listy argumentów zmiennych nie są możliwe przy użyciu jednego konstruktora
  • potrzebujesz metody dla każdego argumentu
  • nie do końca lepsze niż komentarz (coś nowego(/*name*/ "pen", /*size*/ 20, /*weight*/ 8.2))

Jeśli masz wybór spójrz na Scala 2.8. http://www.scala-lang.org/node/2075

 2
Author: deamon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-01-15 14:41:39

Idiom wspierany przez karg library może być warty rozważenia:

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");
    }
}
 1
Author: Dominic Fox,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-02-13 17:18:22

Używając lambdy Javy 8 można zbliżyć się jeszcze bliżej dorzeczywistych nazwanych parametrów.

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

Należy pamiętać, że prawdopodobnie narusza to kilkadziesiąt "najlepszych praktyk Javy" (jak wszystko, co wykorzystuje symbol $).

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

PLUSY:

  • znacznie krótszy niż jakikolwiek wzorzec budowniczy, jaki widziałem do tej pory
  • Działa zarówno dla metod, jak i konstruktorów
  • całkowicie bezpieczny Typ
  • wygląda bardzo blisko rzeczywistych nazwanych parametrów w innych języki programowania
  • Jest tak samo bezpieczny jak typowy wzorzec konstruktora (może ustawiać parametry wiele razy)]}

Wady:

  • twój szef prawdopodobnie zlinczuje cię za to
  • It 's harder to tell what' s going on
 1
Author: Vic,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-08-14 16:50:18

Każde rozwiązanie w Javie prawdopodobnie będzie dość gadatliwe, ale warto wspomnieć, że narzędzia takie jak Google AutoValuesi Immutables będą generować klasy konstruktorów dla ciebie automatycznie przy użyciu przetwarzania adnotacji w czasie kompilacji JDK.

W moim przypadku chciałem użyć nazwanych parametrów w Java enum, aby wzorzec konstruktora nie działał, ponieważ instancje enum nie mogą być tworzone przez inne klasy. Wymyśliłem podejście podobne do odpowiedzi @deamon ale dodaje compile-time sprawdzanie kolejności parametrów (kosztem większej ilości kodu)

Oto kod klienta:

Person p = new Person( age(16), weight(100), heightInches(65) );

I realizacja:

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; }
}
 1
Author: scott,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-12-21 01:49:38

Jest to wariant wzoru Builder opisanego powyżej przez Lawrence ' a.

Używam tego często (w odpowiednich miejscach).

Główna różnica polega na tym, że w tym przypadku Konstruktor jest immuatable . Ma to tę zaletę, że może być ponownie użyty i jest bezpieczny dla wątków.

Możesz więc użyć tego do stworzenia jednego domyślnego konstruktora , a następnie w różnych miejscach, gdzie go potrzebujesz, możesz go skonfigurować i zbudować swój obiekt.

To sprawia, że najbardziej sensowne, jeśli budujesz ten sam obiekt w kółko, ponieważ wtedy możesz sprawić, że budowniczy stanie się statyczny i nie musisz się martwić o zmianę jego ustawień.

Z drugiej strony, jeśli musisz budować obiekty ze zmieniającymi się parametrami, to ma cichy narzut. (ale hej, możesz połączyć generowanie statyczne / dynamiczne z niestandardowymi metodami build)

Oto przykładowy kod:

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

    }
}
 0
Author: Scheintod,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-09-16 18:33:23

@irreputable wpadł na dobre rozwiązanie. Jednakże-może to pozostawić instancję klasy w nieprawidłowym stanie, ponieważ nie dojdzie do walidacji i sprawdzania spójności. Dlatego wolę połączyć to z rozwiązaniem budowniczym, unikając dodatkowej podklasy do utworzenia, chociaż nadal podklasowałaby klasę builder. Dodatkowo, ponieważ klasa EXTRA builder sprawia, że jest bardziej gadatliwa, dodałem jeszcze jedną metodę wykorzystującą lambda. Dodałem kilka innych podejść budowniczych dla kompletność.

Zaczynając od klasy w następujący sposób:

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

}

Następnie używając tego, stosując różne metody:

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

Wygląda na to, że po części jest to całkowite zdzierstwo z tego, co @LaurenceGonsalves już opublikował, ale zobaczysz małą różnicę w wybranej konwencji.

Zastanawiam się, czy JLS kiedykolwiek zaimplementowałby nazwane parametry, jak by to zrobili? Czy rozszerzyłyby się na jeden z istniejących idiomów, zapewniając dla niego wsparcie w formie skróconej? Również jak Scala obsługa nazwanych parametrów?

Hmmm-wystarczy do badań, a może nowe pytanie.
 0
Author: YoYo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-19 02:52:58

Możesz użyć adnotacji project Lombok @Builder do symulacji nazwanych parametrów w Javie. Spowoduje to wygenerowanie konstruktora, którego możesz użyć do tworzenia nowych instancji dowolnej klasy (zarówno klas, które napisałeś, jak i tych pochodzących z zewnętrznych bibliotek).

Oto jak włączyć to na klasie:

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

Następnie możesz użyć tego przez:

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

Jeśli chcesz utworzyć taki Konstruktor dla klasy pochodzącej z biblioteki, Utwórz statyczną metodę z adnotacją, taką jak to:

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

Wygeneruje to metodę o nazwie "builder", którą można wywołać przez:

LibraryUser user = UserBuilder.builder()
    .id(1L)
    .name("joe")
    .build();
 0
Author: Istvan Devai,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-08-01 16:04:33

Uważam, że "obejście problemu" zasługuje na własną odpowiedź (ukrytą w istniejących odpowiedziach i wspomnianą w komentarzach tutaj).

someMethod(/* width */ 1024, /* height */ 768);
 0
Author: Reto Höhener,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-08-21 09:38:35