命名参数

时间:2012-12-22 13:36:35

标签: groovy named-parameters

我有方法

def test(String a, String b) { }

我想用动态参数图调用它。 我总是那样

test(['1','2']); //valid call

以及

test([a:'1',b:'2']); //=> does not work

会奏效。但事实并非如此。所以我记得the spread operator,但无法让它发挥作用......

有没有办法用某种地图作为参数而不是单个参数调用上面的方法?

6 个答案:

答案 0 :(得分:29)

方法调用不应该是test(a:'1', b:'2');而不是test([a:'1',b:'2']);吗?

请检查命名参数 here

答案 1 :(得分:27)

也许我错过了什么,但我认为Groovy现在没有命名参数。有discussionsproposals,但我不知道任何正式的内容。

对于您的情况,我认为地图传播可能有帮助,但并非在每种情况下都有帮助。获取值后,它遵循声明映射值的顺序:

def test(String a, String b) { "a=$a, b=$b" }
def test(Map m) { test m*.value }

assert test(a: "aa", b:"bb") == "a=aa, b=bb"
assert test(b: "aa", a:"bb") != "a=aa, b=bb" // should be false :-(
assert test(b: "ccc", a:"ddd") == "a=ddd, b=ccc" // should have worked :-(

对于课程,我可以建议Groovy's as operator吗?

@groovy.transform.CompileStatic
class Spread {
  class Person {
    String name
    BigDecimal height
  }

  def method(Person p) {
    "Name: ${p.name}, height: ${p.height}"
  }

  def method(Map m) { method m as Person }

  static main(String[] args) {
    assert new Spread().method(name: "John", height: 1.80) == 
      "Name: John, height: 1.80"
  }
}

答案 2 :(得分:9)

