Scala 大小写匹配优化以提高性能和可读性

Scala case-match optimization for performance and readability

我在 Scala 中写了下面的模式匹配:

scala> val rowMap = Map("abc" -> null)
rowMap: scala.collection.immutable.Map[String,Null] = Map(abc -> null)

scala> val postgresKey = "abc"
postgresKey: String = abc

scala> val h2Key = "xyz"
h2Key: String = xyz

建议选项:

scala> (rowMap.get(postgresKey).map(_.asInstanceOf[String]) , rowMap.get(h2Key).map(_.asInstanceOf[String])) match
     |       {
     |         case (Some(value), _) if (value != null && value.trim.nonEmpty) => value
     |         case (None, Some(value)) if (value != null && value.trim.nonEmpty) => value
     |         case (_ , _) => "0.0"
     |       }
res9: String = 0.0

我的代码:

scala> (rowMap.get(postgresKey).map(_.asInstanceOf[String]) , rowMap.get(h2Key).map(_.asInstanceOf[String])) match
     |       {
     |         case (Some(value), _) if (value != null ) => if (value.trim.nonEmpty) value else "0.0"
     |         case (None, Some(value)) if (value != null ) => if (value.trim.nonEmpty) value else "0.0"
     |         case (_ , _) => "0.0"
     |       }  
res10: String = 0.0

建议的选项是否比我的代码有任何性能优势。 我知道 && 会在看到 null 时停止比较 value.trim.nonEmpty。因此,我们节省了 1 次比较。

我明白阅读起来也更干净。 建议的方法还有什么更好的吗?

编辑 1:我被告知要避免使用 null。然而,它是来自 anorm defaultParser 的输入。由于场景难以复制,我举了上面的例子。在我的例子中,单元测试在 H2 中,实际数据库是 postgres。

下面是代码片段:

            val anormQuery = SQL(query)
            // map the anorm Row as per the input params, differentiate into aggregated and group cols
            val tmp = anormQuery.as(anormQuery.defaultParser.*)

            logger.info(" Printing the result set object " + tmp.toString())

            val finalSqlResultset = tmp.map(row ⇒ {

              // Anorm row.asMap has this behaviour that it adds either a leading dot(.) or <tablename>. in front of the map keys (the columns/alias in sql) based on whether it is H2 or Postgres

              val rowMap = for ((k, v) ← row.asMap) yield (k, v)
              logger.info("Print the resultSet as map : " + rowMap.toString())


              val aggregates = expressionsToAggregate.map(input ⇒ {
                val (anormKey, postgresKey) = doesColumnHaveAlias.get(input.alias.getOrElse("UnknownAlias")).getOrElse(("Unknown", "Unknown"))
                //
                val aggResult = AggregatedValue(
                  input.alias.get,
                  (rowMap.get(postgresKey).map(_.asInstanceOf[String]), rowMap.get(anormKey).map(_.asInstanceOf[String])) match {
                    case (Some(value), _) if (value != null && value.trim.nonEmpty) ⇒ value
                    case (None, Some(value)) if (value != null && value.trim.nonEmpty) ⇒ value
                    case (_, _) ⇒ "0.0"
                  })
                logger.info("## TRACE 1 ##" + aggResult)
                aggResult
              })
})

首先,不要使用空值。曾经。 就做 val rowMap = Map("abc" -> "").

或者更好的是,根本不要将垃圾放入地图(无论如何你都在过滤掉它!)。

其次,既然你提到了可读性,看起来你正在寻找这样的东西:

 rowMap.get(postgresKey).filterNot(_.trim.isEmpty)
  .orElse(rowMap.get(h2Key).filerNot(_.trim.isEmpty))
  .getOrElse("0.0")

最后,回答您的问题,您显示的两个版本并不相同。 第一个做我上面做的。第二个是这样的:

 rowMap.get(postgresKey).orElse(rowMap.get(h2Key))
   .filterNot(_.trim.isEmpty)
   .getOrElse("0.0")

在前一种情况下,将 postgresKey 设置为 "" 相当于根本不设置它,在这种情况下,您将始终获得 h2Key 的值。 在后一种情况下,如果 postgresKey 设置为 "",无论 h2Key 设置的是什么,您都将得到“0.0”。

所以,行为是不同的,这取决于你实际需要哪一个。

如果第一种行为是你需要的,也可以这样写:

Some(rowMap.filterValues(_.trim.nonEmpty)).flatMap { case m => 
   m.get(postrgesKey) orElse m.get(h2Key)
}.getOrElse("0.0")

或者,如果您听了我的建议并决定不首先将垃圾放入地图中,您也可以放宽过滤器:

roMap.get(postrgesKey) orElse rowMap.get(h2Key) getOrElse "0.0"