【问题标题】:Named parameters命名参数
【发布时间】:2012-12-09 19:24:40
【问题描述】:

我有办法

def test(String a, String b) { }

我想用动态参数映射来调用它。 我总是这样

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

还有

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

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

有没有办法以某种映射作为参数而不是单个参数来调用上述方法?

【问题讨论】:

  • 我现在不知道 groovy 支持命名参数...您的示例在我的 groovy-2.0.6 中不起作用
  • 你(部分)正确 - 我已经更新了我的问题。部分是因为我的示例是错误的,但是 groovy 支持命名参数。 mrhaki.blogspot.de/2009/09/… - 嘿,我想这就是我的问题的解决方案......

标签: groovy named-parameters


【解决方案1】:

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

请检查命名参数 here

【讨论】:

  • 但是如何在函数声明中定义参数的名称?
【解决方案2】:

也许我错过了一些东西,但我认为 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"
  }
}

【讨论】:

  • 太棒了!这正是我想要的,但我错过了valuein *.value!所以这是我原来问题的解决方案:def test(String a, String b) { }\n test([a:'1',b:'2']*.value);
  • 我不太确定,但我想这个答案已经过时了。请参阅@Tom 的答案。
  • 为了能够使用可选的命名参数调用方法,我最终使用了dedicated class for the optional parameters approach
  • 显然,*.value 方法仅在参数以正确的顺序放置时才有效,因为它显式忽略了指定映射中的键(*.value.collect { it.value } 相同 - 请参阅 Spread operator) .
【解决方案3】:

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

  • 必须首先声明 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)

【讨论】:

    【解决方案4】:

    感谢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!

    【讨论】:

    • 我以为你在寻找类似 python 命名参数(davedash.com/2007/12/21/python-named-arguments-pure-genius)的东西。很高兴你找到了解决方案,无论如何:-)
    • 无论好坏,使用此解决方案,如果对方法进行编码以识别不同类型, test('hi', 5) 也将起作用。它可能很有用——但它会让人困惑:) 语言越灵活,有趣和愚蠢的机会就越大。
    • 还要注意 test(5,a:'h',b:'i') 会工作,但 test(5,[a:'h',b:'i' ] ) 会不,如果你开始突破这些界限,你会得到奇怪的结果。最好按照预期的方式使用事物并避免滥用奇怪的边缘情况。考虑像使用纯 Java 和显式类型一样重载该方法。 (显式类型很棒,它们总是会告诉您预期的内容并使其实现,因此您不必猜测——groovy 对隐式类型的痴迷有点令人讨厌(不要误会我的意思,我喜欢 groovy,但是...... )。
    【解决方案5】:

    我非常讨厌 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. 使用 Map 作为第一个参数不允许使用纯位置参数

    代码

    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)
    

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

    【讨论】:

      【解决方案6】:

      这个问题引起了我的思考,我想出了一个灵活、有趣(如果不是吓人的话)的解决方案。

      这个签名似乎完全可以接受任何参数的组合:

      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 是位置地图还是命名参数.

      顺便说一句,我不一定推荐任何这些,我会尽可能使用精确的参数(我什至更喜欢显式类型)。在我自己的代码中有很多次,我通过添加一些显式类型来解决问题并解决问题,但在某些情况下这可能非常有用。

      【讨论】:

        猜你喜欢
        • 2011-03-21
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-07-26
        • 1970-01-01
        • 2022-01-04
        • 2017-02-21
        相关资源
        最近更新 更多