Java 中的命名参数习惯用法

发布于 2024-08-16 22:01:26 字数 117 浏览 2 评论 0原文

如何在 Java 中实现命名参数习惯用法? (特别是对于构造函数)

我正在寻找一种类似 Objective-C 的语法,而不是像 JavaBeans 中使用的语法。

一个小的代码示例就可以了。

How to implement Named Parameter idiom in Java? (especially for constructors)

I am looking for an Objective-C like syntax and not like the one used in JavaBeans.

A small code example would be fine.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(22

夜深人未静 2024-08-23 22:01:26

我认为在构造函数中模拟关键字参数的最佳 Java 习惯是 Builder 模式,如 Effective 中所述。 Java 第二版

基本思想是拥有一个 Builder 类,该类具有针对不同构造函数参数的 setter(但通常没有 getter)。还有一个 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 构造函数的普通参数。 (人们通常不担心多次设置相同参数的情况。)

您可能还想查看 这篇博文(不是我写的)。

The best Java idiom I've seem for simulating keyword arguments in constructors is the Builder pattern, described in Effective Java 2nd Edition.

The basic idea is to have a Builder class that has setters (but usually not getters) for the different constructor parameters. There's also a build() method. The Builder class is often a (static) nested class of the class that it's used to build. The outer class's constructor is often private.

The end result looks something like:

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

To create an instance of Foo you then write something like:

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

The main caveats are:

  1. Setting up the pattern is pretty verbose (as you can see). Probably not worth it except for classes you plan on instantiating in many places.
  2. There's no compile-time checking that all of the parameters have been specified exactly once. You can add runtime checks, or you can use this only for optional parameters and make required parameters normal parameters to either Foo or the Builder's constructor. (People generally don't worry about the case where the same parameter is being set multiple times.)

You may also want to check out this blog post (not by me).

三人与歌 2024-08-23 22:01:26

值得一提的是:

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

所谓的双括号初始化器。它实际上是一个带有实例初始值设定项的匿名类。

This is worth of mentioning:

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

the so called double-brace initializer. It is actually an anonymous class with instance initializer.

顾冷 2024-08-23 22:01:26

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

    }
}
  • 命名参数
  • 修复参数顺序
  • 静态检查 ->没有无名的人可能
  • 很难意外地切换相同类型的参数(就像在 telescop 构造函数中一样)

Java 8 style:

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

    }
}
  • named parameters
  • fix order of arguments
  • static check -> no nameless Person possible
  • hard to switch arguments of same type by accident (like it is possible in telescop constructors)
与他有关 2024-08-23 22:01:26

您还可以尝试遵循此处的建议。

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

它在调用站点上很冗长,但总体而言开销最低。

You could also try to follow advice from here.

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

It's verbose on the call site, but overall gives the lowest overhead.

诗酒趁年少 2024-08-23 22:01:26

我想指出的是,这种样式解决了命名参数属性功能,而没有getset > 其他语言所具有的前缀。它在 Java 领域并不常见,但它更简单、更短,特别是如果您处理过其他语言的话。

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 Adane").age(3);
  
        // addresses property style
        System.out.println(jacobi.name());
        System.out.println(jacobi.age());
  
        // updates property values
        jacobi.name("Lemuel Jacobi Adane");
        jacobi.age(4);

        System.out.println(jacobi.name());
        System.out.println(jacobi.age());
    }
}

I would like to point out that this style addresses both the named parameter and the properties features without the get and set prefix which other language have. Its not conventional in Java realm but its simpler and shorter, especially if you have handled other languages.

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 Adane").age(3);
  
        // addresses property style
        System.out.println(jacobi.name());
        System.out.println(jacobi.age());
  
        // updates property values
        jacobi.name("Lemuel Jacobi Adane");
        jacobi.age(4);

        System.out.println(jacobi.name());
        System.out.println(jacobi.age());
    }
}
铃予 2024-08-23 22:01:26

如果您使用的是 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));

If you are using Java 6, you can use the variable parameters and import static to produce a much better result. Details of this are found in:

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

In short, you could have something like:

go();
go(min(0));
go(min(0), max(100));
go(max(100), min(0));
go(prompt("Enter a value"), min(0), max(100));
辞取 2024-08-23 22:01:26

怎么样

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

What about

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");
素年丶 2024-08-23 22:01:26

Java 不支持类似于 Objective-C 的构造函数或方法参数的命名参数。此外,这确实不是 Java 的做事方式。

