Scala学习总结(三)

Scala学习总结

六、集合

1. Scala集合的特点

Java集合:
  • 三大类型:列表 List 、集合 Set 、映射 Map ,有多种不同实现。
Scala集合:
  • 三大类型:序列 Seq ,集合 Set ,映射 Map ,所有集合都扩展自 Iterable 。
  • 对于几乎所有集合类,都同时提供可变不可变版本。
    • 不可变集合: scala.collection.immutable
    • 可变集合: scala.collection.mutable
    • 两个包中可能有同名的类型,需要注意区分是用的可变还是不可变版本,避免冲突和混淆。
  • 对于不可变集合,指该集合长度数量不可修改,每次修改(比如增删元素)都会返回一个新的对象,而不会修改源对象。
  • 可变集合可以对源对象任意修改,一般也提供不可变集合相同的返回新对象的方法,但也可以用其他方法修改源对象。
建议:操作集合时,不可变用操作符,可变用方法。操作符也不一定就会返回新对象,但大多是这样的,还是要具体看。

1.1. 不可变集合关系一览

  • 不可变集合没有太多好说的,集合和映射的哈希表和二叉树实现是肯定都有的,序列中分为随机访问序列(数组实现)和线性序列(链表实现),基本数据结构都有了。
  • `Range` 是范围,常用来遍历,有语法糖支持 `1 to 10 by 2` `10 until 1 by -1` 其实就是隐式转换加上方法调用。
  • scala中的 String 就是 java.lang.String ,和集合无直接关系,所以是虚箭头,是通过 `Perdef` 中的低优先级隐式转换来做到的。经过隐式转换为一个包装类型后就可以当做集合了。
  • Array 和 String 类似,在图中漏掉了。
  • 此类包装为了兼容java在scala中非常常见,scala中很多类型就是对java类型的包装或者仅仅是别名。
  • scala中可能会推荐更多地使用不可变集合。能用不可变就用不可变。

1.2. 可变集合一览

  • 序列中多了 Buffer ,整体结构差不多。
  • 不可变指的是对象大小不可变,但是可以修改元素的值,需要注意这一点。而如果用了 val 不变量存储,那么指向对象的地址也不可变。
  • 不可变集合在原集合上个插入删除数据是做不到的,只能返回新的集合。
  • 集合类型大多都是支持泛型,使用泛型的语法是` [Type] `,不同于java的 `<Type>` 。

2. 数组

2.1. 定长数组

  • 访问元素使用 `()` 运算符,通过 `apply/update` 方法实现,源码中的实现只是抛出错误作为存根方法(stab method),具体逻辑由编译器填充。
  • 代码:
// 1. new
val arr = new Array[Int](5)
 
// 2. factory method in companion obejct
val arr1 = Array[Int](5)
val arr2 = Array(0, 1, 3, 4)
 
// 3. traverse, range for
for (i <‐ 0 until arr.length) arr(i) = i
for (i <‐ arr.indices) print(s"${arr(i)} ")
println()
 
// 4. tarverse, foreach
for (elem <‐ arr) print(s"$elem ") // elem is a val
println()
 
// 5. tarverse, use iterator
val iter = arr.iterator
while (iter.hasNext)
print(s"${iter.next()} ")
println()
 
// 6. traverse, use foreach method, pass a function
arr.foreach((elem: Int) => print(s"$elem "))
println()
println(arr2.mkString(", ")) // to string directly
 
// 7. add element, return a new array, : should toward to object
val newArr = arr :+ 10 // arr.:+(10) add to end
println(newArr.mkString(", "))
val newArr2 = 20 +: 10 +: arr :+ 30 // arr.+:(10).+:(20).:+(30)
println(newArr2.mkString(", ")) 

 

  • 可以看到自定义运算符可以非常灵活,规定如果运算符首尾有` : `那么` : `一定要指向对象。
  • 下标越界会抛出异常,使用前应该检查。
  • 通过 Predef 中的隐式转换为一个混入了集合相关特征的包装类型从而得以使用scala的集合相关特征,
  • Array 类型中并没有相关混入。

2.2. 变长数组

  • 类型 `ArrayBuffer` ,类似于Java的ArrayList。
