在Scala中,构建JavaBean对象最简洁的方法是什么?

4

假设Product是一个Java库,我无法进行调整,因此需要通过调用setter方法来实例化:

val product = new Product
product.setName("Cute Umbrella")
product.setSku("SXO-2")
product.setQuantity(5)

我希望能够做这样的事情:

val product = new Product {
  _.setName("Cute Umbrella")
  _.setSku("SXO-2")
  _.setQuantity(5)
}

更好的方式是:
val product =
  new Product(name -> "Cute Umbrella", sku -> "SXO-2", quantity -> 5)

这种情况在Scala中是否有可能实现?

请参见https://dev59.com/GlvUa4cB1Zd3GeqPs19o。 - Duncan McGregor
4个回答

6
您可以导入setter,这样您就不需要限定调用:
val product = {
  val p = new Product
  import p._

  setName("Cute Umbrella")
  setSku("SXO-2")
  setQuantity(5)

  p
}

如果 Product 不是最终版本,你也可以匿名子类化它,并在构造函数中调用设置器:
val product = new Product {
  setName("Cute Umbrella")
  setSku("SXO-2")
  setQuantity(5)
}

当你尝试使用属性名和值的映射实例化对象时,你会失去静态类型检查,因为你需要使用反射。如果你仍然想按这条路走下去,像Apache Commons BeanUtils这样的库可以提供帮助。

另一种方法是传递一系列匿名函数来调用每个setter,并编写一个实用方法将它们应用到对象中。

def initializing[A](a: A)(fs: (A => Unit)*) = { fs.foreach(_(a)); a }

initializing(new Product)(
  _.setName("Cute Umbrella"),
  _.setSku("SXO-2"),
  _.setQuantity(5)
)

6
您可以创建一个Product对象作为工厂,并在Scala中调用它。就像这样:
object Product {
  def apply(name: String, sku: String, quantity: Int) = {
     val newProd = new Product()
     import newProd._
     setName(name)
     setSku(sku)
     setQuantity(quantity)
     newProd
}

然后您可以像之前一样使用它(不需要new关键字)。

   val product = Product(name = "Cute Umbrella", sku = "SXO-2", quantity = 5)

如果以上代码无法编译,请见谅。我在工作中没有访问Scala的权限 :(


(注:Scala是一种编程语言)


我认为你需要用=而不是->,但这是一个好的解决方案。 - Luigi Plinge
@Luigi 这就是我抄袭了原帖的代码后得到的结果 ;) 感谢您的纠正,我已经编辑了我的回复。 - Mikezx6r
这意味着我必须为每个类创建一个工厂... 可能会有数十个或数百个类...(它仍然可以节省冗余,但希望有一种“更好”的方式,即Scala魔法方式) - Hendy Irawan
@Hendy:没错。我没有意识到你在寻找这样一个通用的解决方案。就我个人而言,我会选择retronym的解决方案而不是你选择的那个。两种情况都很清楚地说明了正在发生的事情,没有额外的输入/类创建,并且编译器会告诉你是否有类型错误或属性不存在... - Mikezx6r

1
为了完整起见,如果您需要从Scala类以JavaBean的方式读取属性,则可以使用@BeanProperty和@BooleanBeanProperty注释:
class MyClass(@BeanProperty foo : String, @BooleanBeanProperty bar : Boolean);

然后从Java开始:

MyClass clazz = new MyClass("foo", true);

assert(clazz.getFoo().equals("foo"));
assert(clazz.isBar());

1
我会编写一个隐式转换,以使用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")
  }

在编译时,您无法获得属性名称或类型检查,但它非常接近您想要的语法,并且通常非常有用于创建例如测试数据。


或者借鉴@retronym的函数方法

  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")
  }

(抱怨)“编译时无法获取任何属性名称或类型检查” - 这就是我对Java Beans思维方式最烦恼的地方。我们有这种静态类型语言,可以为您捕获许多愚蠢的错误,但是,让我们屈服于阻挠该语言内置安全性。此外,让我们基本上为所有内容使用字符串。ಠ_ಠ(/抱怨) - Dan Burton
但是嘿,有一些语言成功地通过仅运行时检查来开辟了自己的市场。就我个人而言,我的Java和Scala测试代码充满了像这样的小技巧,因为我知道如果它们不正确,测试将失败,并且价值得到了表达。 - Duncan McGregor
虽然我认为失去类型检查是不好的,但在我的具体情况下,它似乎是一个不可避免的权衡,以获取我想要的东西。而且你的代码只需编写一次,就可以针对任何类进行操作。谢谢! - Hendy Irawan
请注意,这两种方法中第二种是类型安全的。 - Duncan McGregor

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接