在java中,典型的模式是对类和成员进行冗长的命名。类和变量应该是名词,方法命名应该是动词。我想您可以发挥创意,偏离 Java 命名约定,以一种黑客的方式模拟 Objective-C 范式,但这不会被负责维护代码的普通 Java 开发人员特别欣赏。
使用任何语言工作时,您都应该遵守语言和社区的惯例,尤其是在团队中工作时。

Java does not support Objective-C-like named parameters for constructors or method arguments. Furthermore, this is really not the Java way of doing things.

In java, the typical pattern is verbosely named classes and members. Classes and variables should be nouns and method named should be verbs. I suppose you could get creative and deviate from the Java naming conventions and emulate the Objective-C paradigm in a hacky way but this wouldn't be particularly appreciated by the average Java developer charged with maintaining your code.
When working in any language, it behooves you to stick to the conventions of the language and community, especially when working on a team.

绳情 2024-08-23 22:01:26

我觉得“评论解决方法”值得有自己的答案(隐藏在现有答案中并在此处的评论中提到)。

someMethod(/* width */ 1024, /* height */ 768);

I feel like the "comment-workaround" deserves it's own answer (hidden in existing answers and mentioned in comments here).

someMethod(/* width */ 1024, /* height */ 768);
诺曦 2024-08-23 22:01:26

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

Any solution in Java is likely going to be pretty verbose, but it's worth mentioning that tools like Google AutoValues and Immutables will generate builder classes for you automatically using JDK compile time annotation processing.

For my case, I wanted named parameters to use in a Java enum, so a builder pattern wouldn't work because enum instances can't be instantiated by other classes. I came up with an approach similar @deamon's answer but adds compile-time checking of parameter ordering (at the expense of more code)

Here's client code:

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

And the implementation:

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; }
}
和我恋爱吧 2024-08-23 22:01:26

您可以使用项目 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);
    }
  }

这将生成一个名为“的方法”构建器”,可以通过以下方式调用:

LibraryUser user = UserBuilder.builder()
    .id(1L)
    .name("joe")
    .build();

You can use project Lombok's @Builder annotation to simulate named parameters in Java. This will generate a builder for you which you can use to create new instances of any class (both classes you've written and those coming from external libraries).

This is how to enable it on a class:

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

Afterwards you can use this by:

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

If you'd like to create such a Builder for a class coming from a library, create an annotated static method like this:

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

This will generate a method named "builder" which can be called by:

LibraryUser user = UserBuilder.builder()
    .id(1L)
    .name("joe")
    .build();
轻拂→两袖风尘 2024-08-23 22:01:26

您可以使用常用的构造函数和静态方法来为参数命名:

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

You could use a usual constructor and static methods that give the arguments a name:

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

}

Usage:

import static Something.*;

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

Limitations compared to real named parameters:

  • argument order is relevant
  • variable argument lists are not possible with a single constructor
  • you need a method for every argument
  • not really better than a comment (new Something(/*name*/ "pen", /*size*/ 20, /*weight*/ 8.2))

If you have the choice look at Scala 2.8. http://www.scala-lang.org/node/2075

攀登最高峰 2024-08-23 22:01:26

使用 Java 8 的 lambda,您可以更接近真实命名参数。

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

优点:

  • 比我迄今为止见过的任何构建器模式都要短得多
  • 适用于方法和构造函数
  • 完全类型安全
  • 它看起来非常接近其他编程语言中的实际命名参数
  • 它与典型的构建器模式一样安全(可以多次设置参数)

缺点:

  • 你的老板可能会因此对你处以私刑
  • 很难判断发生了什么

Using Java 8's lambdas you can get even closer to real named parameters.

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

Do note that this probably violates a couple dozen "java best practices" (like anything that makes use of the $ symbol).

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

Pros:

  • Considerably shorter than any builder pattern I've seen so far
  • Works for both methods and constructors
  • Completely type safe
  • It looks very close to actual named parameters in other programming languages
  • It's about as safe as your typical builder pattern (can set parameters multiple times)

Cons:

  • Your boss will probably lynch you for this
  • It's harder to tell what's going on
疾风者 2024-08-23 22:01:26

这是上面 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();

    }
}

This is a variant of the Builder Pattern as described by Lawrence above.

I find myself using this a lot (at the apropriate places).

The main difference is, that in this case the Builder is immuatable. This has the advantage that it can be reused and is thread-safe.

So you can use this to make one default Builder and then in the various places where you need it you can configure it and build your object.

This makes most sense, if you are building the same object over and over again, because then you can make the builder static and don't have to worry about changing it's settings.

On the other hand if you have to build objects with changing paramaters this has quiet some overhead. (but hey, you can combine static / dynamic generation with custom build methods)