// 1. create
val arr: ArrayBuffer[Int] = new ArrayBuffer[Int]()
val arr1: ArrayBuffer[Int] = ArrayBuffer(10, 20, 30)
println(arr.mkString(", "))
println(arr1) // call toString ArrayBuffer(10, 20, 30)
 
// 2. visit
arr1(2) = 10
 
// 3. add element to tail
var newArr = arr :+ 15 :+ 20 // do not change arr
println(newArr)
newArr = arr += 15 // modify arr itself, add to tail return itself, do notrecommand assign to
other var
println(arr)
println(newArr == arr) // true
 
// 4. add to head
77 +=: arr
println(arr)
 
// 5. insert to middle
arr.insert(1, 10)
println(arr)
 
// 6. remove element
arr.remove(0, 1) // startIndex, count
println(arr)
arr ‐= 15 // remove specific element
println(arr)
 
// 7. convert to Array
val newImmuArr: Array[Int] = arr.toArray
println(newImmuArr.mkString(", "))
 
// 8. Array to ArryBuffer
val buffer: scala.collection.mutable.Buffer[Int] = newImmuArr.toBuffer
println(buffer)
  • val arr2 = ArrayBufffferInt 也是使用的 apply 方法构建对象
  • def append(elems: A*) { appendAll(elems) } 接收的是可变参数。
  • 每append一次,arr在底层会重新分配空间,进行扩容,arr2的内存地址会发生变化,也就成为新的ArrayBuffffer。
  • 可变数组和不可变数组可以调用方法互相转换。(toBuffffer/toArray)

2.3. 多维数组

  • 就是数组的数组。
  • 使用 `Array.ofDim[Type](firstDim, secondDim, ...)` 方法。
// create 2d array
val arr: Array[Array[Int]] = Array.ofDim[Int](2, 3)
arr(0)(1) = 10
arr(1)(0) = 100
 
// traverse
arr.foreach(v => println(v.mkString(","))) 

 

2.4. Scala 数组与 Java 的 List 的互转

2.4.1 Scala 数组转 Java 的 List

 
import scala.collection.mutable.ArrayBuffer
 
 
object ArrayBuffer2JavaList {
  def main(args: Array[String]): Unit = {
    // Scala 集合和 Java 集合互相转换
    val arr = ArrayBuffer("1", "2", "3")
    import scala.collection.JavaConversions.bufferAsJavaList
    //即这里的 bufferAsJavaList 是一个隐式函数
    /* 
    implicit def bufferAsJavaList[A](b : scala.collection.mutable.Buffer[A]) : java.util.List[
    */
    val javaArr = new ProcessBuilder(arr)
    val arrList = javaArr.command()
    println(arrList) //输出 [1, 2, 3]
  }

2.4.2 Java  List  scala  Buer

//说明:asScalaBuffer 是一个隐式转换

/*

implicit def asScalaBuffer[A](l : java.util.List[A]) : scala.collection.mutable.Buffer[A] 

*/

import scala.collection.JavaConversions.asScalaBuffer

import scala.collection.mutable

// java.util.List ==> Buffer

val scalaArr: mutable.Buffer[String] = arrList

scalaArr.append("jack")

println(scalaArr)

3. 元组 Tuple

元组也是可以理解为一个容器 ,可以存放各种相同或不同类型的数据

特点:

  • `(elem1,elem2, ...)`类型可以不同。
  • 最多只能22个元素 ,从Tuple1定义到了Tuple22
  • 使用 `_1 _2 _3 ...` 访问。
  • 也可以使用 `productElement(index)` 访问 ,下标从0开始。
  • `->` 创建二元组。
  • 遍历:`for(elem <-  tuple.productIterator)`
  • 可以嵌 ,元组的元素也可以是元组。

4. 列表 List

 Scala 中的 List Java List 不一样 ,在 Java  List 是一个接口 ,真正存放数据是 ArrayList ,而 Scala  List 可以 直接存放数据 ,就是一个 object ,默认情况下 Scala  List 是不可变的   List 属于序列 Seq。

4.1. 不可变列表

