Programing

자바에서 명명 된 매개 변수 관용구

lottogame 2020. 10. 28. 07:38
반응형

자바에서 명명 된 매개 변수 관용구


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

주요주의 사항은 다음과 같습니다.

  1. 패턴 설정은 매우 장황합니다 (보시다시피). 많은 곳에서 인스턴스화하려는 클래스를 제외하고는 그만한 가치가 없을 것입니다.
  2. 모든 매개 변수가 정확히 한 번 지정되었는지 컴파일 타임 검사가 없습니다. 런타임 검사를 추가하거나 선택적 매개 변수에만 이것을 사용하고 필수 매개 변수를 Foo 또는 Builder의 생성자에 일반 매개 변수로 만들 수 있습니다. (사람들은 일반적으로 동일한 매개 변수가 여러 번 설정되는 경우에 대해 걱정하지 않습니다.)

이 블로그 게시물 (내가 아님) 도 확인하실 수 있습니다 .


이것은 언급 할 가치가 있습니다.

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

콜 사이트에서는 장황하지만 전반적으로 오버 헤드가 가장 낮습니다.


자바 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을 사용하는 경우 변수 매개 변수를 사용하고 정적을 가져 와서 훨씬 더 나은 결과를 생성 할 수 있습니다. 이에 대한 자세한 내용은 다음에서 찾을 수 있습니다.

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

이 스타일은 다른 언어에있는 getset 접두사 없이 명명 된 매개 변수속성 기능을 모두 처리한다는 점을 지적하고 싶습니다 . 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();
    }
}

이 변형을 사용하면 의사 생성자에게 의미있는 이름을 지정할 수도 있습니다.


Java는 생성자 또는 메소드 인수에 대해 Objective-C와 유사한 명명 된 매개 변수를 지원하지 않습니다. 게다가 이것은 실제로 Java 방식이 아닙니다. Java에서 일반적인 패턴은 자세한 이름이 클래스 및 멤버입니다. 클래스와 변수는 명사 여야하고 명명 된 메서드는 동사 여야합니다. 나는 당신이 창의력을 발휘하고 Java 명명 규칙에서 벗어날 수 있고 해키 방식으로 Objective-C 패러다임을 에뮬레이션 할 수 있다고 생각하지만 이는 코드 유지 관리를 담당하는 평균 Java 개발자가 특별히 높이 평가하지 않을 것입니다. 어떤 언어로 작업 할 때 특히 팀에서 작업 할 때 언어 및 커뮤니티의 관습을 고수해야합니다.


이건 어떤가요

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

실제 명명 된 매개 변수와 비교 한 제한 사항 :

  • 인수 순서가 적절하다
  • 단일 생성자로 가변 인수 목록을 사용할 수 없습니다.
  • 모든 인수에 대한 방법이 필요합니다.
  • 댓글보다별로 좋지 않습니다 (new Something ( /*name*/ "pen", /*size*/ 20, /*weight*/ 8.2))

선택권이 있다면 Scala 2.8을 살펴보십시오. http://www.scala-lang.org/node/2075


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

장점 :

  • 지금까지 본 어떤 빌더 패턴보다 상당히 짧음
  • 메서드와 생성자 모두에서 작동합니다.
  • 완전한 유형 안전
  • 다른 프로그래밍 언어의 실제 명명 된 매개 변수와 매우 유사합니다.
  • 일반적인 빌더 패턴만큼 안전합니다 (매개 변수를 여러 번 설정할 수 있음).

단점 :

  • 당신의 상사가 아마 당신을 린치 할 것입니다.
  • 무슨 일이 일어나고 있는지 말하기가 더 어렵습니다.

Java의 모든 솔루션은 매우 장황 할 수 있지만 Google AutoValuesImmutables같은 도구 가 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; }
}

프로젝트 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);

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

이것은 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();

    }
}

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

It looks like in part a total rip-off from what @LaurenceGonsalves already posted, but you will see the small difference in convention chosen.

I am wonder, if JLS would ever implement named parameters, how they would do it? Would they be extending on one of the existing idioms by providing a short-form support for it? Also how does Scala support named parameters?

Hmmm - enough to research, and maybe a new question.

참고URL : https://stackoverflow.com/questions/1988016/named-parameter-idiom-in-java

반응형