Here is the example code:

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

    }
}
偷得浮生 2024-08-23 22:01:26

这是一个经过编译器检查的构建器模式。注意事项:

  • 这不能防止参数的双重赋值,
  • 你不能有一个很好的 .build() 方法
  • 每个字段一个泛型参数

所以你需要类之外的东西,如果不通过<代码>生成器<是,是,是>。请参阅 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 等。由于方法本身必须编译,所以一切它调用必须编译。所以,一般来说,我们不能把编译测试放在类本身的方法中。

出于类似的原因,我们无法阻止使用构建器模型的双重分配。

Here is a compiler-checked Builder pattern. Caveats:

  • this can't prevent double assignment of an argument
  • you can't have a nice .build() method
  • one generic parameter per field

So you need something outside the class that will fail if not passed Builder<Yes, Yes, Yes>. See the getSum static method as an example.

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

Caveats explained. Why no build method? The trouble is that it's going to be in the Builder class, and it will be parameterized with K1, K2, K3, etc. As the method itself has to compile, everything it calls must compile. So, generally, we can't put a compilation test in a method of the class itself.

For a similar reason, we can't prevent double assignment using a builder model.

梦中的蝴蝶 2024-08-23 22:01:26

现在我们都在 Java 17 上;-),使用记录是模仿这个习惯用法的超级简单的方法:

public class OrderTemplate() {
    private int tradeSize, limitDistance, backoffDistance;

    public record TradeSize( int value ) {}
    public record LimitDistance( int value ) {}
    public record BackoffDistance( int value ) {}
    public OrderTemplate( TradeSize t, LimitDistance d, BackoffDistance b ) {
      this.tradeSize = t.value();
      this.limitDistance = d.value();
      this.backoffDistance = b.value();
    }
}

然后你可以调用:

var t = new OrderTemplate( new TradeSize(30), new LimitDistance(182), new BackoffDistance(85) );

我发现它非常容易阅读,并且我已经完全停止获取所有 int参数混淆了(“是尺寸优先还是距离……”)。

Now that we're all on Java 17 ;-), using records is a super-easy way to imitate this idiom:

public class OrderTemplate() {
    private int tradeSize, limitDistance, backoffDistance;

    public record TradeSize( int value ) {}
    public record LimitDistance( int value ) {}
    public record BackoffDistance( int value ) {}
    public OrderTemplate( TradeSize t, LimitDistance d, BackoffDistance b ) {
      this.tradeSize = t.value();
      this.limitDistance = d.value();
      this.backoffDistance = b.value();
    }
}

Then you can call:

var t = new OrderTemplate( new TradeSize(30), new LimitDistance(182), new BackoffDistance(85) );

Which I've found extremely easy to read and I've completely stopped getting all the int parameters mixed up ("was it size first or distance...").

国际总奸 2024-08-23 22:01:26

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

The idiom supported by the karg library may be worth considering:

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");
    }
}
子栖 2024-08-23 22:01:26

您可以模仿应用此模式的命名参数:

public static class CarParameters {
        
    // to make it shorter getters and props are omitted
        
    public ModelParameter setName(String name) {
        this.name = name;
        return new ModelParameter();
    }
    public class ModelParameter {
        public PriceParameter setModel(String model) {
            CarParameters.this.model = model;
            return new PriceParameter();
        }
    }
    public class PriceParameter {
        public YearParameter setPrice(double price) {
            CarParameters.this.price = price;
            return new YearParameter();
        }
    }
    public class YearParameter {
        public ColorParameter setYear(int year) {
            CarParameters.this.year = year;
            return new ColorParameter();
        }
    }
    public class ColorParameter {
        public CarParameters setColor(Color color) {
            CarParameters.this.color = color;
            return new CarParameters();
        }
    }
}

然后您可以将其传递给您的方法,如下所示:

factory.create(new CarParameters()
        .setName("Ford")
        .setModel("Focus")
        .setPrice(20000)
        .setYear(2011)
        .setColor(BLUE));

您可以在此处阅读更多信息 https://medium.com/@ivorobioff/named-parameters-in-java-9072862cfc8c

You can imitate named parameters applying this pattern:

public static class CarParameters {
        
    // to make it shorter getters and props are omitted
        
    public ModelParameter setName(String name) {
        this.name = name;
        return new ModelParameter();
    }
    public class ModelParameter {
        public PriceParameter setModel(String model) {
            CarParameters.this.model = model;
            return new PriceParameter();
        }
    }
    public class PriceParameter {
        public YearParameter setPrice(double price) {
            CarParameters.this.price = price;
            return new YearParameter();
        }
    }
    public class YearParameter {
        public ColorParameter setYear(int year) {
            CarParameters.this.year = year;
            return new ColorParameter();
        }
    }
    public class ColorParameter {
        public CarParameters setColor(Color color) {
            CarParameters.this.color = color;
            return new CarParameters();
        }
    }
}

