Hendy Irawan
Hendy Irawan

Reputation: 21434

What is the most concise way to construct/build JavaBean objects in Scala?

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

Answers (4)

Duncan McGregor
Duncan McGregor

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

Mikezx6r
Mikezx6r

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

Pablo Fernandez
Pablo Fernandez

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

retronym
retronym

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

Related Questions