Groovy named parameters cause parameter assignments to switch--any way around this?

StackOverflow https://stackoverflow.com/questions/15393962

  •  23-03-2022
  •  | 
  •  

Pregunta

Groovy will collect all the named parameters into a map and pass it into a method as the first parameter. This seems neat, but after trying to make it work it seems really unusable.

So the issue is a method like this:

def method(paramMap, specificVar1 = 7, specificVar2 = 14)

when you call this method with something like this:

method(12, extraValue: "hello")

you get pretty much what you expect:

assert 12 == specificVar1
assert 14 == specificVar2
assert [extraValue:"hello"] == paramMap

not bad, makes sense. The problem is, if you assume the Map params are optional then you can end up with values like this:

method(12)
assert paramMap == 12
assert specificVar1 == 7 // default values
assert specificVar2 == 14

That scalar should have gone into specificVar--not the map. If I specifically type the map in the method:

def method(Map paramMap, specificVar1 = 7, specificVar2 = 14)

then method(12, extraValue: "hello") works just as it did before, but method(12) throws a ClassCastException. This just doesn't seem usable. Is there any way to make that Map "sticky" so that it will simply be empty if there are no Map parameters?

¿Fue útil?

Solución

Setting default values on parameters create overloaded methods with combinations made from left to right, thus, it is hard to make method(12) and also be able to pass map entries.

Your method def method(paramMap, specificVar1=7, specificVar2=14) will generate the following methods:

Object Maps.method(java.lang.Object)
Object Maps.method(java.lang.Object,java.lang.Object)
Object Maps.method(java.lang.Object,java.lang.Object,java.lang.Object)

And a fully typed method with the map param:

def method3(Map paramMap=[:], Integer specificVar1=7, Integer specificVar2=14) {
}

Will generate the following methods:

Object Maps.method3()
Object Maps.method3(java.util.Map)
Object Maps.method3(java.util.Map,java.lang.Integer)
Object Maps.method3(java.util.Map,java.lang.Integer,java.lang.Integer)

(No suitable method for method(12)).

Also, entries passed to the method will be collected and inserted in the first map parameter. The following method:

def method4(Integer specificVar1=7, Integer specificVar2=14, Map map=[:]) {

Generates:

Object Maps.method4()
Object Maps.method4(java.lang.Integer)
Object Maps.method4(java.lang.Integer,java.lang.Integer)
Object Maps.method4(java.lang.Integer,java.lang.Integer,java.util.Map)

Thus, method4 12, a:'b' fails with:

No signature of method: Maps.method4() is applicable for argument types: 
  (java.util.LinkedHashMap, java.lang.Integer) values: [[a:b], 12]

So, no, i don't think you can do what you want using maps :-).


Solution 1:

If you are in for a pure dynamic solution, you can use a single map argument:

def method5(Map map) {
  def specificVar1 = map.specificVar1 ?: 7
  def specificVar2 = map.specificVar2 ?: 14
}

Solution 2 (updated):

You can create a class to represent the parameters. Using a map to be coerced into the object is statically compilable and a syntatic sugar for it.

@groovy.transform.CompileStatic
class Maps {
  def method6(Foo foo) { "$foo.params, $foo.specificVar1, $foo.specificVar2" }
  def method6(Map map) { method6 map as Foo }

  static main(args) {
    def maps = new Maps()

    assert maps.method6(params: [a: 'b', c: 'd'], specificVar1: 40) ==
        "[a:b, c:d], 40, 14"

    assert maps.method6(new Foo(params: [a: 'b', c: 'd'], specificVar2: 21)) == 
        "[a:b, c:d], 7, 21"
  }
}

class Foo {
  def specificVar1 = 7, specificVar2 = 14, params = [:]
}

Solution 3:

An overloaded method.

def method6(Map paramMap, Integer specificVar1=7, Integer specificVar2=14) {
  "$paramMap, $specificVar1, $specificVar2"
}

def method6(Integer specificVar1=7, Integer specificVar2=14) {
  method6 [:], specificVar1, specificVar2
}


assert method6( 12 ) == "[:], 12, 14"
assert method6( ) == "[:], 7, 14"
assert method6( a:'b', 18 ) == "[a:b], 18, 14"
assert method6( 18, a:'b', 27 ) == "[a:b], 18, 27"
assert method6( 90, 100 ) == "[:], 90, 100"
assert method6( a:'b', 140, c:'d' ) == "[a:b, c:d], 140, 14"

The map version method can't have a default parameter, otherwise both methods will generate a parameterless method6 and those will conflict.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top