Benanntes Parameter-Idiom in Java

81

Wie implementiere ich Named Parameter Idiom in Java? (speziell für Konstrukteure)

Ich suche nach einer Objective-C-ähnlichen Syntax und nicht nach der in JavaBeans verwendeten.

Ein kleines Codebeispiel wäre in Ordnung.

Vielen Dank.

Rote Hyäne
quelle

Antworten:

103

Das beste Java-Idiom, das ich für die Simulation von Schlüsselwortargumenten in Konstruktoren zu haben scheine, ist das Builder-Muster, das in Effective Java 2nd Edition beschrieben wird .

Die Grundidee besteht darin, eine Builder-Klasse zu haben, die Setter (aber normalerweise keine Getter) für die verschiedenen Konstruktorparameter enthält. Es gibt auch eine build()Methode. Die Builder-Klasse ist häufig eine (statische) verschachtelte Klasse der Klasse, die zum Erstellen verwendet wird. Der Konstruktor der äußeren Klasse ist oft privat.

Das Endergebnis sieht ungefähr so ​​aus:

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

Um eine Instanz von Foo zu erstellen, schreiben Sie Folgendes:

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

Die wichtigsten Einschränkungen sind:

  1. Das Einrichten des Musters ist ziemlich ausführlich (wie Sie sehen können). Wahrscheinlich nicht wert, außer für Klassen, die Sie an vielen Orten instanziieren möchten.
  2. Es wird nicht überprüft, ob alle Parameter genau einmal angegeben wurden. Sie können Laufzeitprüfungen hinzufügen oder diese nur für optionale Parameter verwenden und erforderliche Parameter entweder für Foo oder den Konstruktor des Builders zu normalen Parametern machen. (Die Leute machen sich im Allgemeinen keine Sorgen über den Fall, dass derselbe Parameter mehrmals eingestellt wird.)

Vielleicht möchten Sie auch diesen Blog-Beitrag lesen (nicht von mir).

Laurence Gonsalves
quelle
12
Das sind wirklich keine benannten Parameter, wie es Objective-C tut. Das sieht eher nach einer fließenden Oberfläche aus. Es ist wirklich nicht dasselbe.
Asaph
29
Ich benutze .withFoolieber, als .setFoo: newBuilder().withSize(1).withName(1).build()anstattnewBuilder().setSize(1).setName(1).build()
notnoop
16
Asaph: Ja, ich weiß. Java hat keine benannten Parameter. Deshalb habe ich gesagt, dies sei "die beste Java-Sprache, die ich für die Simulation von Schlüsselwortargumenten zu haben scheine ". Die "benannten Parameter" von Objective-C sind ebenfalls weniger als ideal, da sie eine bestimmte Reihenfolge erzwingen. Sie sind keine echten Keyword-Argumente wie in Lisp oder Python. Zumindest beim Java Builder-Muster müssen Sie sich nur die Namen und nicht die Reihenfolge merken, genau wie bei echten Schlüsselwortargumenten.
Laurence Gonsalves
14
notnoop: Ich bevorzuge "set", da dies Setter sind, die den Status des Builders mutieren. Ja, "mit" sieht in dem einfachen Fall gut aus, in dem Sie alles miteinander verketten, aber in komplizierteren Fällen, in denen Sie den Builder in einer eigenen Variablen haben (vielleicht weil Sie Eigenschaften bedingt festlegen), gefällt mir die Menge Das Präfix macht völlig klar, dass der Builder beim Aufrufen dieser Methoden mutiert wird. Das Präfix "mit" klingt für mich funktional, und diese Methoden sind definitiv nicht funktionsfähig.
Laurence Gonsalves
4
There's no compile-time checking that all of the parameters have been specified exactly once.Dieses Problem kann überwunden werden, indem Schnittstellen Builder1an BuilderNdie Stellen zurückgegeben werden, an denen sie entweder einen der Setter oder den Setter abdecken build(). Der Code ist viel ausführlicher, bietet jedoch Compiler-Unterstützung für DSL und macht die automatische Vervollständigung sehr angenehm.
rsp
72

Dies ist erwähnenswert:

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

