Каков самый лаконичный способ создания/создания объектов JavaBean в Scala?

Предположим, что Product находится в библиотеке Java, которую я не могу настроить, поэтому для ее создания путем вызова сеттеров:

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?


person Hendy Irawan    schedule 31.10.2011    source источник
comment
См. также stackoverflow.com/q/7455681/97777.   -  person Duncan McGregor    schedule 31.10.2011


Ответы (4)


Я бы написал неявное преобразование для использования 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")
  }

Вы не получаете имя свойства или проверку типа во время компиляции, но это очень близко к желаемому синтаксису и, как правило, очень полезно при создании, например, тестовых данных.


Или, взяв пример функционального подхода @retrony

  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")
  }
person Duncan McGregor    schedule 31.10.2011
comment
(разглагольствование) Вы не получаете никакого имени свойства или проверки типа во время компиляции - это то, что меня действительно раздражает в менталитете java beans. У нас есть язык со статической типизацией, который может отловить для вас множество глупых ошибок, но давайте из кожи вон лезем, чтобы помешать встроенной безопасности этого языка. Кроме того, давайте просто будем использовать String практически для всего. ಠ_ಠ (/напыщенная речь) - person Dan Burton; 01.11.2011
comment
:-) Но есть несколько языков, которым удалось занять нишу только с проверками во время выполнения. Лично мой тестовый код Java и Scala полон таких маленьких хаков, потому что я знаю, что тесты не пройдут, если они неверны, и ценю выразительность. - person Duncan McGregor; 01.11.2011
comment
Хотя я думаю, что потеря проверки типов — это плохо, в моем конкретном случае это кажется неизбежным компромиссом, чтобы получить то, что я хочу. И ваш код нужно написать только один раз, и он будет работать для любого класса. Спасибо! - person Hendy Irawan; 12.11.2011
comment
Обратите внимание, что второй из этих подходов является типобезопасным. - person Duncan McGregor; 13.11.2011

Вы можете импортировать сеттеры, поэтому вам не нужно квалифицировать вызовы:

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

Как только вы попытаетесь создать экземпляр с помощью Map имен и значений свойств, вы потеряете проверку статического типа, поскольку прибегаете к отражению. Библиотеки, такие как Apache Commons BeanUtils, могут помочь, если вы все еще хотите пойти по этому пути.

Еще один подход состоит в том, чтобы передать последовательность анонимных функций для вызова каждого установщика и написать служебный метод для применения их к объекту.

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

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

Вы можете создать объект 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
}

и тогда вы можете использовать его именно так, как вы хотели (без нового).

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

(извините, если вышеприведенное не скомпилируется. У меня нет доступа к Scala на работе :(

person Mikezx6r    schedule 31.10.2011
comment
Я думаю, вам нужно =, а не ->, но это хорошее решение - person Luigi Plinge; 31.10.2011
comment
@Luigi Вот что я получаю за копирование кода OP;) Спасибо за исправление, и я отредактировал свой ответ. - person Mikezx6r; 31.10.2011
comment
Это означает, что я должен создать фабрику для каждого класса... Потенциально могут быть десятки или сотни классов... (это все еще сохраняет избыточность, но я надеялся, что есть лучший способ, т.е. магический способ Scala) - person Hendy Irawan; 12.11.2011
comment
@ Хенди: Правильно. Я не знал, что вы ищете такое общее решение. Лично я бы выбрал решение ретронима, а не то, которое вы выбрали. В обоих случаях ясно, что происходит, нет дополнительного ввода/создания класса, и компилятор сообщит вам, если вы ошиблись типом или свойство больше не существует... - person Mikezx6r; 14.11.2011

Для полноты, если вам когда-нибудь понадобится пойти наоборот, читая свойства в стиле javabean из классов scala, вы можете использовать аннотации @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());
person Pablo Fernandez    schedule 31.10.2011