Reputation: 21434
Let's say Product
is in a Java library that I can't tweak, so to instantiate it by calling setters:
val product = new Product
product.setName("Cute Umbrella")
product.setSku("SXO-2")
product.setQuantity(5)
I'd prefer to be able to do something like this:
val product = new Product {
_.setName("Cute Umbrella")
_.setSku("SXO-2")
_.setQuantity(5)
}
or better yet:
val product =
new Product(name -> "Cute Umbrella", sku -> "SXO-2", quantity -> 5)
Is something like this possible with Scala ?
Upvotes: 4
Views: 234
Reputation: 18177
I'd write an implicit conversion to use Apache Commons BeanUtils
import org.apache.commons.beanutils.BeanUtils
implicit def any2WithProperties[T](o: T) = new AnyRef {
def withProperties(props: Pair[String, Any]*) = {
for ((key, value) <- props) { BeanUtils.setProperty(o, key, value) }
o
}
}
test("withProperties") {
val l = new JLabel().withProperties("background" -> Color.RED, "text" -> "banana")
l.getBackground should be (Color.RED)
l.getText should be ("banana")
}
You don't get property name or type checking at compile time, but it is very close to the syntax that you wanted, and is generally very useful in creating eg testdata.
Or taking a cue from @retronym's function approach
implicit def any2WithInitialisation[T](o: T) = new AnyRef {
def withInitialisation(fs: (T =>Unit)*) = {
fs.foreach(_(o))
o
}
}
test("withInitialisation") {
val l = new JLabel().withInitialisation(
_.setBackground(Color.RED),
_.setText("banana")
)
l.getBackground should be (Color.RED)
l.getText should be ("banana")
}
Upvotes: 1
Reputation: 16905
You could create a Product object as a factory, and call that in your scala. Something like this:
object Product {
def apply(name: String, sku: String, quantity: Int) = {
val newProd = new Product()
import newProd._
setName(name)
setSku(sku)
setQuantity(quantity)
newProd
}
and then you can use it exactly as you wanted to (without the new).
val product = Product(name = "Cute Umbrella", sku = "SXO-2", quantity = 5)
(apologies if the above doesn't compile. Don't have access to Scala at work :(
Upvotes: 6
Reputation: 105258
For the sake of completeness if you ever need to go the other way around, reading properties in a javabean style from scala classes, you can use @BeanProperty
and @BooleanBeanProperty
annotations:
class MyClass(@BeanProperty foo : String, @BooleanBeanProperty bar : Boolean);
And then from java:
MyClass clazz = new MyClass("foo", true);
assert(clazz.getFoo().equals("foo"));
assert(clazz.isBar());
Upvotes: 1
Reputation: 55028
You can import the setters so you don't need to qualify the calls:
val product = {
val p = new Product
import p._
setName("Cute Umbrella")
setSku("SXO-2")
setQuantity(5)
p
}
If Product
isn't final, you could also anonymously subclass it and call the setters in the constructor:
val product = new Product {
setName("Cute Umbrella")
setSku("SXO-2")
setQuantity(5)
}
As soon as you try to instantiate with a Map of property names and values, you lose static type checking, as you resort to reflection. Libraries like Apache Commons BeanUtils can help out, if you still want to go down that path.
Yet another approach is to pass a sequence of anonymous functions to call each setter, and write a utility method to apply them to the object
def initializing[A](a: A)(fs: (A => Unit)*) = { fs.foreach(_(a)); a }
initializing(new Product)(
_.setName("Cute Umbrella"),
_.setSku("SXO-2"),
_.setQuantity(5)
)
Upvotes: 6