  • `List `,抽象类,不能直接 `new` ,使用伴生对象 `apply` 传入元素创建。
  • `List` 本身也有 `apply` 能随机访问(做了优化),但是不能 `update` 更改。
  • `foreach` 方法遍历。
  • 支持 `+: :+ `首尾添加元素。
  • `Nil `空列表, `::` 添加元素到表头。
  • 常用 `Nil.::(elem) `创建列表,换一种写法就是 `10 :: 20 :: 30 :: Nil `得到结果 `List(10, 20, 30)`
  • 合并两个列表: `list1 ::: list2` 或者 `list1 ++ list2 `。 

4.2. 可变列表 

  • 可变列表 `ListBuffer` ,和 `ArrayBuffer` 很像。
  • `final`的,可以直接 `new` ,也可以伴生对象 `apply `传入元素创建
  • 方法: `append prepend insert remove`
  • 添加元素到头或尾:` +=: +=`
  • 合并: `++` 得到新的列表, `++=` 合并到源上。
  • 删除元素也可以用 `-= `运算符。 

5. 队列 Queue

  • 队列是一个有序列表,在底层可以用数组或是链表来实现。
  • 其输入和输出要遵循先入先出的原则。即:先存入队列的数据,要先取出。后存入的要后取出
  • 在 Scala 中,由设计者直接给我们提供队列类型使用。
  • 在 scala 中, 有 `scala.collection.mutable.Queue `和 `scala.collection.immutable.Queue` , 一般来说,我们在开发中通常使用可变集合中的队列
  • 入队 `enqueue(Elem*)` 出队` Elem = dequeue() `

6. 集 Set

6.1. 不可变集

  • 数据无序,不可重复。
  • 可变和不可变都叫 `Set `,需要做区分。默认 `Set` 定义为` immutable.Set` 别名。
  • 创建时重复数据会被去除,可用来去重。
  • 添加元素: `set + elem` 
  • 合并: `set1 ++ set2`
  • 移除元素: `set - elem`
  • 不改变源集合。 

6.2. 可变集

  • 操作基于源集合做更改。
  • 为了与不可变集合区分,` import scala.collection.mutable `并用 `mutable.Set `。
  • 不可变集合有的都有。
  • 添加元素到源上: `set += elem` `add`
  • 删除元素: `set -= elem remove`
  • 合并: `set1 ++= set2 `

7映射 Map

Scala 中的 Map  Java 类似 ,也是一个散列表 ,它存储的内容也是键值对(key-value)映射 Scala 中不可变的 Map 是有序的 ,可变的 Map 是无序的。

7.1. 不可变映射

  • Map 默认就是 `immutable.Map` 别名。
  • 两个泛型类型。
  • 基本元素是一个二元组。 
// create Map
val map: Map[String, Int] = Map("a" ‐> 13, "b" ‐> 20)
println(map)
 
// traverse
map.foreach((kv: (String, Int)) => println(kv))
map.foreach(kv => println(s"${kv._1} : ${kv._2}"))
 
// get keys and values
for (key <‐ map.keys) {
  println(s"${key} : ${map.get(key)}")
}
 
// get value of given key
println(map.get("a").get)
println(map.getOrElse("c", ‐1)) // avoid excption
println(map("a")) // if no such key will throw exception
 
// merge
val map2 = map ++ Map("e" ‐> 1024)
println(map2) 

 

7.2. 可变映射

  • `mutable.Map`
  • 不可变的都支持
// create mutable Map
val map: mutable.Map[String, Int] = mutable.Map("a" ‐> 10, "b" ‐> 20)
 
// add element
map.put("c", 30)
map += (("d", 40)) // two () represent tuple to avoid ambiguity
println(map)
 
// remove element
map.remove("a")
map ‐= "b" // just need key
println(map)
 
// modify element
map.put("c", 100) // call update, add/modify
println(map)
 
// merge Map
map ++= Map("a" ‐> 10, "b" ‐> 20, "c" ‐> 30) // add and will override
println(map)

 

七、集合的应用操作

1. 集合通用属性和方法