der sogenannte Double-Brace-Initialisierer . Es ist eigentlich eine anonyme Klasse mit Instanzinitialisierer.

unwiderlegbar
quelle
26
Interessante Technik, scheint aber etwas teuer zu sein, da sie jedes Mal eine neue Klasse erstellt, wenn ich sie in meinem Code verwende.
Rote Hyäne
6
Abgesehen von Warnungen vor automatischer Formatierung, Unterklassifizierung und Serialisierung kommt dies der C # -Syntax für die eigenschaftsbasierte Initialisierung ziemlich nahe. In C # ab 4.0 sind jedoch auch Parameter benannt, sodass Programmierer die Qual der Wahl haben, im Gegensatz zu Java-Programmierern, die Redewendungen simulieren müssen, die verhindern, dass sie sich später in den Fuß schießen.
Distortum
3
Ich bin froh zu sehen, dass dies möglich ist, aber ich musste abstimmen, da diese Lösung teuer ist, wie Red Hyena betonte. Ich kann es kaum erwarten, bis Java tatsächlich benannte Parameter unterstützt, wie es Python tut.
Gattster
12
Upvote. Dies beantwortet die Frage auf die lesbarste und prägnanteste Weise. Fein. Es ist "nicht performant". Wie viele zusätzliche Millisekunden und Bits sprechen wir hier? Einer von ihnen? Zehn? Versteh mich nicht falsch - ich werde dies nicht verwenden, da ich es hassen würde, von einer wortreichen Java-Nuss ausgeführt zu werden (Wortspiel beabsichtigt;)
Steve
2
Perfekt! Benötigt nur öffentliche / geschützte Felder. Es ist definitiv die beste Lösung und verursacht viel weniger Overhead als Builder. Hyäne / Gattster: Bitte (1) lesen Sie JLS und (2) überprüfen Sie den generierten Bytecode, bevor Sie Ihre Kommentare schreiben.
Jonatan Kaźmierczak
21

Sie können auch versuchen, den Ratschlägen von hier aus zu folgen: http://www.artima.com/weblogs/viewpost.jsp?thread=118828

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

Es ist ausführlich auf der Anrufseite, bietet aber insgesamt den geringsten Overhead.

rkj
quelle
3
Gute Ursache für den geringen Overhead, aber es fühlt sich so hackisch an. Ich werde wahrscheinlich die Builder () -Methode für Fälle verwenden, in denen es viele Argumente gibt.
Gattster
23
Ich denke, dies verfehlt den Punkt der benannten Parameter völlig. (Das hat etwas, das Namen mit Werten assoziiert ). Es gibt keinerlei Anhaltspunkte dafür, ob Sie die Reihenfolge umkehren. Anstatt dies zu tun, würde ich raten, einfach einen Kommentar hinzuzufügen:doIt( /*value*/ 13, /*location*/ 47, /*overwrite*/ true )
Scheintod
20

Java 8-Stil:

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

    }
}
  • benannte Parameter
  • Reihenfolge der Argumente festlegen
  • statische Prüfung -> keine namenlose Person möglich
  • Es ist schwierig, Argumente des gleichen Typs versehentlich zu wechseln (wie es bei Teleskopkonstruktoren möglich ist).
Alex
quelle
3
nett. Was für ein Scherz, es hat keine variable Argumentreihenfolge. (Aber nicht zu sagen, dass ich das benutzen würde ...)
Scheintod
1
Das ist eine geniale Idee. Die Definition von create()hielt mich in meinen Spuren auf. Ich habe diese Art der Lambda-Verkettung in Java noch nie gesehen. Haben Sie diese Idee zum ersten Mal in einer anderen Sprache mit Lambdas entdeckt?
Kevinarpe
2
Es heißt Currying: en.wikipedia.org/wiki/Currying . Übrigens: Vielleicht ist es eine clevere Idee, aber ich würde diesen benannten Argumentationsstil nicht empfehlen. Ich habe es in einem realen Projekt mit vielen Argumenten getestet und es führt zu schwer lesbarem und schwer zu navigierendem Code.
Alex
Schließlich erhält Java den Visual Basic-Stil mit dem Namen parameter. Java hat es vorher nicht getan, weil C ++ es nicht tut. Aber wir werden es irgendwann schaffen. Ich würde sagen, dass 90% des Java-Polymorphismus nur um optionale Parameter herum hackt.
Tuntable
7

