Javaで名前付きパラメーターイディオムを実装する方法? (特にコンストラクターの場合)
JavaBeansで使用されている構文ではなく、Objective-Cのような構文を探しています。
小さなコード例で問題ありません。
ありがとう。
最良のJavaコンストラクタでキーワード引数をシミュレートするイディオムは、 Effective Java 2nd Edition 。
基本的な考え方は、異なるコンストラクターパラメーターのセッター(通常はゲッターではない)を持つ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();
主な注意事項は次のとおりです。
また、 このブログ投稿 (私ではなく)をチェックアウトすることもできます。
これは言及する価値があります:
Foo foo = new Foo() {{
color = red;
name = "Fred";
size = 42;
}};
いわゆるdouble-brace initializer。実際には、インスタンス初期化子を持つ匿名クラスです。
ここからアドバイスに従うこともできます: 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を使用している場合は、変数パラメーターを使用し、staticをインポートして、より良い結果を生成できます。この詳細については、次を参照してください。
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));
このスタイルは、getおよびsetなしでnamed parameterとpropertiesの両方の機能に対応していることを指摘したいと思います。 =他の言語が持つ接頭辞。 Javaレルムでは一般的ではありませんが、他の言語を処理した場合は特に単純で、理解するのは難しくありません。
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());
}
}
以下は、Joshua BlochのEffective Javaに記載されている手法の少しのバリエーションです。ここでは、クライアントコードをより読みやすく(または、よりDSLっぽく)しようと試みました。
/**
* 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();
}
}
このバリエーションでは、擬似コンストラクタに意味のある名前を付けることもできます。
どう?
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");
Javaは、コンストラクターまたはメソッド引数のObjective-Cのような名前付きパラメーターをサポートしていません。さらに、これは実際にはJavaの方法ではありません。 Javaでは、典型的なパターンは詳細な名前のクラスとメンバーです。クラスと変数は名詞で、メソッドは動詞である必要があります。 Java命名規則から独創的で逸脱したり、Objective-Cパラダイムをハックした方法でエミュレートしたりできると思いますが、これは平均的なJava開発者には特に評価されませんコードを維持します。任意の言語で作業する場合、特にチームで作業する場合は、言語とコミュニティの規則に従う必要があります。
Java 8のラムダを使用すると、名前付きパラメータrealにさらに近づけることができます。
foo($ -> {$.foo = -10; $.bar = "hello"; $.array = new int[]{1, 2, 3, 4};});
これはおそらく、数十個の「Javaのベストプラクティス」に違反することに注意してください($
シンボル)。
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 enumで名前付きパラメーターを使用したかったので、enumインスタンスは他のクラスによってインスタンス化できないため、ビルダーパターンは機能しませんでした。 @deamonの答えに似ていますが、パラメータの順序のコンパイル時のチェックを追加します(コードが多くなります)
クライアントコードは次のとおりです。
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; }
}
プロジェクトLombokの @ 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);
引数に名前を付ける通常のコンストラクターと静的メソッドを使用できます。
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 をご覧ください。
これは、上記のLawrenceが説明したBuilder
パターンの変形です。
私はこれを(適切な場所で)頻繁に使用していることに気付きます。
主な違いは、この場合、Builderはimmuatableです。これには、reusedであり、スレッドセーフであるという利点があります。
したがって、これを使用して1つデフォルトの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();
}
}
karg library でサポートされているイディオムは検討する価値があるかもしれません:
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");
}
}
@irreputableは素晴らしいソリューションを思いつきました。ただし、検証や整合性チェックが行われないため、クラスインスタンスが無効な状態のままになる可能性があります。したがって、これをBuilderソリューションと組み合わせて、余分なサブクラスが作成されるのを避けますが、ビルダークラスはサブクラスのままです。さらに、余分なビルダークラスにより冗長になるため、ラムダを使用するもう1つのメソッドを追加しました。完全を期すために、他のビルダーアプローチをいくつか追加しました。
次のようなクラスから始めます。
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が名前付きパラメーターを実装するとしたら、どのように実装するのでしょうか。短い形式のサポートを提供することにより、既存のイディオムの1つを拡張しますか?また、Scalaは名前付きパラメーターをどのようにサポートしますか?
うーん-研究するのに十分で、おそらく新しい質問です。