ChatGPT解决这个技术问题 Extra ChatGPT

Java中的命名参数习语

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

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

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


L
Laurence Gonsalves

我认为在构造函数中模拟关键字参数的最佳 Java 习语是 Builder 模式,在 Effective Java 2nd Edition 中进行了描述。

基本思想是拥有一个 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();

主要注意事项是:

设置模式非常冗长(如您所见)。可能不值得,除了您计划在许多地方实例化的类。没有编译时检查是否所有参数都已指定一次。您可以添加运行时检查,也可以仅将其用于可选参数,并将必需参数设为 Foo 或 Builder 的构造函数的普通参数。 (人们一般不会担心同一个参数被多次设置的情况。)

您可能还想查看 this blog post(不是我)。


这真的不是Objective-C那样的命名参数。这看起来更像是一个流畅的界面。这真的不是一回事。
我喜欢使用 .withFoo 而不是 .setFoonewBuilder().withSize(1).withName(1).build() 而不是 newBuilder().setSize(1).setName(1).build()
阿萨夫:是的,我知道。 Java 没有命名参数。这就是为什么我说这是“模拟关键字参数的最佳 Java 习语”。 Objective-C 的“命名参数”也不太理想,因为它们强制特定的顺序。它们不是像 Lisp 或 Python 那样的真正的关键字参数。至少对于 Java Builder 模式,您只需要记住名称,而不是顺序,就像真正的关键字参数一样。
notnoop:我更喜欢“set”,因为这些是改变 Builder 状态的 setter。是的,“with”在您将所有内容链接在一起的简单情况下看起来不错,但在更复杂的情况下,您将 Builder 放在自己的变量中(可能是因为您有条件地设置属性)我喜欢这个集合前缀清楚地表明,当调用这些方法时,Builder 正在发生变化。 “with”前缀对我来说听起来很实用,而这些方法显然不起作用。
There's no compile-time checking that all of the parameters have been specified exactly once. 这个问题可以通过返回接口 Builder1BuilderN 来解决,其中每个接口都包含一个 setter 或 build()。它的代码要冗长得多,但它为您的 DSL 提供了编译器支持,并且使自动完成非常好用。
u
user994998

值得一提的是:

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

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


有趣的技术,但似乎有点贵,因为每次我在代码中使用它时都会创建一个新类。
除了自动格式化、子类化和序列化警告之外,这实际上非常接近基于属性的初始化的 C# 语法。然而,从 4.0 开始的 C# 也有命名参数,所以程序员真的被宠坏了,不像 Java 程序员必须模拟成语以防止他们以后自责。
很高兴看到这是可能的,但我不得不投反对票,因为正如 Red Hyena 指出的那样,这种解决方案很昂贵。不能等到 Java 像 Python 那样真正支持命名参数。
点赞。这以最易读、最简洁的方式回答了这个问题。美好的。它“不高效”。我们在这里谈论多少额外的毫秒和比特?其中一个?十?不要误会我的意思——我不会使用它,因为我讨厌被一个冗长的 Java nut 执行(双关语;)
完美的!只需要公共/受保护的字段。这绝对是最好的解决方案,比 builder 造成的开销要少得多。 Hyena/Gattster:请 (1) 阅读 JLS 和 (2) 在写评论之前检查生成的字节码。
A
Alex

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 构造函数中一样)


好的。可惜它没有可变的参数顺序。 (但不是说我会用这个……)
这是一个绝妙的主意。 create() 的定义让我停下了脚步。我从未在 Java 中看到过这种风格的 lambda 链接。你第一次发现这个想法是用另一种语言的 lambdas 吗?
这称为柯里化:en.wikipedia.org/wiki/Currying。顺便说一句:也许这是一个聪明的主意,但我不推荐这种命名参数风格。我在一个带有许多参数的真实项目中对其进行了测试,这导致代码难以阅读和导航。
最终,Java 将被赋予 Visual Basic 风格的命名参数。 Java 以前没有,因为 C++ 没有。但我们最终会到达那里。我会说 90% 的 Java 多态性只是围绕可选参数进行修改。
r
rkj

您也可以尝试遵循此处的建议:http://www.artima.com/weblogs/viewpost.jsp?thread=118828

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

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


很好,因为开销很低,但感觉很hackish。对于有很多参数的情况,我可能会使用 Builder() 方法。
我认为这完全忽略了命名参数的意义。 (它具有将名称与值相关联的东西)。如果您颠倒订单,则没有任何指示whatsoever。我建议不要这样做,而是简单地添加评论:doIt( /*value*/ 13, /*location*/ 47, /*overwrite*/ true )
L
LEMUEL ADANE

我想指出,这种风格既解决了命名参数又解决了属性功能,而没有其他语言具有的 get 和 set 前缀。它在 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());
    }
}

R
R Casha

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

我喜欢它,但它仍然只解决了一半的问题。在 Java 中,如果不丢失对所需值的编译时检查,就无法防止意外转置参数。
没有类型安全,这比简单的//注释更糟糕。
A
Asaph