  • 线性序列才有长度 length 、所有集合类型都有大小 size 。
  • 遍历 `for (elem <- collection) `、迭代器 `for (elem <- collection.iterator)` 。
  • 生成字符串 `toString mkString` ,像 Array 这种是隐式转换为scala集合的,` toString `是继承自`java.lang.Object` 的,需要自行处理。
  • 是否包含元素 `contains `。 

2. 衍生集合的方法

  • 获取集合的头元素 head (元素)和剩下的尾 tail (集合)。
  • 集合最后一个元素 last (元素)和除去最后一个元素的初始数据 init (集合)。
  • 反转 reverse 。
  • 取前后n个元素 take(n) takeRight(n)
  • 去掉前后n个元素 drop(n) dropRight(n)
  • 交集 intersect
  • 并集 union ,线性序列的话已废弃用 concat 连接。
  • 差集 diff ,得到属于自己、不属于传入参数的部分。
  • 拉链 zip ,得到两个集合对应位置元素组合起来构成二元组的集合,大小不匹配会丢掉其中一个集合不匹配
  • 的多余部分。
  • 滑窗 sliding(n, step = 1) ,框住特定个数元素,方便移动和操作。得到迭代器,可以用来遍历,每个迭代
  • 的元素都是一个n个元素集合。步长大于1的话最后一个窗口元素数量可能个数会少一些。

3. 集合的简单计算操作

  • 求和 sum 求乘积 product 最小值 min 最大值 max
  • maxBy(func) 支持传入一个函数获取元素并返回比较依据的值,比如元组默认就只会判断第一个元素,要根
  • 据第二个元素判断就返回第二个元素就行 xxx.maxBy(_._2) 。
  • 排序 sorted ,默认从小到大排序。从大到小排序 sorted(Ordering[Int].reverse) 。
  • 按元素排序 sortBy(func) ,指定要用来做排序的字段。也可以再传一个隐式参数逆序 sortBy(func)
  • (Ordering[Int].reverse)
  • 自定义比较器 sortWith(cmp) ,比如按元素升序排列 sortWith((a, b) => a < b) 或者 sortWith(_ < _) ,
  • 按元组元素第二个元素升序 sortWith(_._2 > _._2) 。
  • 例子:
object Calculations {
  def main(args: Array[String]): Unit = {
    // calculations of collections
    val list = List(1, 4, 5, 10)
    
    // sum
    var sum = 0
    for (elem <‐ list) sum += elem
    println(sum)
    
    println(list.sum)
    println(list.product)
    println(list.min)
    println(list.max)
    
    val list2 = List(('a', 1), ('b', 2), ('d', ‐3))
    println(list2.maxBy((tuple: (Char, Int)) => tuple._2))
    println(list2.minBy(_._2))
    
    // sort, default is ascending
    val sortedList = list.sorted
    println(sortedList)
    
    // descending
    println(list.sorted(Ordering[Int].reverse))
    
    // sortBy
    println(list2.sortBy(_._2))
    
    // sortWith
    println(list.sortWith((a, b) => a < b))
    println(list2.sortWith(_._2 > _._2))
  }

4. 集合高级计算函数