命名参数支持非常灵活,但文档有点薄。以下是我发现的一些规则。 请注意,我试图在参数的用户(在方法中声明)和args(传递给方法调用)中明确无误

  • 首先必须声明Map参数。 这是一个很大的问题。并不明显。
  • 您的args中不需要完整的Map,只需要Map元素 即(a: "aa")足够好,您不需要([a: "aa"])
  • 您可以将有序(未命名)的args与名称args混合使用 因为有序的args保持与参数相同的顺序 他们填写
  • 您可以使用常规有序args散布命名args。 这很酷,但是有序的args必须在, 好吧,订购。
  • 您甚至可以使用相同的可选订购参数 方法签名(参见下面示例中的x
  • 您可以为Map参数指定一个默认的空地图args=[:] 命名args可选,但是如果你有这个效果不好 其他可选参数(见下面的最后一个例子)

以下是一些例子: 这些参数不需要输入,但为了清晰起见我添加了类型。

// this method has a map args to capture all named args
// and non-named (ordered) args String s, int n, and int x
// x has a default value so is optional
// the map (here untyped) to capture the nameed args MUST COME FIRST
def m(Map args=[:], String s, int n, int x=1)
{
    println "s:$s n:$n x:$x, args:$args"
}

//1: pass in named args first, then ordered
m(a: "aa", b: 3, "ss", 44, 5) // s:s n:44 x:5, args:[a:aa, b:3]

//2:  ordered args first -  named args last (same result)
m("ss", 44, 5, a: "aa", b: 3) // s:s n:44 x:5, args:[a:aa, b:3]

//3:  bring the first ordered arg (s) to the start (same result)
m("ss", a: "aa", b: 3, 44, 5) // s:s n:44 x:5, args:[a:aa, b:3]

//4: stick the ordered arg n in the middle of the named args (same result!)
m("ss", a: "aa", 44, b: 3, 5) // s:s n:44 x:5, args:[a:aa, b:3]


//5:  mix the ordered args in with the named and SKIP the arg x with default value (x=1)
m(a: "aa", "ss", b: 3, 44) // s:ss n:44 x:1, args:[a:aa, b:3] 

//6: ordered arg n first - so in the wrong order (Fail!)
//m(44, "ss", a: "aa", b: 3, 5) //MissingMethodException: No signature .. of .. m() .. applicable for 
                             // argument types: (java.util.LinkedHashMap, java.lang.Integer, java.lang.String, java.lang.Integer)
                             // values: [[a:aa, b:3], 44, ss, 5]

//7:  no named args: Fails! (change signature to add default: Map args=[:] and it will succeed with: s:ss n:44 x:1, args:[:]
m("ss", 44) // ...No signature ... applicaple ... types (java.lang.String, java.lang.Integer)

//8: no named args: Fails! (even with default map in signature this fails!)
m("ss", 44, 5) // ...No signature ... applicaple ... types (java.lang.String, java.lang.Integer, java.lang.Integer)

答案 3 :(得分:6)

感谢Will P的评论,我找到了一个适合我的问题的解决方案:

如果我定义一个没有类型的参数,我可以传入各种类型,包括hashMaps。而groovy将a:'h',b:'i'之类的构造自动转换为hashmap

def test(myParams, Integer i) {
    return myParams.a + myParams.b
}

assert test(a:'h',b:'i',5) == test(b:'i',a:'h',5)
assert test([a:'h',b:'i'],5) == test(b:'i',a:'h',5)
test('h','i',5); //still throws an exception

这样,我可以使用单个命名参数,但也可以使用Map!

答案 4 :(得分:1)

这个问题让我开始思考,我想出了一个灵活,有趣(即使不是很恐怖)的解决方案。

此签名似乎绝对接受参数的任意组合:

f(Map m=null, Object... obj)

它与它们一起做可预测的事情。

  • 如果您在任何位置(任何位置)都传入任何命名参数,它们将进入m
  • 如果您传递位置参数,它们将以正确的顺序进入obj。
  • 如果您传递地图,则将其视为位置参数
  • 如果您没有任何命名参数,则m为null(除非您将地图作为第一个参数,请参见下文)

唯一令人烦恼的是将地图作为第一个位置参数传递的情况。

f([a:1], 2) has a map m[a:1] and one obj[0]=2

但是

 f([a:1], b:2)

具有[b:2]的映射m和obj [0] =映射[a:1],因此您将无法确定映射m是位置映射还是命名参数

顺便说一句,我不必推荐任何一种方法,我会尽可能使用精确的参数(甚至我更喜欢显式类型)。在我自己的代码中,有好几次我都添加了一些显式类型,使我有些困惑并解决了问题,但是在某些情况下,这可能非常有用。

答案 5 :(得分:0)

我绝对讨厌groovy如何处理位置和命名/默认参数。它是可怕的。 Python毫无疑问地做到了。

问题

  1. 使用参数名称调用函数实际上会创建一个映射,并使该映射成为第一个参数。

代码

test(a:"a", b: "b") // Actual myfunc([a: "a", b: "b"])
test("a", b: "b")  // Actual myfunc([b: "b"], "a")
test(a: "a", "b")  // Actual myfunc([a: "a"], "b")

这很糟糕,因为它实际上会改变位置参数的顺序。

  1. 正常的默认参数不能乱序调用

代码

def test(String a, String b, int x=1, int y=2){
  a = args.get('a', a)
  b = args.get('b', b)
  x = args.get('x', x)
  y = args.get('y', y)

  println "a:$a b:$b x:$x, y:$y"
}

test("a", 'b')  // Positional arguments without giving the default values
// "a:a b:b x:1 y:2"

test("a", "b", 3)  // Positional arguments with giving 1 default and not the last
// "a:a b:b x:3 y:2"

test("a", "b", y:4)  // Positional with Keyword arguments. Actual call test([y:4], "a", "b")
// This fails!? No signature of method, because Map is the first argument

当然,您始终可以覆盖该函数以使参数与所需位置匹配。当您有很多争论时,这只是一个巨大的麻烦。

  1. 使用地图作为第一个参数不允许使用纯位置参数

代码

def test1(Map args=[:], String a, String b, int x=1, int y=2){
  a = args.get('a', a)
  b = args.get('b', b)
  x = args.get('x', x)
  y = args.get('y', y)

  println "test1(a:$a b:$b x:$x, y:$y, args:$args)"
}

test1("ss", "44", 5, c: "c", d: 3)  // Actual test2([c: "c", d: 3], "ss", "44", 5) Matches our definition
// test1(a:ss b:44 x:5, y:2, args:[c:c, d:3, a:ss, b:44, x:5, y:2])

test1(a: "aa", b: 3, "ss", "44", 5)  // Actual test2([a: "aa", b: 3], "ss", "44", 5) Nothing wrong with repeat parameters because they are in the map
// test1(a:aa b:3 x:5, y:2, args:[a:aa, b:3, x:5, y:2])

test1(a: "aa", b: 3, "ss", "44", y:5)  // Actual test2([a: "aa", b: 3, y:5], "ss", "44") y is in the map, so y still has the default positional value
// test1(a:aa b:3 x:1, y:5, args:[a:aa, b:3, y:5, x:1])

test1("ss", "44", y:3)  // Actual test2([y:3], "ss", "44")
// test1(a:ss b:44 x:1, y:3, args:[y:3, a:ss, b:44, x:1])

test1('a', 'b')  // Pure positional arguments only required arguments given (no defaults given)
// test1(a:a b:b x:1, y:2, args:[a:a, b:b, x:1, y:2])

test1("ss", "44", 5)  // Pure positional arguments one missing
// This fails!? No signature of method. Why?

test1("ss", "44", 5, 6)  // Pure positional arguments all arguments given
// This fails!? No signature of method. Why?

我的解决方案...

最终我的解决方案是将任意数量的参数作为对象,并使用已定义的参数映射来映射这些参数。

代码

// Return a Map of arguments with default values. Error if argument is null
def mapArgs(Object args, Map m){
  Map check = [:]
  def offset = 0

  // Check if first argument is map and set values
  if (args[0] instanceof Map){
    check = args[0]
    offset += 1
    check.each{ subitem ->
      m[subitem.key] = subitem.value
    }
  }

  // Iter positional arguments. Do not replace mapped values as they are primary.
  m.eachWithIndex{ item, i ->
    m[item.key] = ((i + offset) < args.size() && !check.containsKey(item.key)) ? args[i + offset] : item.value
    if (m[item.key] == null){
      throw new IllegalArgumentException("Required positional argument ${item.key}")
    }
  }
  return m
}

def test2(Object... args) {
  // println "args $args"
  def m = mapArgs(args, [a: null, b: null, x: 1, y:2])
  println "test2(a:$m.a b:$m.b x:$m.x, y:$m.y, args:null)"
}

test2("ss", "44", 5, c: "c", d: 3)  // Actual test2([c: "c", d: 3], "ss", "44", 5) Matches our definition
// test1(a:ss b:44 x:5, y:2, args:[c:c, d:3, a:ss, b:44, x:5, y:2])
// test2(a:ss b:44 x:5, y:2, args:null)

test2(a: "aa", b: 3, "ss", "44", 5)  // Actual test2([a: "aa", b: 3], "ss", "44", 5) Nothing wrong with repeat parameters because they are in the map
// test1(a:aa b:3 x:5, y:2, args:[a:aa, b:3, x:5, y:2])
// test2(a:aa b:3 x:5, y:2, args:null)

test2(a: "aa", b: 3, "ss", "44", y:5)  // Actual test2([a: "aa", b: 3, y:5], "ss", "44") y is in the map, so y still has the default positional value
// test1(a:aa b:3 x:1, y:5, args:[a:aa, b:3, y:5, x:1])
// test2(a:aa b:3 x:1, y:5, args:null)

test2("ss", "44", y:3)  // Actual test2([y:3], "ss", "44")
// test1(a:ss b:44 x:1, y:3, args:[y:3, a:ss, b:44, x:1])
// test2(a:ss b:44 x:1, y:3, args:null)

test2('a', 'b')  // Pure positional arguments only required arguments given (no defaults given)
// test1(a:a b:b x:1, y:2, args:[a:a, b:b, x:1, y:2])
// test2(a:a b:b x:1, y:2, args:null)

test2("ss", "44", 5)  // Pure positional arguments one missing
// This fails!? No signature of method. Why?
// test2(a:ss b:44 x:5, y:2, args:null)

test2("ss", "44", 5, 6)  // Pure positional arguments all arguments given
// This fails!? No signature of method. Why?
// test2(a:ss b:44 x:5, y:6, args:null)

我对这种解决方案并不满意,但是它使关键字参数可以满足我的需求。