Java에서 Named Parameter 관용구를 구현하는 방법은 무엇입니까? (특히 생성자)
JavaBeans에서 사용되는 구문이 아닌 Objective-C와 같은 구문을 찾고 있습니다.
작은 코드 예제가 좋습니다.
감사.
답변:
생성자에서 키워드 인수를 시뮬레이션하기 위해 내가 본 최고의 Java 관용구는 Effective Java 2nd Edition에 설명 된 Builder 패턴 입니다.
기본 아이디어는 다른 생성자 매개 변수에 대한 setter (일반적으로 getter가 아님)가있는 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();
주요주의 사항은 다음과 같습니다.
이 블로그 게시물 (내가 아님) 도 확인하실 수 있습니다 .
.withFoo
보다는 .setFoo
: newBuilder().withSize(1).withName(1).build()
보다는newBuilder().setSize(1).setName(1).build()
There's no compile-time checking that all of the parameters have been specified exactly once.
이 문제는 인터페이스를 반환함으로써 극복 될 수 Builder1
로 BuilderN
여기서 각 커버 어느 족의 하나 이상의 build()
. 코드가 훨씬 더 장황하지만 DSL에 대한 컴파일러 지원과 함께 제공되며 자동 완성 작업을 매우 편리하게 만듭니다.
이것은 언급 할 가치가 있습니다.
Foo foo = new Foo() {{
color = red;
name = "Fred";
size = 42;
}};
소위 이중 중괄호 이니셜 라이저 . 실제로 인스턴스 이니셜 라이저가있는 익명 클래스입니다.
여기에서 조언을 따를 수도 있습니다 : http://www.artima.com/weblogs/viewpost.jsp?thread=118828
int value; int location; boolean overwrite;
doIt(value=13, location=47, overwrite=true);
호출 사이트에서는 장황하지만 전반적으로 오버 헤드가 가장 낮습니다.
doIt( /*value*/ 13, /*location*/ 47, /*overwrite*/ true )
자바 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);
}
}
create()
내 트랙에서 나를 멈췄다. Java에서 그런 스타일의 람다 체인을 본 적이 없습니다. 람다를 사용하여 다른 언어로이 아이디어를 처음 발견 했습니까?
Java는 생성자 또는 메소드 인수에 대해 Objective-C와 유사한 명명 된 매개 변수를 지원하지 않습니다. 게다가 이것은 실제로 Java 방식이 아닙니다. Java에서 일반적인 패턴은 자세한 이름이 지정된 클래스 및 멤버입니다. 클래스와 변수는 명사 여야하고 명명 된 메서드는 동사 여야합니다. 나는 당신이 창의력을 발휘하고 Java 명명 규칙에서 벗어날 수 있고 해키 방식으로 Objective-C 패러다임을 에뮬레이션 할 수 있다고 생각하지만 이것은 코드 유지 관리를 담당하는 평균 Java 개발자가 특별히 높이 평가하지 않을 것입니다. 어떤 언어로 작업 할 때 특히 팀에서 작업 할 때 언어 및 커뮤니티의 관습을 고수해야합니다.
Java 6을 사용하는 경우 변수 매개 변수를 사용하고 정적을 가져 와서 훨씬 더 나은 결과를 생성 할 수 있습니다. 이에 대한 자세한 내용은 다음에서 찾을 수 있습니다.
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 접두사 없이 명명 된 매개 변수 와 속성 기능을 모두 처리한다는 점을 지적하고 싶습니다 . 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());
}
}
이건 어떤가요
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");
인수에 이름을 지정하는 일반적인 생성자와 정적 메서드를 사용할 수 있습니다.
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
not really better than a comment
... 다른 한편으로는 ...)
Java 8의 람다를 사용하면 실제 명명 된 매개 변수에 더 가까워 질 수 있습니다 .
foo($ -> {$.foo = -10; $.bar = "hello"; $.array = new int[]{1, 2, 3, 4};});
이것은 아마도 $
기호를 사용하는 모든 것과 같은 몇 가지 "자바 모범 사례"를 위반할 것입니다 .
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));
}
}
장점 :
단점 :
$foo
호출자에게 탈출하지 (누군가가 콜백의 변수 내부에 할당하지 않는 한) 왜 수 없습니다 그들은 공개?
프로젝트 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();
이것은 Builder
위에서 Lawrence가 설명한 패턴 의 변형입니다 .
나는 이것을 (적절한 장소에서) 많이 사용하고 있음을 발견했습니다.
가장 큰 차이점은이 경우 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();
}
}
Java의 모든 솔루션은 매우 장황 할 수 있지만 Google AutoValues 및 Immutables 와 같은 도구 가 JDK 컴파일 시간 주석 처리를 사용하여 자동으로 빌더 클래스를 생성 한다는 점을 언급 할 가치가 있습니다.
필자의 경우 이름이 지정된 매개 변수가 Java 열거 형에서 사용되기를 원했기 때문에 열거 형 인스턴스를 다른 클래스에서 인스턴스화 할 수 없기 때문에 빌더 패턴이 작동하지 않습니다. @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; }
}
karg 라이브러리에서 지원하는 관용구 는 고려할 가치가 있습니다.
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");
}
}
R Casha
답변 과 동일 하지만 설명하는 코드가없는 것 같습니다 .
다음은 컴파일러 확인 빌더 패턴입니다. 주의 사항 :
.build()
방법을 가질 수 없습니다따라서 통과하지 못하면 실패 할 클래스 외부의 무언가가 필요합니다 Builder<Yes, Yes, Yes>
. getSum
예제로 정적 메서드를 참조하십시오 .
class No {}
class Yes {}
class Builder<K1, K2, K3> {
int arg1, arg2, arg3;
Builder() {}
static Builder<No, No, No> make() {
return new Builder<No, No, No>();
}
@SuppressWarnings("unchecked")
Builder<Yes, K2, K3> arg1(int val) {
arg1 = val;
return (Builder<Yes, K2, K3>) this;
}
@SuppressWarnings("unchecked")
Builder<K1, Yes, K3> arg2(int val) {
arg2 = val;
return (Builder<K1, Yes, K3>) this;
}
@SuppressWarnings("unchecked")
Builder<K1, K2, Yes> arg3(int val) {
this.arg3 = val;
return (Builder<K1, K2, Yes>) this;
}
static int getSum(Builder<Yes, Yes, Yes> build) {
return build.arg1 + build.arg2 + build.arg3;
}
public static void main(String[] args) {
// Compiles!
int v1 = getSum(make().arg1(44).arg3(22).arg2(11));
// Builder.java:40: error: incompatible types:
// Builder<Yes,No,Yes> cannot be converted to Builder<Yes,Yes,Yes>
int v2 = getSum(make().arg1(44).arg3(22));
System.out.println("Got: " + v1 + " and " + v2);
}
}
주의 사항 설명 . 왜 빌드 방법이 없습니까? 문제는 그것이 Builder
클래스에있을 것이고 K1, K2, K3
, 등 으로 매개 변수화 될 것이라는 것입니다 . 메소드 자체가 컴파일되어야하므로 호출하는 모든 것이 컴파일되어야합니다. 따라서 일반적으로 클래스 자체의 메서드에 컴파일 테스트를 넣을 수 없습니다.
비슷한 이유로 빌더 모델을 사용하여 이중 할당을 방지 할 수 없습니다.
@irreputable이 좋은 해결책을 내놓았습니다. 그러나-유효성 검사 및 일관성 검사가 발생하지 않으므로 클래스 인스턴스가 잘못된 상태로 남을 수 있습니다. 따라서 빌더 클래스를 하위 클래스로 만들더라도 추가 하위 클래스가 생성되는 것을 피하면서이를 Builder 솔루션과 결합하는 것을 선호합니다. 또한 추가 빌더 클래스로 인해 더 장황 해지기 때문에 람다를 사용하여 메서드를 하나 더 추가했습니다. 완전성을 위해 다른 빌더 접근 방식 중 일부를 추가했습니다.
다음과 같이 클래스로 시작합니다.
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가 명명 된 매개 변수를 구현한다면 어떻게할까요? 짧은 형식의 지원을 제공하여 기존 관용구 중 하나를 확장 할 수 있습니까? 또한 Scala는 명명 된 매개 변수를 어떻게 지원합니까?
흠-조사하기에 충분하며 새로운 질문 일 수도 있습니다.