  • 大数据的处理核心就是映射(map)和规约(reduce)。
  • 映射操作(广义上的map):
    •   过滤:自定义过滤条件, filter(Elem => Boolean)
    •   转化/映射(狭义上的map):自定义映射函数, map(Elem => NewElem)
    •   扁平化(flflatten):将集合中集合元素拆开,去掉里层集合,放到外层中来。 flatten
    •   扁平化+映射:先映射,再扁平化, flatMap(Elem => NewElem)
    •   分组(group):指定分组规则, groupBy(Elem => Key) 得到一个Map,key根据传入的函数运用于集
    •   合元素得到,value是对应元素的序列。
  • 规约操作(广义的reduce):
    •   简化/规约(狭义的reduce):对所有数据做一个处理,规约得到一个结果(比如连加连乘操作)。reduce((CurRes, NextElem) => NextRes) ,传入函数有两个参数,第一个参数是第一个元素(第一次运算)和上一轮结果(后面的计算),第二个是当前元素,得到本轮结果,最后一轮的结果就是最终结果。 reduce 调用 reduceLeft 从左往右,也可以 reduceRight 从右往左(实际上是递归调用,和一般意义上的从右往左有区别,看下面例子)。
    •   折叠(fold): fold(InitialVal)((CurRes, Elem) => NextRes) 相对于 reduce 来说其实就是 fold 自己给初值,从第一个开始计算, reduce 用第一个做初值,从第二个元素开始算。 fold 调用foldLeft ,从右往左则用 foldRight (翻转之后再 foldLeft )。具体逻辑还得还源码。从右往左都有点绕和难以理解,如果要使用需要特别注意。
  • 案例: 
object HighLevelCalculations {
  def main(args: Array[String]): Unit = {
    val list = List(1, 10, 100, 3, 5, 111)
    
    // 1. map functions
    // filter
    val evenList = list.filter(_ % 2 == 0)
    println(evenList)
 
    // map
    println(list.map(_ * 2))
    println(list.map(x => x * x))
 
    // flatten
    val nestedList: List[List[Int]] = List(List(1, 2, 3), List(3, 4, 5), List(10, 100))
    val flatList = nestedList(0) ::: nestedList(1) ::: nestedList(2)
    println(flatList)
    val flatList2 = nestedList.flatten
    println(flatList2) // equals to flatList
 
    // map and flatten
  
    // example: change a string list into a word list
    val strings: List[String] = List("hello world", "hello scala", "yes no")
    val splitList: List[Array[String]] = strings.map(_.split(" ")) // divide string to words
    val flattenList = splitList.flatten
    println(flattenList)
    // merge two steps above into one
 
    // first map then flatten
    val flatMapList = strings.flatMap(_.split(" "))
    println(flatMapList)
    
    // divide elements into groups
    val groupMap = list.groupBy(_ % 2) // keys: 0 & 1
    val groupMap2 = list.groupBy(data => if (data % 2 == 0) "even" else "odd") // keys :"even" & "odd"
    println(groupMap)
    println(groupMap2)
    val worldList = List("China", "America", "Alice", "Curry", "Bob", "Japan")
    println(worldList.groupBy(_.charAt(0)))
 
    // 2. reduce functions
    // narrowly reduce
    println(List(1, 2, 3, 4).reduce(_ + _)) // 1+2+3+4 = 10
    println(List(1, 2, 3, 4).reduceLeft(_ ‐ _)) // 1‐2‐3‐4 = ‐8
    println(List(1, 2, 3, 4).reduceRight(_ ‐ _)) // 1‐(2‐(3‐4)) = ‐2, a little confusing
 
    // fold
    println(List(1, 2, 3, 4).fold(0)(_ + _)) // 0+1+2+3+4 = 10
    println(List(1, 2, 3, 4).fold(10)(_ + _)) // 10+1+2+3+4 = 20
    println(List(1, 2, 3, 4).foldRight(10)(_ ‐ _)) // 1‐(2‐(3‐(4‐10))) = 8, a little
    confusing
  }
}

5. 集合应用案例 

