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.
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:
Vielleicht möchten Sie auch diesen Blog-Beitrag lesen (nicht von mir).
.withFoo
lieber, als.setFoo
:newBuilder().withSize(1).withName(1).build()
anstattnewBuilder().setSize(1).setName(1).build()
There's no compile-time checking that all of the parameters have been specified exactly once.
Dieses Problem kann überwunden werden, indem SchnittstellenBuilder1
anBuilderN
die Stellen zurückgegeben werden, an denen sie entweder einen der Setter oder den Setter abdeckenbuild()
. Der Code ist viel ausführlicher, bietet jedoch Compiler-Unterstützung für DSL und macht die automatische Vervollständigung sehr angenehm.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.
quelle
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.
quelle
doIt( /*value*/ 13, /*location*/ 47, /*overwrite*/ true )
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); } }
quelle
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?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.
quelle
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));
quelle
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()); } }
quelle
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");
quelle
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:
/*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
quelle
not really better than a comment
... andererseits ...;)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:
Nachteile:
quelle
$foo
entkommt dem Anrufer niemals (es sei denn, jemand weist ihn einer Variablen innerhalb des Rückrufs zu). Warum kann er also nicht öffentlich sein?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();
quelle
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);
quelle
Dies ist eine Variante des
Builder
Musters, 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
build
Methoden 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(); } }
quelle
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; } }
quelle
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"); } }
quelle
R Casha
Antwort, jedoch ohne den Code, der dies erklärt.@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.
quelle