web-dev-qa-db-fra.com

Paramètre nommé idiome en Java

Comment implémenter l'idiome de paramètre nommé en Java? (surtout pour les constructeurs)

Je suis à la recherche d'une syntaxe semblable à Objective-C et non semblable à celle utilisée dans JavaBeans.

Un petit exemple de code serait bien.

Merci.

68
Red Hyena

Le meilleur langage que je connaisse pour simuler les arguments de mots clés dans les constructeurs est le modèle Builder, décrit dans Effective Java 2nd Edition .

L'idée de base est de disposer d'une classe Builder comportant des setters (mais généralement pas des getters) pour les différents paramètres du constructeur. Il y a aussi une méthode build(). La classe Builder est souvent une classe imbriquée (statique) de la classe pour laquelle elle est construite. Le constructeur de la classe externe est souvent privé.

Le résultat final ressemble à quelque chose comme:

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

Pour créer une instance de Foo, vous écrivez quelque chose comme:

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

Les principales mises en garde sont les suivantes:

  1. La configuration du motif est assez détaillée (comme vous pouvez le voir). Probablement pas la peine, sauf pour les cours que vous prévoyez d’instancier dans de nombreux endroits.
  2. Au moment de la compilation, il n'est pas vérifié si tous les paramètres ont été spécifiés exactement une fois. Vous pouvez ajouter des vérifications à l'exécution, ou uniquement pour des paramètres facultatifs et définir les paramètres normaux requis pour les paramètres Foo ou le constructeur du constructeur. (Les gens ne s’inquiètent généralement pas du cas où le même paramètre est défini plusieurs fois.)

Vous pouvez également vouloir consulter cet article de blog (pas par moi).

86
Laurence Gonsalves

Cela vaut la peine de mentionner:

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

le soi-disant initialisateur à double accolade . C'est en fait une classe anonyme avec un initialiseur d'instance. 

62
irreputable

Vous pouvez également essayer de suivre les conseils d’ici: http://www.artima.com/weblogs/viewpost.jsp?thread=118828

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

Il est détaillé sur le site d’appel, mais donne globalement les frais généraux les plus bas.

19
rkj

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

    }
}
  • paramètres nommés
  • fixer l'ordre des arguments
  • vérification statique -> personne sans nom possible
  • difficile de changer accidentellement des arguments du même type (comme dans les constructeurs de telescop)
15
Alex

Si vous utilisez Java 6, vous pouvez utiliser les paramètres de variable et importer static pour obtenir un résultat bien meilleur. On en trouve des détails dans: 

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

En bref, vous pourriez avoir quelque chose comme:

go();
go(min(0));
go(min(0), max(100));
go(max(100), min(0));
go(Prompt("Enter a value"), min(0), max(100));
8
R Casha

Voici une petite variation de la technique donnée dans Effective Java de Joshua Bloch. Ici, j'ai tenté de rendre le code client plus lisible (ou peut-être plus 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();
    }
}

Veuillez noter qu'avec cette variante, vous pouvez également donner des noms significatifs à vos pseudo-constructeurs.

7
missingfaktor

Qu'en est-il de

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
user564819

Je tiens à souligner que ce style concerne à la fois le paramètre named et les properties features sans les préfixes get et set que l 'autre langue possède. Ce n'est pas conventionnel en Java, mais c'est simple, facile à comprendre, surtout si vous avez déjà utilisé d'autres langages. 

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

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

      //...

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

      println(jacobi.name());
      println(jacobi.age());
   }
}
6
LEMUEL ADANE

Java ne prend pas en charge les paramètres nommés de type Objective-C-like pour les constructeurs ou les arguments de méthodes. De plus, ce n'est vraiment pas la manière de faire de Java. En Java, le modèle typique est constitué de classes et de membres nommés de manière verbale. Les classes et les variables doivent être des noms et la méthode nommée doit être un verbe. Je suppose que vous pouvez faire preuve de créativité et vous écarter des conventions de dénomination Java et émuler le paradigme Objective-C de manière simpliste, mais cela ne serait pas particulièrement apprécié du développeur Java moyen chargé de maintenir votre code. Lorsque vous travaillez dans n'importe quelle langue, il vous incombe de respecter les conventions de la langue et de la communauté, en particulier lorsque vous travaillez en équipe.

6
Asaph

Toute solution en Java sera probablement assez détaillée, mais il convient de noter que des outils tels que Google AutoValues ​​ et Immutables généreront automatiquement des classes de générateur à l'aide du traitement des annotations au moment de la compilation JDK.

