¿Cómo implementar el idioma de parámetro con nombre en Java? (especialmente para constructores)
Estoy buscando una sintaxis similar a Objective-C y no como la que se usa en JavaBeans.
Un pequeño ejemplo de código estaría bien.
Gracias.
¿Cómo implementar el idioma de parámetro con nombre en Java? (especialmente para constructores)
Estoy buscando una sintaxis similar a Objective-C y no como la que se usa en JavaBeans.
Un pequeño ejemplo de código estaría bien.
Gracias.
Respuestas:
El mejor lenguaje de Java que he encontrado para simular argumentos de palabras clave en constructores es el patrón Builder, descrito en Effective Java 2nd Edition .
La idea básica es tener una clase Builder que tenga setters (pero generalmente no getters) para los diferentes parámetros del constructor. También hay un build()
método. La clase Builder es a menudo una clase anidada (estática) de la clase que se usa para construir. El constructor de la clase externa suele ser privado.
El resultado final se parece a lo siguiente:
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...
}
Para crear una instancia de Foo, escribe algo como:
Foo foo = Foo.builder()
.setColor(red)
.setName("Fred")
.setSize(42)
.build();
Las principales advertencias son:
También puede consultar esta publicación de blog (no de mí).
.withFoo
, en lugar de .setFoo
: en newBuilder().withSize(1).withName(1).build()
lugar denewBuilder().setSize(1).setName(1).build()
There's no compile-time checking that all of the parameters have been specified exactly once.
Este problema puede superarse devolviendo las interfaces Builder1
a BuilderN
donde cada una cubre uno de los establecedores o el build()
. Es mucho más detallado de codificar, pero viene con soporte de compilador para su DSL y hace que el autocompletado sea muy agradable para trabajar.
Vale la pena mencionar esto:
Foo foo = new Foo() {{
color = red;
name = "Fred";
size = 42;
}};
el llamado inicializador de doble abrazadera . En realidad, es una clase anónima con un inicializador de instancia.
También puede intentar seguir los consejos desde aquí: http://www.artima.com/weblogs/viewpost.jsp?thread=118828
int value; int location; boolean overwrite;
doIt(value=13, location=47, overwrite=true);
Es detallado en el sitio de la llamada, pero en general ofrece la sobrecarga más baja.
doIt( /*value*/ 13, /*location*/ 47, /*overwrite*/ true )
Estilo 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);
}
}
create()
me detuvo en seco. Nunca he visto ese estilo de encadenamiento lambda en Java. ¿Descubrió esta idea por primera vez en otro idioma con lambdas?
Java no admite parámetros con nombre tipo Objective-C para constructores o argumentos de método. Además, esta no es realmente la forma en que Java hace las cosas. En java, el patrón típico es clases y miembros con nombres prolijos. Las clases y variables deben ser sustantivos y los métodos nombrados deben ser verbos. Supongo que podría ser creativo y desviarse de las convenciones de nomenclatura de Java y emular el paradigma Objective-C de una manera hacky, pero esto no sería particularmente apreciado por el desarrollador promedio de Java encargado de mantener su código. Cuando trabajes en cualquier idioma, te conviene ceñirte a las convenciones del idioma y la comunidad, especialmente cuando trabajas en equipo.
Si está utilizando Java 6, puede utilizar los parámetros variables e importar estática para producir un resultado mucho mejor. Los detalles de esto se encuentran en:
http://zinzel.blogspot.com/2010/07/creating-methods-with-named-parameters.html
En resumen, podrías tener algo como:
go();
go(min(0));
go(min(0), max(100));
go(max(100), min(0));
go(prompt("Enter a value"), min(0), max(100));
Me gustaría señalar que este estilo aborda tanto el parámetro nombrado como las características de propiedades sin el prefijo get y set que tienen otros idiomas. No es convencional en el ámbito de Java pero es más simple, no es difícil de entender, especialmente si ha manejado otros lenguajes.
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());
}
}
Qué pasa
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");
Puede utilizar un constructor habitual y métodos estáticos que den un nombre a los argumentos:
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;
}
}
Uso:
import static Something.*;
Something s = new Something(name("pen"), size(20), weight(8.2));
Limitaciones en comparación con los parámetros con nombre reales:
/*name*/ "pen", /*size*/ 20, /*weight*/ 8.2)
)Si tiene la opción, mire Scala 2.8. http://www.scala-lang.org/node/2075
not really better than a comment
... por otro lado ...;)
Usando las lambdas de Java 8, puede acercarse aún más a los parámetros con nombre reales .
foo($ -> {$.foo = -10; $.bar = "hello"; $.array = new int[]{1, 2, 3, 4};});
Tenga en cuenta que esto probablemente viola un par de docenas de "mejores prácticas de Java" (como cualquier cosa que haga uso del $
símbolo).
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:
Contras:
$foo
nunca se escapa a la persona que llama (a menos que alguien lo asigne a una variable dentro de la devolución de llamada) entonces, ¿por qué no pueden ser públicos?
Puede usar la anotación @Builder del proyecto Lombok para simular parámetros con nombre en Java. Esto generará un constructor para usted que puede usar para crear nuevas instancias de cualquier clase (tanto las clases que ha escrito como las que provienen de bibliotecas externas).
Así es como habilitarlo en una clase:
@Getter
@Builder
public class User {
private final Long id;
private final String name;
}
Luego puede usar esto por:
User userInstance = User.builder()
.id(1L)
.name("joe")
.build();
Si desea crear un constructor de este tipo para una clase que proviene de una biblioteca, cree un método estático anotado como este:
class UserBuilder {
@Builder(builderMethodName = "builder")
public static LibraryUser newLibraryUser(Long id, String name) {
return new LibraryUser(id, name);
}
}
Esto generará un método llamado "constructor" que puede ser llamado por:
LibraryUser user = UserBuilder.builder()
.id(1L)
.name("joe")
.build();
Siento que la "solución de comentario" merece su propia respuesta (oculta en las respuestas existentes y mencionada en los comentarios aquí).
someMethod(/* width */ 1024, /* height */ 768);
Esta es una variante del Builder
Patrón descrito por Lawrence anteriormente.
Me encuentro usando esto mucho (en los lugares apropiados).
La principal diferencia es que en este caso el Constructor es inmutable . Esto tiene la ventaja de que se puede reutilizar y es seguro para subprocesos.
Entonces puedes usar esto para hacer un constructor predeterminado y luego, en los distintos lugares donde lo necesite, puede configurarlo y construir su objeto.
Esto tiene más sentido si está construyendo el mismo objeto una y otra vez, porque entonces puede hacer que el constructor sea estático y no tiene que preocuparse por cambiar su configuración.
Por otro lado, si tiene que construir objetos con parámetros cambiantes, esto tiene un poco de sobrecarga. (pero bueno, puede combinar la generación estática / dinámica con build
métodos personalizados )
Aquí está el código de ejemplo:
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();
}
}
Cualquier solución en Java es probable que va a ser bastante detallado, pero vale la pena mencionar que las herramientas como Google autovalores y inmutables generarán clases constructoras de forma automática utilizando JDK procesamiento de anotación de tiempo de compilación.
Para mi caso, quería que los parámetros con nombre se usaran en una enumeración de Java, por lo que un patrón de constructor no funcionaría porque otras clases no pueden crear instancias de enumeración. Se me ocurrió un enfoque similar a la respuesta de @ deamon, pero agrega la verificación en tiempo de compilación del orden de los parámetros (a expensas de más código)
Aquí está el código de cliente:
Person p = new Person( age(16), weight(100), heightInches(65) );
Y la implementación:
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; }
}
Vale la pena considerar el idioma admitido por la biblioteca karg :
class Example {
private static final Keyword<String> GREETING = Keyword.newKeyword();
private static final Keyword<String> NAME = Keyword.newKeyword();
public void greet(KeywordArgument...argArray) {
KeywordArguments args = KeywordArguments.of(argArray);
String greeting = GREETING.from(args, "Hello");
String name = NAME.from(args, "World");
System.out.println(String.format("%s, %s!", greeting, name));
}
public void sayHello() {
greet();
}
public void sayGoodbye() {
greet(GREETING.of("Goodbye");
}
public void campItUp() {
greet(NAME.of("Sailor");
}
}
R Casha
respuesta pero sin el código para explicarlo.
@irreputable se le ocurrió una buena solución. Sin embargo, podría dejar su instancia de Class en un estado no válido, ya que no se realizará ninguna validación ni verificación de coherencia. Por lo tanto, prefiero combinar esto con la solución Builder, evitando que se cree la subclase adicional, aunque todavía sería una subclase de la clase Builder. Además, debido a que la clase de constructor adicional lo hace más detallado, agregué un método más usando un lambda. Agregué algunos de los otros enfoques del constructor para completar.
Comenzando con una clase de la siguiente manera:
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; }
}
Luego, usando esto aplicando los diferentes métodos:
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";
}
);
Parece, en parte, una estafa total de lo que @LaurenceGonsalves ya publicó, pero verá la pequeña diferencia en la convención elegida.
Me pregunto, si JLS alguna vez implementara parámetros con nombre, ¿cómo lo harían? ¿Se extenderían a uno de los modismos existentes al proporcionar un soporte de forma abreviada para él? Además, ¿cómo admite Scala los parámetros con nombre?
Hmmm, suficiente para investigar y tal vez una nueva pregunta.