and then you can pass it to your method as this:

factory.create(new CarParameters()
        .setName("Ford")
        .setModel("Focus")
        .setPrice(20000)
        .setYear(2011)
        .setColor(BLUE));

You can read more here https://medium.com/@ivorobioff/named-parameters-in-java-9072862cfc8c

趁微风不噪 2024-08-23 22:01:26
package org.xxx.lang;

/**
 * A hack to work around the fact that java does not support
 * named parameters in function calls.
 * 
 * Its easy to swap a few String parameters, for example.
 * Some IDEs are better than others than showing the parameter names.
 * This will enforce a compiler error on an inadvertent swap. 
 *
 * @param <T>
 */
public class Datum<T> {

    public final T v;
    
    public Datum(T v) {
        this.v = v;
    }
    
    public T v() {
        return v;
    }
    
    public T value() {
        return v;
    }
    
    public String toString() {
        return v.toString();
    }
}

例子

class Catalog extends Datum<String> {
        public Catalog(String v) {
            super(v);
        }
    }

    class Schema extends Datum<String> {
        public Schema(String v) {
            super(v);
        }
    }
class Meta {
        public void getTables(String catalog, String schema, String tablePattern) {
            // pseudo DatabaseMetaData.getTables();
        }
    }
    
    class MetaChecked {
        public void getTables(Catalog catalog, Schema schema, String tablePattern) {
            // pseudo DatabaseMetaData.getTables();
        }
    }

    @Test
    public void test() {
        Catalog c = new Catalog("test");
        assertEquals("test",c.v);
        assertEquals("test",c.v());
        assertEquals("test",c.value());
        String t = c.v;
        assertEquals("test",t);
    }

    public void uncheckedExample() {
        new Meta().getTables("schema","catalog","%"); 
        new Meta().getTables("catalog","schema","%"); // ooops
    }
    
    public void checkedExample() {
         // new MetaChecked().getTables(new Schema("schema"),new Catalog("catalog"),"%"); // won't compile
          new MetaChecked().getTables(new Catalog("catalog"), new Schema("schema"),"%"); 
    }
package org.xxx.lang;

/**
 * A hack to work around the fact that java does not support
 * named parameters in function calls.
 * 
 * Its easy to swap a few String parameters, for example.
 * Some IDEs are better than others than showing the parameter names.
 * This will enforce a compiler error on an inadvertent swap. 
 *
 * @param <T>
 */
public class Datum<T> {

    public final T v;
    
    public Datum(T v) {
        this.v = v;
    }
    
    public T v() {
        return v;
    }
    
    public T value() {
        return v;
    }
    
    public String toString() {
        return v.toString();
    }
}

Example

class Catalog extends Datum<String> {
        public Catalog(String v) {
            super(v);
        }
    }

    class Schema extends Datum<String> {
        public Schema(String v) {
            super(v);
        }
    }
class Meta {
        public void getTables(String catalog, String schema, String tablePattern) {
            // pseudo DatabaseMetaData.getTables();
        }
    }
    
    class MetaChecked {
        public void getTables(Catalog catalog, Schema schema, String tablePattern) {
            // pseudo DatabaseMetaData.getTables();
        }
    }

    @Test
    public void test() {
        Catalog c = new Catalog("test");
        assertEquals("test",c.v);
        assertEquals("test",c.v());
        assertEquals("test",c.value());
        String t = c.v;
        assertEquals("test",t);
    }

    public void uncheckedExample() {
        new Meta().getTables("schema","catalog","%"); 
        new Meta().getTables("catalog","schema","%"); // ooops
    }
    
    public void checkedExample() {
         // new MetaChecked().getTables(new Schema("schema"),new Catalog("catalog"),"%"); // won't compile
          new MetaChecked().getTables(new Catalog("catalog"), new Schema("schema"),"%"); 
    }
忘东忘西忘不掉你 2024-08-23 22:01:26

也许可以使用这个:

HashMapFlow<String,Object>  args2 = HashMapFlow.of( "name", "Aton", "age", 21 );
    Integer age = args2.get("age",51);
    System.out.println(args2.get("name"));
    System.out.println(age);
    System.out.println((Integer)args2.get("dayOfBirth",26));

类:

import java.util.HashMap;

