Как реализовать именованный Именованный Параметр в Java? (особенно для конструкторов)
Я ищу синтаксис Objective-C, а не тот, который используется в JavaBeans.
Небольшой пример кода будет в порядке.
Спасибо.
Как реализовать именованный Именованный Параметр в Java? (особенно для конструкторов)
Я ищу синтаксис Objective-C, а не тот, который используется в JavaBeans.
Небольшой пример кода будет в порядке.
Спасибо.
Лучшая Java-идиома, которая, как мне кажется, для моделирования аргументов ключевого слова в конструкторах - это шаблон Builder, описанный в Эффективный Java 2nd Edition.
Основная идея состоит в том, чтобы иметь класс Builder, который имеет сеттеры (но обычно не геттеры) для разных параметров конструктора. Также существует метод build()
. Класс Builder часто является (статическим) вложенным классом класса, который он использовал для сборки. Конструктор внешнего класса часто является частным.
Конечный результат выглядит примерно так:
public class Foo {
public static class Builder {
public Foo build() {
return new Foo(this);
}
public Builder setSize(int size) {
this.size = size;
return this;
}
public Builder setColor(Color color) {
this.color = color;
return this;
}
public Builder setName(String name) {
this.name = name;
return this;
}
// you can set defaults for these here
private int size;
private Color color;
private String name;
}
public static Builder builder() {
return new Builder();
}
private Foo(Builder builder) {
size = builder.size;
color = builder.color;
name = builder.name;
}
private final int size;
private final Color color;
private final String name;
// The rest of Foo goes here...
}
Чтобы создать экземпляр Foo, вы тогда напишите что-то вроде:
Foo foo = Foo.builder()
.setColor(red)
.setName("Fred")
.setSize(42)
.build();
Основные оговорки:
Вы также можете проверить этот пост в блоге (не я).
Это стоит упомянуть:
Foo foo = new Foo() {{
color = red;
name = "Fred";
size = 42;
}};
так называемый инициализатор с двойной привязкой. Это фактически анонимный класс с инициализатором экземпляра.
Вы также можете попробовать следовать совету: http://www.artima.com/weblogs/viewpost.jsp?thread=118828
int value; int location; boolean overwrite;
doIt(value=13, location=47, overwrite=true);
Он многословный на сайте вызова, но в целом дает наименьшие издержки.
Стиль 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);
}
}
Если вы используете 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));
Здесь немного вариации техники, приведенной в Joshua Bloch Effective Java. Здесь я попытался сделать код клиента более читаемым (или, возможно, больше 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();
}
}
Обратите внимание, что с этим вариантом вы также можете дать значимые имена вашим псевдоконструкторам.
Java не поддерживает Objective-C -именованные параметры для конструкторов или аргументов метода. Кроме того, на самом деле это не Java-способ делать что-то. В java типичный шаблон содержит многословные классы и члены. Классы и переменные должны быть существительными, а имя метода должно быть глаголом. Я предполагаю, что вы можете проявить творческий подход и отклониться от соглашений об именах Java и эмулировать парадигму Objective-C хакерским способом, но это не будет особенно полезно среднему разработчику Java, которому поручено поддерживать ваш код. При работе на любом языке вам следует придерживаться конвенций языка и сообщества, особенно при работе в команде.
Что насчет
public class Tiger {
String myColor;
int myLegs;
public Tiger color(String s)
{
myColor = s;
return this;
}
public Tiger legs(int i)
{
myLegs = i;
return this;
}
}
Tiger t = new Tiger().legs(4).color("striped");
Вы можете использовать обычный конструктор и статические методы, которые дают аргументам имя:
public class Something {
String name;
int size;
float weight;
public Something(String name, int size, float weight) {
this.name = name;
this.size = size;
this.weight = weight;
}
public static String name(String name) {
return name;
}
public static int size(int size) {
return size;
}
public float weight(float weight) {
return weight;
}
}
Использование:
import static Something.*;
Something s = new Something(name("pen"), size(20), weight(8.2));
Ограничения по сравнению с реальными именованными параметрами:
/*name*/ "pen", /*size*/ 20, /*weight*/ 8.2)
)Если у вас есть выбор, посмотрите Scala 2.8. http://www.scala-lang.org/node/2075
Я хотел бы указать, что в этом стиле используются как именованный параметр, так и свойства без get и , который имеет другой язык. Он не является обычным в Java-сфере, но его проще, не трудно понять, особенно если вы занимались другими языками.
public class Person {
String name;
int age;
// name property
// getter
public String name() { return name; }
// setter
public Person name(String val) {
name = val;
return this;
}
// age property
// getter
public int age() { return age; }
// setter
public Person age(int val) {
age = val;
return this;
}
public static void main(String[] args) {
// Addresses named parameter
Person jacobi = new Person().name("Jacobi").age(3);
// Addresses property style
System.out.println(jacobi.name());
System.out.println(jacobi.age());
//...
jacobi.name("Lemuel Jacobi");
jacobi.age(4);
System.out.println(jacobi.name());
System.out.println(jacobi.age());
}
}
Идиома, поддерживаемая 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");
}
}
Это вариант шаблона Builder
, описанный Лоуренсом выше.
Я очень много использую это (в подходящих местах).
Основное различие заключается в том, что в этом случае Builder является immuatable. Это имеет то преимущество, что его можно повторно использовать и поточно-безопасным.
Итак, вы можете использовать это, чтобы создать один по умолчанию 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();
}
}
Используя Java 8 lambdas, вы можете приблизиться к реальным именованным параметрам.
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));
}
}
Плюсы:
Минусы:
Любое решение в Java, вероятно, будет довольно многословным, но стоит упомянуть, что такие инструменты, как Google AutoValues и Immutables будет генерировать классы-конструкторы для автоматического использования обработки аннотации компиляции JDK.
В моем случае я хотел, чтобы именованные параметры использовались в перечислении Java, поэтому шаблон компоновщика не работал, потому что экземпляры enum не могут быть созданы другими классами. Я придумал подход, похожий на @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; }
}