问题
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.
Thanks.
回答1:
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:
- 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.
- 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).
回答2:
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.
回答3:
You could also try to follow advise from here: http://www.artima.com/weblogs/viewpost.jsp?thread=118828
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.
回答4:
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)
回答5:
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));
回答6:
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, not hard to understand, specially if you have handled other languages.
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());
}
}
回答7:
Here's a little variation of the technique given in Joshua Bloch's Effective Java. Here I have made an attempt to make the client code more readable (or perhaps more DSLish).
/**
* 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();
}
}
Please note that with this variation, you can also give meaningful names to your pseudo-constructors.
回答8:
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.
回答9:
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");
回答10:
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
回答11:
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
回答12:
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; }
}
回答13:
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();
回答14:
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);
回答15:
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");
}
}
回答16:
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();
}
}
回答17:
@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.
来源:https://stackoverflow.com/questions/1988016/named-parameter-idiom-in-java