Since double curly braces are generally avoided, you can create a very simple and generic sort of "builder" class that can set properties in a somewhat idiomatic way.
Note: I call the class "Bean" or POJO to follow the javabean standard: What is a JavaBean exactly?. I would primarily use this class to init javabeans anyway.
Bean.java
public class Bean<T> {
private T object;
public Bean(Supplier<T> supplier) { object = supplier.get(); }
public Bean(T object) { this.object = object; }
public T set(Consumer<T> setter) {
setter.accept(object);
return object;
}
}
Instances of this Bean class can be created from an existing object or generated using a Supplier. The object is stored in the the field object
. The set method is a higher-order function that takes in another function--Consumer<T>
. Consumers take in one argument and return void. This will create the setter side-effects in a new scope.
The Bean .set(...)
method returns object
that can be used directly in an assignment.
I like this method because the object's assignment are contained within closed blocks and it feels like I'm setting properties before the object is created rather than than creating the object and mutating it.
The end result is a decent way to create new java objects but this is still a little wordy from the C# object initializer sigh.
And here is the class in use:
// '{}' creates another scope so this function's scope is not "polluted"
// '$' is used as the identifier simply because it's short
Rectangle rectangle = new Bean<>(Rectangle::new).set($ -> {
$.setLocation(0, 0);
$.setBounds(0, 0, 0, 0);
// set other properties
});
if you have nested items, it might be a better to name the variables accordingly. Java doesn't let you use reuse $
because it exists in the outer scope and there is no shadowing.
// this time we pass in new Rectangle() instead of a Supplier
Rectangle rectangle3 = new Bean<>(new Rectangle()).set(rect-> {
rect.setLocation(-50, -20);
// setBounds overloads to take in another Rectangle
rect.setBounds(new Bean<>(Rectangle::new).set(innerRect -> {
innerRect.setLocation(0, 0);
innerRect.setSize(new Bean<>(Dimension::new).set(dim -> {
dim.setSize(640, 480);
}));
}));
});
now compare the normal code
// innerRect and dimension are part of the outer block's scope (bad)
Rectangle rectangle4 = new Rectangle();
rectangle4.setLocation(-50, -20);
Rectangle innerRect = new Rectangle();
innerRect.setLocation(0, 0);
Dimension dimension = new Dimension();
dimension.setSize(640, 480);
innerRect.setSize(dimension);
rectangle4.setBounds(innerRect);
Alternatively, you could have a lambda that takes in void and returns your object and cast it as a Supplier<DesiredType>
and immediately invoke .get()
. This doesn't require a separate class but you have to create bean yourself.
Rectangle rectangle5 = ((Supplier<Rectangle>)() -> {
Rectangle rect = new Rectangle();
rect.setLocation(0, 0);
return rect;
}).get();
A note on practicality: Because you can't reuse $
when nesting elements, this method still tends to be a bit wordy. The variable names start getting long and the any syntax appeal goes away.
It can also be easy to abuse the set() method to create instance of objects within the closure. To use correctly, the only side affects should be on the object you're creating.
One more note: this is really just for fun. Don't ever use this in production.