Java unterstützt keine Objective-C-ähnlichen benannten Parameter für Konstruktoren oder Methodenargumente. Darüber hinaus ist dies nicht die Java-Methode. In Java wird das typische Muster ausführlich als Klassen und Mitglieder bezeichnet. Klassen und Variablen sollten Substantive sein und die benannte Methode sollte Verben sein. Ich nehme an, Sie könnten kreativ werden und von den Java-Namenskonventionen abweichen und das Objective-C-Paradigma auf hackige Weise emulieren, aber dies würde der durchschnittliche Java-Entwickler, der mit der Pflege Ihres Codes beauftragt ist, nicht besonders schätzen. Wenn Sie in einer beliebigen Sprache arbeiten, müssen Sie sich an die Konventionen der Sprache und der Community halten, insbesondere wenn Sie in einem Team arbeiten.

Asaph
quelle
4
+1 - für den Rat, sich an die Redewendungen der Sprache zu halten, die Sie gerade verwenden. Bitte denken Sie an die anderen Personen, die Ihren Code lesen müssen!
Stephen C
3
Ich habe Ihre Antwort positiv bewertet, weil ich denke, dass Sie einen guten Punkt machen. Wenn ich raten müsste, warum Sie die Ablehnung erhalten haben, liegt das wahrscheinlich daran, dass dies die Frage nicht beantwortet. F: "Wie mache ich benannte Parameter in Java?" A: "Sie nicht"
Gattster
12
Ich habe abgelehnt, weil ich denke, dass Ihre Antwort nichts mit der Frage zu tun hat. Ausführliche Namen lösen das Problem der Parameterreihenfolge nicht wirklich. Ja, Sie könnten sie im Namen kodieren, aber das ist eindeutig nicht praktikabel. Das Aufrufen eines nicht verwandten Paradigmas erklärt nicht, warum ein Paradigma nicht unterstützt wird.
Andreas Mueller
7

Wenn Sie Java 6 verwenden, können Sie die variablen Parameter verwenden und statische Daten importieren, um ein viel besseres Ergebnis zu erzielen. Details dazu finden Sie in:

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

Kurz gesagt, Sie könnten so etwas haben wie:

go();
go(min(0));
go(min(0), max(100));
go(max(100), min(0));
go(prompt("Enter a value"), min(0), max(100));
R Casha
quelle
2
Ich mag es, aber es löst immer noch nur die Hälfte des Problems. In Java können Sie nicht versehentlich transponierte Parameter verhindern, ohne eine Überprüfung der Kompilierungszeit auf erforderliche Werte zu verlieren.
cdunn2001
Ohne Typensicherheit ist dies schlimmer als einfache // Kommentare.
Peter Davis
7

Ich möchte darauf hinweisen, dass dieser Stil sowohl den benannten Parameter als auch die Eigenschaftenmerkmale ohne das Präfix get und set behandelt, das andere Sprachen haben. Es ist im Java-Bereich nicht konventionell, aber einfacher, nicht schwer zu verstehen, insbesondere wenn Sie mit anderen Sprachen umgegangen sind.

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());
   }
}
LEMUEL ADANE
quelle
6

Wie wäre es mit

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");
user564819
quelle
5
Builder ist viel besser, da Sie einige Einschränkungen für build () überprüfen können. Ich bevorzuge aber auch kürzere Argumente ohne set / mit Präfix.
rkj
4
Außerdem ist das Builder-Muster besser, da Sie damit die gebaute Klasse (in diesem Fall Tiger) unveränderlich machen können.
Jeff Olson
2

Sie können einen üblichen Konstruktor und statische Methoden verwenden, die den Argumenten einen Namen geben:

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

}

Verwendung:

import static Something.*;

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

Einschränkungen im Vergleich zu tatsächlich benannten Parametern:

  • Argumentreihenfolge ist relevant
  • Variablenargumentlisten sind mit einem einzelnen Konstruktor nicht möglich
  • Sie benötigen für jedes Argument eine Methode
  • nicht wirklich besser als ein Kommentar (neues Something ( /*name*/ "pen", /*size*/ 20, /*weight*/ 8.2))

