Dónal
Dónal

Reputation: 187399

adding a custom constructor without losing the default map constructor

By default every Groovy class has a Map constructor, e.g.

class Foo {
  def a
  def b
}

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

However, it seems that as soon as you add a constructor of your own, this default constructor is not available

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

Is it possible to add your own constructor without losing the default map constructor? I tried annotating the class with @TupleConstructor but it made no difference. I realise I could add the map constructor myself, e.g.

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

Though the constructor above is not identical to the default map constructor because a key in the map that does not have a corresponding property in the class will cause an exception.

Upvotes: 17

Views: 12586

Answers (3)

bdkosher
bdkosher

Reputation: 5883

If you are on Groovy 2.5 or later, you can apply the @MapConstructor annotation.

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

// this works
new Foo(1)

// the map constructor is present, too
def mappedFoo = new Foo(a: '1', b: '1')
assert mappedFoo.a == '1'
assert mappedFoo.b == '1'

If you're using an older version of Groovy, the @InheritConstructors annotation can be used as a substitute for @MapConstructor, but as Arie pointed out, avoid this approach if you're class extends some base class; if the base class lacks a no-arg constructor this will not work.

Upvotes: 22

Will
Will

Reputation: 14559

Upon compilation, Groovy's map constructor gets translated to an object creation using an empty constructor plus a bunch of setters (in a javabean style). Having an empty constructor solves the issue:

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"

Upvotes: 13

Dave Newton
Dave Newton

Reputation: 160311

Add a no-arg ctor and call super, e.g.,

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

Upvotes: 7

Related Questions