public class HashMapFlow<K,V> extends HashMap {

    public static <K, V> HashMapFlow<K, V> of(Object... args) {
        HashMapFlow<K, V> map = new HashMapFlow();
        for( int i = 0; i < args.length; i+=2) {
            map.put((K)args[i], (V)args[i+1]);
        }
        return map;
    }

    public <T> T get(Object key, V defaultValue) {
        V result = (V)get(key);
        if( result == null ) {
            result = defaultValue;
        }
        return (T)result;
    }

    public HashMapFlow add(K key, V value) {
        put(key,value);
        return this;
    }
}

maybe can use this:

HashMapFlow<String,Object>  args2 = HashMapFlow.of( "name", "Aton", "age", 21 );
    Integer age = args2.get("age",51);
    System.out.println(args2.get("name"));
    System.out.println(age);
    System.out.println((Integer)args2.get("dayOfBirth",26));

class:

import java.util.HashMap;

public class HashMapFlow<K,V> extends HashMap {

    public static <K, V> HashMapFlow<K, V> of(Object... args) {
        HashMapFlow<K, V> map = new HashMapFlow();
        for( int i = 0; i < args.length; i+=2) {
            map.put((K)args[i], (V)args[i+1]);
        }
        return map;
    }

    public <T> T get(Object key, V defaultValue) {
        V result = (V)get(key);
        if( result == null ) {
            result = defaultValue;
        }
        return (T)result;
    }

    public HashMapFlow add(K key, V value) {
        put(key,value);
        return this;
    }
}

不离久伴 2024-08-23 22:01:26

注意我主要在测试中使用它,我想在测试中验证一些东西,但根据您的用例,您可以将参数名称放在方法名称本身中

示例:

  private void verifyCountsForUnprocessedSuccessFailed(int expectedNotProcessedCount, int expectedSuccessCount, int expectedFailedCount) {
    ....
    assertEquals(expectedFailedCount, actualFailedCount);
    assertEquals(expectedSuccessCount, actualSuccessCount);
    assertEquals(expectedNotProcessedCount, actualNotProcessedCount);
  }

或者,您可以向方法添加额外的参数,每个参数前面一个,“命名”它们,

  private void verifyJobCounts(ParamTypeEnum state1, int count1, ParamTypeEnum state2, int count2, ParamTypeEnum state3, int count3) {
    ...

    assertEquals(count1, actualCount1);
    assertEquals(count2, actualCount2);
    assertEquals(count3, actualCount3);
  }

例如

verifyJobCounts(SUCCESS, 0, NOT_PROCESSED, 0, FAILED, 0);

Note I mostly use this in tests, where I want to verify things, but depending on your use case, you could put the parameter names in the method name itself

Example:

  private void verifyCountsForUnprocessedSuccessFailed(int expectedNotProcessedCount, int expectedSuccessCount, int expectedFailedCount) {
    ....
    assertEquals(expectedFailedCount, actualFailedCount);
    assertEquals(expectedSuccessCount, actualSuccessCount);
    assertEquals(expectedNotProcessedCount, actualNotProcessedCount);
  }

or, you can add extra parameters to your method, one before each parameter, to "name" them, e.g.

  private void verifyJobCounts(ParamTypeEnum state1, int count1, ParamTypeEnum state2, int count2, ParamTypeEnum state3, int count3) {
    ...

    assertEquals(count1, actualCount1);
    assertEquals(count2, actualCount2);
    assertEquals(count3, actualCount3);
  }

e.g.

verifyJobCounts(SUCCESS, 0, NOT_PROCESSED, 0, FAILED, 0);
奢欲 2024-08-23 22:01:26

@irreputable 提出了一个很好的解决方案。但是 - 它可能会使您的类实例处于无效状态,因为不会发生验证和一致性检查。因此,我更喜欢将其与构建器解决方案结合起来,避免创建额外的子类,尽管它仍然会子类化构建器类。此外,由于额外的构建器类使其更加冗长,因此我使用 lambda 添加了另一种方法。为了完整性,我添加了一些其他构建器方法。

从一个类开始,如下所示:

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 如何支持命名参数?

嗯——足够研究了,也许是一个新问题。

@irreputable came up with a nice solution. However - it might leave your Class instance in a invalid state, as no validation and consistency checking will happen. Hence I prefer to combine this with the Builder solution, avoiding the extra subclass to be created, although it would still subclass the builder class. Additionally, because the extra builder class makes it more verbose, I added one more method using a lambda. I added some of the other builder approaches for completeness.

Starting with a class as follows:

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

}

Then using this applying the different methods:

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文