Wenn Sie die Wahl haben, schauen Sie sich Scala 2.8 an. http://www.scala-lang.org/node/2075

Deamon
quelle
2
Ein Nachteil dieses Ansatzes ist , dass Sie müssen die Argumente in der richtigen Reihenfolge zu bekommen. Mit dem obigen Code können Sie schreiben: Etwas s = Neues Etwas (Name ("Stift"), Größe (20), Größe (21)); Dieser Ansatz hilft Ihnen auch nicht, die Eingabe optionaler Argumente zu vermeiden.
Matt Quail
1
Ich würde dies für die Analyse positiv bewerten: not really better than a comment... andererseits ...;)
Scheintod
2

Mit den Lambdas von Java 8 können Sie den tatsächlich benannten Parametern noch näher kommen .

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

Beachten Sie, dass dies wahrscheinlich gegen ein paar Dutzend "Java Best Practices" verstößt (wie alles, was das $Symbol verwendet).

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

Vorteile:

  • Deutlich kürzer als jedes Builder-Muster, das ich bisher gesehen habe
  • Funktioniert sowohl für Methoden als auch für Konstruktoren
  • Geben Sie den Safe vollständig ein
  • Es kommt den tatsächlich benannten Parametern in anderen Programmiersprachen sehr nahe
  • Es ist ungefähr so ​​sicher wie ein typisches Builder-Muster (kann Parameter mehrmals einstellen)

Nachteile:

  • Ihr Chef wird Sie wahrscheinlich dafür lynchen
  • Es ist schwieriger zu sagen, was los ist
Vic
quelle
1
Nachteile: Felder sind öffentlich und nicht endgültig. Wenn Sie damit einverstanden sind, warum nicht einfach Setter verwenden? Wie funktioniert es bei Methoden?
Alex
Könnte Setter verwenden, aber worum geht es dabei? Es macht den Code nur länger und das würde den Vorteil beseitigen, es so zu machen. Die Zuordnung ist nebenwirkungsfrei und Setter sind Black Boxes. $fooentkommt dem Anrufer niemals (es sei denn, jemand weist ihn einer Variablen innerhalb des Rückrufs zu). Warum kann er also nicht öffentlich sein?
Vic
2

Sie können die @ Builder-Annotation von project Lombok verwenden , um benannte Parameter in Java zu simulieren. Dadurch wird ein Builder für Sie generiert, mit dem Sie neue Instanzen einer beliebigen Klasse erstellen können (sowohl Klassen, die Sie geschrieben haben, als auch solche, die aus externen Bibliotheken stammen).

So aktivieren Sie es für eine Klasse:

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

Danach können Sie dies verwenden durch:

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

Wenn Sie einen solchen Builder für eine Klasse aus einer Bibliothek erstellen möchten, erstellen Sie eine kommentierte statische Methode wie folgt:

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

Dadurch wird eine Methode mit dem Namen "builder" generiert, die wie folgt aufgerufen werden kann:

LibraryUser user = UserBuilder.builder()
    .id(1L)
    .name("joe")
    .build();
Istvan Devai
quelle
Google Auto / Value dient einem ähnlichen Zweck, verwendet jedoch das Framework für die Verarbeitung von Anmerkungen, das viel sicherer ist (nach einem Upgrade der JVM funktioniert es immer noch) als die Manipulation des Byte-Codes von Project Lombocks.
René
1
Ich denke, dass der Auto / Wert von Google im Vergleich zur Verwendung von Lombok eine zusätzliche Meile erfordert. Lomboks Ansatz ist mehr oder weniger kompatibel mit herkömmlichem JavaBean-Schreiben (z. B. können Sie über neue instanziieren, Felder werden in einem Debugger ordnungsgemäß angezeigt usw.). Ich bin auch kein großer Fan der Byte-Code-Manpiluation + IDE-Plugin-Lösung, die Lombok verwendet, aber ich muss zugeben, dass sie in der Praxis in Ordnung ist. Bisher keine Probleme mit JDK-Versionsänderungen, Reflexion usw.
Istvan Devai
Ja, das ist wahr. Für auto / value müssen Sie eine abstrakte Klasse angeben, die dann implementiert wird. Lombok benötigt viel weniger Code. Vor- und Nachteile müssen also verglichen werden.
René
2

