Добавление настраиваемого конструктора без потери конструктора карты по умолчанию

По умолчанию каждый класс Groovy имеет конструктор Map, например

class Foo {
  def a
  def b
}

// this works
new Foo(a: '1', b: '2')

Однако, кажется, что, как только вы добавите собственный конструктор, этот конструктор по умолчанию недоступен

class Foo {

  Foo(Integer x) {
    println 'my constructor was called'  
  }

  def a
  def b
}

// this works
new Foo(1)

// now this doesn't work, I get the error: groovy.lang.GroovyRuntimeException: 
// failed to invoke constructor
new Foo(a: '1', b: '2')

Можно ли добавить свой собственный конструктор без потери конструктора карты по умолчанию? Я пробовал аннотировать класс с помощью @TupleConstructor, но это не имело никакого значения. Я понимаю, что я мог бы добавить конструктор карты сам, например.

public Foo(Map map) {    
  map?.each { k, v -> this[k] = v }  
}

Хотя конструктор выше не идентичен конструктору карты по умолчанию, потому что ключ на карте, который не имеет соответствующего свойства в классе, вызовет исключение.

Ответ 1

Вы можете использовать аннотацию @InheritConstructors.

@groovy.transform.InheritConstructors
class Foo {
    def a, b
    Foo(Integer x) {
        println 'my constructor was called'
    }
}

// this works
new Foo(1)

def mappedFoo = new Foo(a: '1', b: '1')
assert mappedFoo.a == '1'
assert mappedFoo.b == '1'

Ответ 2

После компиляции конструктор карты Groovy преобразуется в создание объекта с использованием пустого конструктора плюс набор настроек (в стиле javabean). Наличие пустого конструктора решает проблему:

class Foo {

  Foo(Integer x) {
    println 'my constructor was called'  
  }

  Foo() {}

  def a
  def b
}

new Foo(1)

foo = new Foo(a: '1', b: '2')

assert foo.a == "1"

Ответ 3

Добавьте no-arg ctor и вызовите super, например,

class Foo {
  Foo(Integer x) {
    println 'my constructor was called'  
  }

  Foo() { super() } // Or just Foo() {}

  def a
  def b
}

f = new Foo(a: '1', b: '2')
println f.a
=> 1