【问题标题】:Scala default parameters and nullScala默认参数和null
【发布时间】:2018-07-16 15:04:29
【问题描述】:

我有这样的方法:

def aMethod(param: String = "asdf") = {
    ...
}

如果方法调用如下,则param被赋予默认值“asdf”:

aMethod() ...

但我想要的是,如果使用null 调用该方法,那么也将应用默认值:

aMethod(null)  //inside the method, I can use `param` and it has the value "asdf".

在 Scala 中最好的方法是什么?我可以想到模式匹配或简单的if 语句。

【问题讨论】:

    标签: scala


    【解决方案1】:

    模式匹配

    def aMethod(param: String = null) {
        val paramOrDefault = param match {
            case null => "asdf"
            case s => s
        }
    }
    

    选项(隐式)

    def aMethod(param: String = null) {
        val paramOrDefault = Option(param).getOrElse("asdf")
    }
    

    选项(显式)

    def aMethod(param: Option[String] = None) {
        val paramOrDefault = param getOrElse "asdf"
    }
    

    一旦你习惯了,最后一种方法实际上是最惯用和最易读的。

    【讨论】:

    • 有没有办法隐式转换为选项?当然我可以编写自己的 def,但是 API 中已经有一个了吗?
    • @JohnSmith:你的意思是:implicit def obj2Option[T](t: T) = Option(t)?我认为这有点太脆弱/危险,不能成为标准隐式转换的一部分,我不知道有这样的。
    • @TomaszNurkiewicz 请看我的回答,因为这个解决方案,经过仔细检查,将对 Scala 开发工具产生负面影响,并且有可能在涉及继承时破坏代码。
    • 这个答案似乎没有解决@VladGudim 的解决方案中提到的特征,例如:* The method definition clearly indicates the parameter's default value.; * usage in traits, etc.;请澄清是否如此。与@VladGudim 相比,您的方法有什么优势
    【解决方案2】:
    def aMethod(param: String = null) = { 
      val p = 
        if(param == null)
          "asdf"
         else
           param
    
      println(p) 
    }
    

    但必须要问一个问题:为什么允许null?在您的情况下,Option 可能吗?为此,您可以这样做:

    def aMethod(param: Option[String]) = { 
      val p = param.getOrElse("asdf")    
      println(p)
    }
    

    这清楚地表明您的方法期望“空”参数的可能性。

    【讨论】:

      【解决方案3】:

      如果该方法只有一个或两个可以设置为null 的默认参数,请考虑以下模式:

      // please note that you must specify function return type
      def aMethod (x:String = "asdf"):String = if (x==null) aMethod() else {
          // aMethod body ...
          x 
      }
      

      有一些好处:

      • 方法定义清楚地表明了参数的默认值。
      • Scala 工具(包括 ScalaDoc)可以选择正确的默认值。
      • 无需定义附加值来替换方法主体中的原始参数 - 出错的空间更小,更容易推理。
      • 模式相当简洁。

      此外,考虑以下场景:

      trait ATrait {
        def aMethod (x:String = "trait's default value for x"):String
      }
      
      class AClass extends ATrait {
          ....
      }
      

      显然,这里我们需要扩展 trait,同时保留原始默认值。任何涉及最初将参数设置为null,然后进行检查和实际默认值的模式都将破坏特征建立的契约:

      class AClass extends ATrait {
        // wrong, breaks the expected contract
        def aMethod(x: String = null):String = {
            val xVal = if (x == null) "asdf" else x 
            ...
        }
      }
      

      确实,在这种情况下,保留 ATrait 的原始值的唯一方法是:

      class AClass extends ATrait {
        override def aMethod (x:String):String = if (x==null) aMethod() else {
          ... // x contains default value defined within ATrait
        }
      }
      

      但是,在有多个默认参数可以设置为null 的情况下,模式开始变得相当混乱:

      // two parameters
      def aMethod (x:String = "Hello",y:String = "World"):String = 
        if (x==null) aMethod(y=y) else
        if (y==null) aMethod(x=x) else {
          // aMethod body ...
          x + " " + y
      }
      
      // three parameters
      def aMethod (x:String = "Hello",y:String = " ",z:String = "World"):String = 
        if (x==null) aMethod(y=y,z=z) else
        if (y==null) aMethod(x=x,z=z) else 
        if (z==null) aMethod(x=x,y=y) else {
          // aMethod body ...
          x + y + z
      }
      

      仍然在覆盖现有合同时,这可能是遵守原始默认值的唯一方法。

      【讨论】:

      • 我真的很喜欢你的方法(和理由);我希望能给它更多的分数;因为我认为这应该是答案。谢谢。
      猜你喜欢
      • 1970-01-01
      • 2011-08-06
      • 1970-01-01
      • 2010-09-21
      • 1970-01-01
      • 2012-05-26
      • 2012-03-14
      • 1970-01-01
      • 2017-01-24
      相关资源
      最近更新 更多