Ich bin der Meinung, dass die "Kommentar-Problemumgehung" eine eigene Antwort verdient (versteckt in vorhandenen Antworten und in Kommentaren hier erwähnt).

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

Dies ist eine Variante des BuilderMusters, wie es oben von Lawrence beschrieben wurde.

Ich benutze dies oft (an den entsprechenden Orten).

Der Hauptunterschied besteht darin, dass der Builder in diesem Fall unbeweglich ist . Dies hat den Vorteil, dass es wiederverwendet werden kann und threadsicher ist.

Sie können dies also verwenden, um einen Standard-Builder zu erstellen, und dann können Sie ihn an den verschiedenen Stellen, an denen Sie ihn benötigen, konfigurieren und Ihr Objekt erstellen.

Dies ist am sinnvollsten, wenn Sie immer wieder dasselbe Objekt erstellen, da Sie dann den Builder statisch machen können und sich nicht um die Änderung seiner Einstellungen kümmern müssen.

Wenn Sie dagegen Objekte mit sich ändernden Parametern erstellen müssen, ist der Aufwand etwas geringer. (aber hey, Sie können statische / dynamische Generierung mit benutzerdefinierten buildMethoden kombinieren )

Hier ist der Beispielcode:

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

    }
}
Scheintod
quelle
1

Jede Lösung in Java wird wahrscheinlich ziemlich ausführlich sein, aber es ist erwähnenswert, dass Tools wie Google AutoValues und Immutables mithilfe der JDK-Annotationsverarbeitung zur Kompilierungszeit automatisch Builder-Klassen für Sie generieren.

In meinem Fall wollte ich, dass benannte Parameter in einer Java-Aufzählung verwendet werden, sodass ein Builder-Muster nicht funktioniert, da Aufzählungsinstanzen nicht von anderen Klassen instanziiert werden können. Ich habe einen ähnlichen Ansatz wie die Antwort von @ deamon gefunden, aber eine Überprüfung der Parameterreihenfolge zur Kompilierungszeit hinzugefügt (auf Kosten von mehr Code).

Hier ist der Client-Code:

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

Und die Umsetzung:

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; }
}
Scott
quelle
0

Die von der Karg-Bibliothek unterstützte Redewendung kann eine Überlegung wert sein:

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");
    }
}
Dominic Fox
quelle
Dies scheint im Grunde dasselbe zu sein wie die R CashaAntwort, jedoch ohne den Code, der dies erklärt.
Scheintod
-1

@irreputable hat eine schöne Lösung gefunden. Es kann jedoch sein, dass Ihre Klasseninstanz in einem ungültigen Zustand bleibt, da keine Validierung und Konsistenzprüfung durchgeführt wird. Daher bevorzuge ich es, dies mit der Builder-Lösung zu kombinieren, wobei zu vermeiden ist, dass die zusätzliche Unterklasse erstellt wird, obwohl die Builder-Klasse weiterhin untergeordnet wäre. Da die zusätzliche Builder-Klasse es ausführlicher macht, habe ich außerdem eine weitere Methode mit einem Lambda hinzugefügt. Der Vollständigkeit halber habe ich einige der anderen Builder-Ansätze hinzugefügt.

Beginnen Sie mit einer Klasse wie folgt:

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

}

Verwenden Sie dazu die verschiedenen Methoden:

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

Es sieht zum Teil wie eine totale Abzocke von dem aus, was @LaurenceGonsalves bereits gepostet hat, aber Sie werden den kleinen Unterschied in der gewählten Konvention sehen.

Ich frage mich, ob JLS jemals benannte Parameter implementieren würde, wie sie das tun würden. Würden sie eine der bestehenden Redewendungen erweitern, indem sie sie in Kurzform unterstützen? Wie unterstützt Scala benannte Parameter?

Hmmm - genug um zu recherchieren und vielleicht eine neue Frage.

YoYo
quelle