  • Map的默认合并操作是用后面的同key元素覆盖前面的,如果要定制为累加他们的值可以用 `fold` 。 
// merging two Map will override the value of the same key
// custom the merging process instead of just override
val map1 = Map("a" ‐> 1, "b" ‐> 3, "c" ‐> 4)
val map2 = mutable.Map("a" ‐> 6, "b" ‐> 2, "c" ‐> 5, "d" ‐> 10)
val map3 = map1.foldLeft(map2)(
  (mergedMap, kv) => {
    mergedMap(kv._1) = mergedMap.getOrElse(kv._1, 0) + kv._2
    mergedMap
  }
)
println(map3) // HashMap(a ‐> 7, b ‐> 5, c ‐> 9, d ‐> 10) 
  • 经典案例:单词计数:分词,计数,取排名前三结果。 
// count words in string list, and get 3 highest frequency words
def wordCount(): Unit = {
  val stringList: List[String] = List(
    "hello",
    "hello world",
    "hello scala",
    "hello spark from scala",
    "hello flink from scala"
  )
 
// 1. split
  val wordList: List[String] = stringList.flatMap(_.split(" "))
  println(wordList)
 
// 2. group same words
  val groupMap: Map[String, List[String]] = wordList.groupBy(word => word)
  println(groupMap)
 
// 3. get length of the every word, to (word, length)
  val countMap: Map[String, Int] = groupMap.map(kv => (kv._1, kv._2.length))
 
// 4. convert map to list, sort and take first 3
  val countList: List[(String, Int)] = countMap.toList
    .sortWith(_._2 > _._2)
    .take(3)
  
  println(countList) // result
}
  • 单词计数案例扩展,每个字符串都可能出现多次并且已经统计好出现次数,解决方式,先按次数合并之后再按照上述例子处理。 
// strings has their frequency
def wordCountAdvanced(): Unit = {
  val tupleList: List[(String, Int)] = List(
    ("hello", 1),
    ("hello world", 2),
    ("hello scala", 3),
    ("hello spark from scala", 1),
    ("hello flink from scala", 2)
  )
  
  val newStringList: List[String] = tupleList.map(
    kv => (kv._1.trim + " ") * kv._2
  )
 
  // just like wordCount
  val wordCountList: List[(String, Int)] = newStringList
    .flatMap(_.split(" "))
    .groupBy(word => word)
    .map(kv => (kv._1, kv._2.length))
    .toList
    .sortWith(_._2 > _._2)
    .take(3)
 
  println(wordCountList) // result
}
  • 当然这并不高效,更好的方式是利用上已经统计的频率信息。 
def wordCountAdvanced2(): Unit = {
  val tupleList: List[(String, Int)] = List(
    ("hello", 1),
    ("hello world", 2),
    ("hello scala", 3),
    ("hello spark from scala", 1),
    ("hello flink from scala", 2)
  )
 
  // first split based on the input frequency
  val preCountList: List[(String, Int)] = tupleList.flatMap(
    tuple => {
      val strings: Array[String] = tuple._1.split(" ")
      strings.map(word => (word, tuple._2)) // Array[(String, Int)]
    }
  )
 
  // group as words
  val groupedMap: Map[String, List[(String, Int)]] = preCountList.groupBy(_._1)
  println(groupedMap)
 
  // count frequency of all words
  val countMap: Map[String, Int] = groupedMap.map(
    kv => (kv._1, kv._2.map(_._2).sum)
  )
  println(countMap)
 
  // to list, sort and take first 3 words
  val countList = countMap.toList.sortWith(_._2 > _._2).take(3)
 
  println(countList)
}

6. 并行集合(Parllel Collection)

  • 使用并行集合执行时会调用多个线程加速执行。
  • 使用集合类前加一个 `.par` 方法。
  • 具体细节待补。
  • 依赖 `scala.collection.parallel.immutable/mutable` ,2.13版本后不再在标准库中提供,需要单独下载,
  • 暂未找到编好的jar的下载地址,从源码构造需要sbt,TODO。 

八、模式匹配 

1. match-case

