需求
已知某路径下有若干文件,返回文件名满足如下条件的文件列表:
- 文件名以xxx结尾的
- 文件名中包含xxx的
- 文件名匹配某正则表达式的
举例
某路径下有如下文件:
- Some.java
- Some2.scala
- Some678Other.scala
- SomeJdbcOther1.scala
- SomeJdbcOther2.java
输出满足“文件名以scala结尾”、“文件名包含Jdbc”、“文件名中有3位数字”的文件列表(此处省略了绝对路径):
- Some2.scala, Some678Other.scala, SomeJdbcOther1.scala
- SomeJdbcOther1.scala, SomeJdbcOther2.java
- Some678Other.scala
第一个版本
//Files1.scala
object Files1 {
private def filesHere = (new java.io.File("D:/Temp/20200420/")).listFiles
def filesEnding(query: String) =
for (file <- filesHere; if file.getName.endsWith(query))
yield file
def filesContaining(query: String) =
for (file <- filesHere; if file.getName.contains(query))
yield file
def filesRegex(query: String) =
for (file <- filesHere; if file.getName.matches(query))
yield file
def main(args: Array[String]) {
println(filesEnding("scala").toList)
println("===")
println(filesContaining("Jdbc").toList)
println("===")
println(filesRegex(".*\\d{3}.*").toList)
}
}
需求要什么就满足什么,这是人的一般思维方式,第一个版本很好理解,注意“yield file”这种用法,即把for循环的返回值收集到一个数组中,最终返回Array[File]。
第二个版本(重构)
如果需要列出以xxx开头的文件名,可能需要新增一个方法,如def filesStartWith(query: String)。你会发现这个新方法和filesEnding、filesContaining、filesRegex比较类似,代码存在重复的“坏味道”。把文件过滤条件抽取出来,不失为一个好注意。
//Files2.scala
object Files2 {
private def filesHere = (new java.io.File("D:/Temp/20200420/")).listFiles
/**
* matcher函数的第一个参数表示:文件名称
* matcher函数的第二个参数表示:文件后缀、文件名称特征字符串或者正则表达式
*/
def filesMatching(query: String, matcher: (String, String) => Boolean) = {
for (file <- filesHere; if matcher(file.getName, query))
yield file
}
def filesEnding(query: String) =
filesMatching(query, (fileName: String, query: String) => fileName.endsWith(query))
def filesContaining(query: String) =
filesMatching(query, (fileName: String, query: String) => fileName.contains(query))
def filesRegex(query: String) =
filesMatching(query, (fileName: String, query: String) => fileName.matches(query))
def main(args: Array[String]) {
println(filesEnding("scala").toList)
println("===")
println(filesContaining("Jdbc").toList)
println("===")
println(filesRegex(".*\\d{3}.*").toList)
}
}
第三个版本(重构)
Scala有类型推断特性,(fileName: String, query: String)可简写为(fileName, query)
//Files3.scala
object Files3 {
private def filesHere = (new java.io.File("D:/Temp/20200420/")).listFiles
/**
* matcher函数的第一个参数表示:文件名称
* matcher函数的第二个参数表示:文件后缀、文件名称特征字符串或者正则表达式
*/
def filesMatching(query: String, matcher: (String, String) => Boolean) = {
for (file <- filesHere; if matcher(file.getName, query))
yield file
}
def filesEnding(query: String) =
filesMatching(query, (fileName, query) => fileName.endsWith(query))
def filesContaining(query: String) =
filesMatching(query, (fileName, query) => fileName.contains(query))
def filesRegex(query: String) =
filesMatching(query, (fileName, query) => fileName.matches(query))
def main(args: Array[String]) {
println(filesEnding("scala").toList)
println("===")
println(filesContaining("Jdbc").toList)
println("===")
println(filesRegex(".*\\d{3}.*").toList)
}
}
第四个版本(重构)
对于(fileName, query) => fileName.endsWith(query),由于参数fileName和query在函数体内分别只用到了一次,可以用占位符语法来写:_
endsWith(_
)。
//Files4.scala
object Files4 {
private def filesHere = (new java.io.File("D:/Temp/20200420/")).listFiles
/**
* matcher函数的第一个参数表示:文件名称
* matcher函数的第二个参数表示:文件后缀、文件名称特征字符串或者正则表达式
*/
def filesMatching(query: String, matcher: (String, String) => Boolean) = {
for (file <- filesHere; if matcher(file.getName, query))
yield file
}
def filesEnding(query: String) =
filesMatching(query, _.endsWith(_))
def filesContaining(query: String) =
filesMatching(query, _.contains(_))
def filesRegex(query: String) =
filesMatching(query, _.matches(_))
def main(args: Array[String]) {
println(filesEnding("scala").toList)
println("===")
println(filesContaining("Jdbc").toList)
println("===")
println(filesRegex(".*\\d{3}.*").toList)
}
}
第五个版本(重构)
matcher函数的第二个参数,其实来自filesEnding(query: String)里的query,并没有被改变,因而filesMatching(query, _
.endsWith(_
))里query参数显得也重复了,利用Scala的闭包特性可进一步重构。
//Files5.scala
object Files5 {
private def filesHere = (new java.io.File("D:/Temp/20200420/")).listFiles
/**
* matcher函数的第一个参数表示:文件名称
* matcher函数的第二个参数表示:文件后缀、文件名称特征字符串或者正则表达式
*/
private def filesMatching(matcher: String => Boolean) = {
for (file <- filesHere; if matcher(file.getName))
yield file
}
def filesEnding(query: String) =
filesMatching(_.endsWith(query))
def filesContaining(query: String) =
filesMatching(_.contains(query))
def filesRegex(query: String) =
filesMatching(_.matches(query))
def main(args: Array[String]) {
println(filesEnding("scala").toList)
println("===")
println(filesContaining("Jdbc").toList)
println("===")
println(filesRegex(".*\\d{3}.*").toList)
}
参考
1.《Scala编程(第3版)》9.1 减少代码重复