Java 不支持构造函数或方法参数的类似 Objective-C 的命名参数。此外,这确实不是 Java 的做事方式。在 java 中,典型的模式是详细命名的类和成员。类和变量应该是名词,命名的方法应该是动词。我想您可以发挥创造力并偏离 Java 命名约定并以一种 hacky 的方式模拟 Objective-C 范式,但是负责维护您的代码的普通 Java 开发人员不会特别欣赏这一点。在使用任何语言工作时,您都应该遵守语言和社区的约定,尤其是在团队工作时。


+1 - 有关坚持使用您当前使用的语言的成语的建议。请考虑需要阅读您的代码的其他人!
我赞成您的回答,因为我认为您的观点很好。如果我不得不猜测您为什么投反对票,那可能是因为这不能回答问题。问:“我如何在 Java 中做命名参数?”答:“你没有”
我投了反对票,因为我认为您的回答与问题无关。冗长的名称并不能真正解决参数排序问题。是的,您可以在名称中对它们进行编码,但这显然是不切实际的。提出一个不相关的范式并不能解释为什么不支持一个范式。
u
user564819

关于什么

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

Builder 要好得多,因为您可以检查 build() 上的一些约束。但我也更喜欢没有 set/with 前缀的较短参数。
此外,构建器模式更好,因为它允许您使构建的类(在本例中为 Tiger)不可变。
R
Reto Höhener

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

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

d
deamon

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

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


这种方法的一个缺点是您必须以正确的顺序获取参数。上面的代码可以让你写:Something s = new Something(name("pen"), size(20), size(21));此外,这种方法并不能帮助您避免输入可选参数。
我会赞成这个分析:not really better than a comment ...另一方面...;)
V
Vic

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

优点:

比我迄今为止看到的任何构建器模式都要短得多

适用于方法和构造函数

完全键入安全

它看起来非常接近其他编程语言中的实际命名参数

它与典型的构建器模式一样安全(可以多次设置参数)

缺点:

你的老板可能会为此对你处以私刑

很难说出发生了什么


缺点:字段是公开的,不是最终的。如果您对此感到满意,为什么不直接使用 setter 呢?它对方法有什么作用?
可以使用二传手,但那有什么意义呢?它只会使代码更长,并且会消除这样做的好处。赋值没有副作用,setter 是黑匣子。$foo 永远不会转义给调用者(除非有人确实将它赋值给回调内部的变量),那么为什么不能将它们公开?
I
Istvan Devai

您可以使用 Lombok 项目的 @Builder annotation 来模拟 Java 中的命名参数。这将为您生成一个构建器,您可以使用它来创建任何类的新实例(您编写的类和来自外部库的类)。

这是在类上启用它的方法:

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

之后,您可以通过以下方式使用它:

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

如果您想为来自库的类创建这样的 Builder,请创建一个带注释的静态方法,如下所示:

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

Google auto/value 有类似的目的,但使用注解处理框架,它比 Lombocks 字节码操作项目更安全(升级 JVM 后仍然可以工作)。
我认为与使用 Lombok 相比,Google 自动/价值需要多花一点时间。 Lombok 的方法或多或少与传统的JavaBean 编写兼容(例如,您可以通过new 实例化,字段在调试器中正确显示等)。我也不是 Lombok 使用的字节码操作 + IDE 插件解决方案的忠实粉丝,但我不得不承认,在实践中它工作正常。到目前为止,JDK 版本更改、反射等都没有问题。
是的,这是真的。对于 auto/value,您需要提供一个抽象类,然后将其实现。 Lombok 需要更少的代码。所以需要比较利弊。
S
Scheintod

这是上面 Lawrence 描述的 Builder 模式的变体。

我发现自己经常使用这个(在适当的地方)。

主要区别在于,在这种情况下,Builder 是不可变的。这样做的好处是可以重复使用并且是线程安全的。

因此,您可以使用它来创建一个默认 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();

    }
}

s
scott

Java 中的任何解决方案都可能非常冗长,但值得一提的是,Google AutoValuesImmutables 等工具将使用 JDK 编译时注释处理自动为您生成构建器类。

就我而言,我希望在 Java 枚举中使用命名参数,因此构建器模式不起作用,因为枚举实例不能被其他类实例化。我想出了一种类似于@deamon's answer的方法,但增加了参数排序的编译时检查(以更多代码为代价)

这是客户端代码:

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

B
Ben

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

这不能防止参数的双重分配

你不能有一个好的 .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 等参数化。由于方法本身必须编译,所以它调用的所有内容都必须编译。所以,一般来说,我们不能在类本身的方法中进行编译测试。

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


D
Dominic Fox

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

这似乎与 R Casha 答案基本相同,但没有解释它的代码。
N
Next Developer

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

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


m
mmarkholt

现在我们都在使用 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 参数(“它是大小优先还是距离......”)。


u
user8243552
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"),"%"); 
    }

Y
YoYo

@irreputable 想出了一个不错的解决方案。但是 - 它可能会使您的 Class 实例处于无效状态,因为不会发生验证和一致性检查。因此,我更喜欢将它与 Builder 解决方案结合起来,避免创建额外的子类,尽管它仍然是 builder 类的子类。此外,由于额外的构建器类使其更加冗长,我添加了一个使用 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 是如何支持命名参数的?

嗯 - 足以研究,也许是一个新问题。