  • 用于替代传统C/C++/Java的 switch-case 结构,但补充了更多功能,拥有更强的能力。
  • 语法:(Java中现在也支持 => 的写法了) 
value match {
  case caseVal1 => returnVal1
  case caseVal2 => returnVal2
  ...
  case _ => defaultVal
  • 每一个case条件成立才返回,否则继续往下走。
  • case 匹配中可以添加模式守卫,用条件判断来代替精确匹配。 
def abs(num: Int): Int= {
  num match {
    case i if i >= 0 => i
    case i if i < 0 => ‐i
  }
}
  • 模式匹配支持类型:所有类型字面量,包括字符串、字符、数字、布尔值、甚至数组列表等。
  • 你甚至可以传入 `Any `类型变量,匹配不同类型常量。
  • 需要注意默认情况处理, `case _ `也需要返回值,如果没有 但是又没有匹配到,就抛出运行时错误。默认情况 `case _ `不强制要求通配符(只是在不需要变量的值建议这么做),也可以用 `case abc` 一个变量来接住,可以什么都不做,可以使用它的值。 

2. 类型匹配

  • 通过指定匹配变量的类型(用特定类型变量接住),可以匹配类型而不匹配值,也可以混用。
  • 需要注意类型匹配时由于泛型擦除,可能并不能严格匹配泛型的类型参数,编译器也会报警告。但 `Array` 是基本数据类型,对应于java的原生数组类型,能够匹配泛型类型参数。 
// match type
def describeType(x: Any) = x match {
  case i: Int => "Int " + i
  case s: String => "String " + s
  case list: List[String] => "List " + list
  case array: Array[Int] => "Array[Int] " + array
  case a => "Something else " + a
}
println(describeType(20)) // match
println(describeType("hello")) // match
println(describeType(List("hi", "hello"))) // match
println(describeType(List(20, 30))) // match
println(describeType(Array(10, 20))) // match
println(describeType(Array("hello", "yes"))) // not match
println(describeType((10, 20))) // not match 

3. 匹配数组

  • 对于数组可以定义多种匹配形式,可以定义模糊的元素类型匹配、元素数量匹配或者精确的某个数组元素值匹配,非常强大。 
for (arr <‐ List(
  Array(0),
  Array(1, 0),
  Array(1, 1, 0),
  Array(10, 2, 7, 5),
  Array("hello", 20, 50)
)) {
  val result = arr match {
    case Array(0) => "0"
    case Array(1, 0) => "Array(1, 0)"
    case Array(x: Int, y: Int) => s"Array($x, $y)" // Array of two elements
    case Array(0, _*) => s"an array begin with 0"
    case Array(x, 1, z) => s"an array with three elements, no.2 is 1"
    case Array(x:String, _*) => s"array that first element is a string"
    case _ => "somthing else"
  }
 
println(result)

4. 匹配列表

  • List匹配和Array差不多,也很灵活。还可用用集合类灵活的运算符来匹配。比如使用` :: `运算符匹配` first :: second :: rest `,将一个列表拆成三份,第一个第二个元素和剩余元素构成的列表。
  • 注意模式匹配不仅可以通过返回值当做表达式来用,也可以仅执行语句类似于传统 switch-case 语句不关心返回值,也可以既执行语句同时也返回。

5. 匹配元组

  • 可以匹配n元组、匹配元素类型、匹配元素值。如果只关心某个元素,其他就可以用通配符或变量。
  • 元组大小固定,所以不能用 `_*` 。 

6. 变量声明匹配 

  • 变量声明也可以是一个模式匹配的过程。
  • 元组常用于批量赋值。
  • val (x, y) = (10, "hello")
  • val List(first, second, _*) = List(1, 3, 4, 5)
  • val List(first :: second :: rest) = List(1, 2, 3, 4)

7. for表达式中的模式匹配

  • 元组中取元素时,必须用 _1 _2 ... ,可以用元组赋值将元素赋给变量,更清晰一些。
  • for ((first, second) <- tupleList)
  • for ((first, _) <- tupleList)
  • 指定特定元素的值,可以实现类似于循环守卫的功能,相当于加一层筛选。比如 `for ((10, second) <- tupleList)`
  • 其他匹配也同样可以用,可以关注数量、值、类型等,相当于做了筛选。
  • 元组列表匹配、赋值匹配、 for 循环中匹配非常灵活,灵活运用可以提高代码可读性。

8. 匹配对象

  • 对象内容匹配。
  • 直接 match-case 中匹配对应引用变量的话语法是有问题的。编译报错信息提示:不是样例类也没有一个合法的 unapply/unapplySeq 成员实现。
  • 要匹配对象,需要实现伴生对象 unapply 方法,用来对对象属性进行拆解以做匹配。

9. 样例类

  • 第二种实现对象匹配的方式是样例类。
  • case class className 定义样例类,会直接将打包 apply 和拆包 unapply 的方法直接定义好。
  • 样例类定义中主构造参数列表中的 val 甚至都可以省略,如果是 var 的话则不能省略,最好加上的感觉,奇奇怪怪的各种边角简化。 
  • 对象匹配和样例类例子:
object MatchObject {
  def main(args: Array[String]): Unit = {
    val person = new Person("Alice", 18)
    val result: String = person match {
      case Person("Alice", 18) => "Person: Alice, 18"
      case _ => "something else"
    }
    println(result)
    val s = Student("Alice", 18)
    val result2: String = s match {
      case Student("Alice", 18) => "Student: Alice, 18"
      case _ => "something else"
    }
    println(result2)
  }
}
 
class Person(val name: String, val age: Int)
 
object Person {
  def apply(name: String, age: Int) = new Person(name, age)
  def unapply(person: Person): Option[(String, Int)] = {
    if (person == null) { // avoid null reference
      None
    } else {
      Some((person.name, person.age))
    }
  }
}
 
case class Student(name: String, age: Int) // name and age are vals 

10. 偏函数(partial function)

  • 偏函数是函数的一种,通过偏函数我们可以方便地对参数做更精确的检查,例如偏函数输入类型是 `List[Int] `,需要第一个元素是0的集合,也可以通过模式匹配实现的。
  • 定义:
val partialFuncName: PartialFunction[List[Int], Option[Int]] = {
  case x :: y :: _ => Some(y)
}
  • 通过一个变量定义方式定义, PartialFunction 的泛型类型中,前者是参数类型,后者是返回值类型。函数体中用一个 case 语句来进行模式匹配。上面例子返回输入的 List 集合中的第二个元素。
  • 一般一个偏函数只能处理输入的一部分场景,实际中往往需要定义多个偏函数用以组合使用。
  • 例子: 
object PartialFunctionTest {
  def main(args: Array[String]): Unit = {
    val list: List[(String, Int)] = List(("a", 12), ("b", 10), ("c", 100), ("a", 5))
    // keep first constant and double second value of the tuple
    
    // 1. use map
    val newList = list.map(tuple => (tuple._1, tuple._2 * 2))
    println(newList)
 
    // 2. pattern matching
    val newList1 = list.map(
      tuple => {
        tuple match {
          case (x, y) => (x, y * 2)
        }
      }
    )
    
    println(newList1)
 
 
    // simplify to partial function
    val newList2 = list.map {
      case (x, y) => (x, y * 2) // this is a partial function
    }
    println(newList2)
 
    // application of partial function
    // get absolute value, deal with: negative, 0, positive
    val positiveAbs: PartialFunction[Int, Int] = {
      case x if x > 0 => x
    }
    val negativeAbs: PartialFunction[Int, Int] = {
      case x if x < 0 => ‐x
    }
    val zeroAbs: PartialFunction[Int, Int] = {
      case 0 => 0
    }
 
    // combine a function with three partial functions
    def abs(x: Int): Int = (positiveAbs orElse negativeAbs orElse zeroAbs) (x)
    println(abs(‐13))
    println(abs(30))
    println(abs(0))
  }

九、泛型

1. 泛型

  • `[TypeList] `,定义和使用都是。
  • 常用于集合类型中用于支持不同元素类型。
  • 和java一样通过类型擦除/擦拭法来实现。
  • 定义时可以用` +- `表示协变和逆变,不加则是不变。
    • class MyList[+T] {} // 协变
    • class MyList[‐T] {} // 逆变
    • class MyList[T] {} // 不变

2. 协变和逆变

  • 比如 Son 和 Father 是父子关系, Son 是子类。
    • 协变(Covariance): MyList[Son] 是 MyList[Father] 的子类,协同变化。
    • 逆变(Contravariance): MyList[Son] 是 MyList[Father] 的父类,逆向变化。
    • 不变(Invariant): MyList[Father] MyList[Son] 没有父子关系。
  • 还需要深入了解。

3. 泛型上下限

  • 泛型上限: class MyList[T <: Type] ,可以传入 Type 自身或者子类。
  • 泛型下限: class MyList[T >: Type] ,可以传入 Type 自身或者父类。
  • 对传入的泛型进行限定。 

4. 上下文限定

  • `def f[A : B](a: A) = println(a)` 等同于 `def f[A](a: A)(implicit arg: B[A])`
  • 是将泛型和隐式转换结合的产物,使用上下文限定(前者)后,方法内无法使用隐式参数名调用隐式参数,
  • 需要通过 `implicitly[Ordering[A]] `获取隐式变量。
posted @   夜凉歌尽  阅读(40)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示