Dans mon cas, je souhaitais que les paramètres nommés soient utilisés dans une énumération Java. Par conséquent, un modèle de générateur ne fonctionnerait pas car les instances enum ne peuvent pas être instanciées par d'autres classes. J'ai trouvé une approche similaire à celle de @ deamon mais ajoute une vérification au moment de la compilation du classement des paramètres (au détriment de davantage de code)

Voici le code client:

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

Et la mise en place:

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

Vous pouvez utiliser le projet annotation @Builder du projet Lombok pour simuler des paramètres nommés en Java. Cela générera pour vous un générateur que vous pourrez utiliser pour créer de nouvelles instances de toute classe (les classes que vous avez écrites et celles provenant de bibliothèques externes).

Voici comment l'activer sur une classe:

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

Ensuite, vous pouvez utiliser ceci en:

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

Si vous souhaitez créer un tel générateur pour une classe provenant d'une bibliothèque, créez une méthode statique annotée comme celle-ci:

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

Cela générera une méthode nommée "constructeur" qui peut être appelée par:

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

Je pense que la "solution de contournement" mérite sa propre réponse (cachée dans les réponses existantes et mentionnée dans les commentaires ici).

someMethod(/* width */ 1024, /* height */ 768);
2
Reto Höhener

En utilisant les lambdas de Java 8, vous pouvez vous rapprocher encore plus des paramètres nommés real

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

Notez que cela enfreint probablement une vingtaine de "meilleures pratiques Java" (comme tout ce qui utilise le symbole $).

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

Avantages: 

  • Considérablement plus court que n'importe quel modèle de constructeur que j'ai vu jusqu'à présent
  • Fonctionne pour les méthodes et les constructeurs
  • Complètement tapez safe
  • Il semble très proche des paramètres nommés réels dans d'autres langages de programmation
  • Il est à peu près aussi sûr que votre modèle de constructeur typique (peut définir des paramètres plusieurs fois)

Les inconvénients:

  • Votre patron vous lynchera probablement pour cela
  • Il est plus difficile de dire ce qui se passe
2
Vic

Vous pouvez utiliser un constructeur habituel et des méthodes statiques qui donnent un nom aux arguments:

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 par rapport aux paramètres nommés réels:

  • l'ordre des arguments est pertinent
  • les listes d'arguments variables ne sont pas possibles avec un seul constructeur
  • vous avez besoin d'une méthode pour chaque argument
  • pas vraiment mieux qu'un commentaire (nouveau quelque chose (/*name*/ "pen", /*size*/ 20, /*weight*/ 8.2))

Si vous avez le choix, regardez Scala 2.8. http://www.scala-lang.org/node/2075

2
deamon

Ceci est une variante du modèle Builder décrit par Lawrence ci-dessus.

Je me retrouve souvent à l'utiliser (aux endroits appropriés).

La principale différence est que, dans ce cas, le constructeur est immuatable. Cela a l’avantage de pouvoir être réutilisé et d’être thread-safe.

Vous pouvez donc l'utiliser pour créer un constructeur default puis, aux différents endroits où vous en avez besoin, vous pouvez le configurer et construire votre objet.

Cela semble logique si vous construisez le même objet encore et encore, car vous pouvez alors rendre le générateur statique et ne pas avoir à vous soucier de la modification de ses paramètres.

D'un autre côté, si vous devez construire des objets avec des paramètres changeants, cela entraîne une légère surcharge. (mais bon, vous pouvez combiner la génération statique/dynamique avec des méthodes personnalisées build)

Voici l'exemple de code: 

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

    }
}
1
Scheintod

L'idiome supporté par la bibliothèque karg peut être utile:

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
Dominic Fox

@irreputable a proposé une solution intéressante. Cependant, cela pourrait laisser votre instance de classe dans un état non valide, car aucune validation ni vérification de la cohérence ne sera effectuée. Par conséquent, je préfère combiner cela avec la solution Builder, en évitant la création d’une sous-classe supplémentaire, bien que cela reste une sous-classe de la classe Builder. De plus, comme la classe de construction supplémentaire la rend plus détaillée, j'ai ajouté une méthode supplémentaire utilisant un lambda. J'ai ajouté certaines des autres approches de constructeur pour la complétude.

Commençant par une classe comme suit:

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

}

Puis en utilisant ceci en appliquant les différentes méthodes:

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

Cela ressemble en partie à une arnaque totale de ce que @LaurenceGonsalves a déjà publié, mais vous verrez la petite différence de convention choisie.

Je me demande si JLS implémenterait jamais les paramètres nommés, comment ils le feraient? Seraient-ils étendus à l'un des idiomes existants en fournissant un support abrégé pour cela? Aussi, comment Scala prend-il en charge les paramètres nommés?

Hmmm - assez pour faire des recherches et peut-être une nouvelle question.

0
YoYo