6.1.4 Scala 《集合》不/可变,Ls,Qu,Set,Map,转换《算子》遍历,展开,并算,归约,排序《隐式》转换/函数,参数/值《类型参数》泛型类/函数,协/逆变《Akka》Actor

目录

第九部分 集合

第1节 可变和不可变集合

第2节 Seq元素序列

2.1 List

2.2 Queue

第3节 Set

第4节 Map

第5节 集合常用算子

5.1 遍历: map、foreach & mapValues

5.2 展开: flatten & flatMap

5.3 偏函数并行计算: collect

5.4 归约: reduce

5.5 排序: sorted sortwith & sortby

第6节 与Java集合的转换

第十部分 隐式机制

第1节 隐式转换

第2节 隐式转换函数

第3节 隐式参数和隐式值

第十一部分 扩展部分

第1节 类型参数

1.1 泛型类

1.2 泛型函数

1.3 协变和逆变

第2节 Akka 高并发、分布式和容错应用

2.1 Actor


 

第九部分 集合

主要内容:
1、Scala中的可变和不可变集合
2、集合的三大类:Seq、Set、Map
3、集合的常用算子
4、Scala与Java之间的集合转换

第1节 可变和不可变集合

根据容器中元素的组织方式和操作方式,可以分为有序和无序、可变和不可变等不同的容器类别;
不可变集合是指集合内的元素一旦初始化完成就不可再进行更改,任何对集合的改变都将生成一个新的集合;
可变集合提供了改变集合内元素的方法;
Scala同时支持可变集合和不可变集合,主要下面两个包:

  • scala.collection.mutable:定义了可变集合的特质和具体实现类
  • scala.collection.immutable:定义了不可变集合的特质和具体实现类

对于几乎所有的集合类,Scala都同时提供了可变和不可变的版本。

Scala优先采用不可变集合,不可变集合元素不可更改,可以安全的并发访问。
Scala集合有三大类:Seq(序列)、Set(集)、Map(映射);
所有的集合都扩展自Iterable特质。

 

immutable不可变集合:

 

mutable可变集合:

小结:
String属于IndexedSeq
Queue队列和Stack堆这两个经典的数据结构属于LinearSeq
Map体系下有一个SortedMap,说明Scala中的Map是可以支持排序的
mutable可变集合中Seq中的Buffer下有ListBuffer,它相当于可变的List列表;
List列表属于Seq中的LinearSeq

 

第2节 Seq元素序列

Seq代表按照一定顺序排列的元素序列;
该序列是一种特别的可迭代集合,包含可重复的元素;
元素的顺序是确定的,每个元素对应一个索引值;
Seq提供了两个重要的子特质:

  • IndexedSeq:提供了快速随机访问元素的功能,它通过索引来查找和定位的
  • LinearSeq:提供了访问head、tail的功能,它是线型的,有头部和尾部的概念,通过遍历来查找。

 

2.1 List

List代表元素顺序固定的不可变的链表,它是Seq的子类,在Scala编程中经常使用。
List是函数式编程语言中典型的数据结构,与数组类似,可索引、存放类型相同的元素。
List一旦被定义,其值就不能改变。
List列表有头部和尾部的概念,可以分别使用head和tail方法来获取:

  • head返回的是列表第一个元素的值
  • tail返回的是除第一个元素外的其它元素构成的新列表

这体现出列表具有递归的链表结构。

 

Scala定义了一个空列表对象Nil,定义为List[Nothing]
借助 Nil 可将多个元素用操作符 :: 添加到列表头部,常用来初始化列表;
操作符 ::: 用于拼接两个列表;

package com.ch.part09

object ListDemo {
  def main(args: Array[String]): Unit = {

    //Nil表示一个空的列表
    //::操作符表示向集合中添加元素,它是从右往左进行运算的,所以集合对象一定要放在最右边
    val list1 = 1 :: 2 :: 3 :: 4 :: Nil
    val list2 = 5 :: 6 :: 7 :: 8 :: Nil

    //使用:::操作符拼接集合
    val list3=list1:::list2

    //返回第一个元素
    println(list3.head)       // 1

    //返回除第一个元系之外的其他元素构成的新列表
    println(list3.tail)       // List(2, 3, 4, 5, 6, 7, 8)

    //返回除最后一个元素之外的其他元素构成的新列表
    println(list3.init)       // List(1, 2, 3, 4, 5, 6, 7)

    //返回最后一个元素
    println(list3.last)       // List(1, 2, 4, 6, 7, 9)

    val list4=List(4,2,6,1,7,9)
    println(quickSort(list4))
  }
  def quickSort(list:List[Int]):List[Int]={
    list match {
      case Nil=>Nil           // 传入为空, 返回空
      case head :: tail=>     // 否则使用 head 和 tail 获取两部分的集合
        
        //通过partition将tail分为两部分
        //小于head的元素放入less列表中,大于head的元素放入greater列表中
        val (less,greater) = tail.partition(_<head)
        
        // 从右向左, head 添加进后面的集合中, 再和前面的集合连接到一起
        quickSort(less) ::: head :: quickSort(greater)
    }
  }
}

 

2.2 Queue

队列Queue是一个先进先出的结构。
队列是一个有序列表,在底层可以用数组或链表来实现。
先进先出的原则,就是先存入的数据,要先取出,后存入的数据后取出。
在Scala中,有可变队列scala.collection.mutable.Queue 和不可变队列scala.collection.immutable.Queue,一般来说,我们使用的是scala.collection.mutable.Queue

package com.ch.part09

import scala.collection.mutable

object QueueDemo {
  def main(args: Array[String]): Unit = {

    //创建一个可变的队列
    val queue1=new mutable.Queue[Int]()
    println(queue1)       // Queue()

    //队列当中添加元素
    queue1 +=1

    //队列当中添加List列表
    queue1 ++=List(2,3,4)
    println(queue1)         // Queue(1, 2, 3, 4)

    //按照进入队列的顺序,删除队列当中的元素
    //返回队列中的第一个元素,并从队列中删除这个元素
    val dequeue=queue1.dequeue()
    println(dequeue)      // 1
    println(queue1)       // Queue(2, 3, 4)

    //再向队列中添加元素
    queue1.enqueue(5,6,7)
    println(queue1)       // Queue(2, 3, 4, 5, 6, 7)

    //获取第一个、最后一个元素
    println(queue1.head)    // 2
    println(queue1.last)    // 7
  }
}

 

第3节 Set

Set(集合)是没有重复元素的对象集合,Set中的元素是唯一的;
Set分为可变的和不可变的集合;
默认情况下,使用的是不可变集合(引用 scala.collection.immutable.Set);
使用可变集合,需要引用 scala.collection.mutable.Set 包;

package com.ch.part09

object SetDemo {
  def main(args: Array[String]): Unit = {
    //创建一个Set集合
    val set = Set(1, 2, 3, 4, 5, 6)
    set.drop(1)
    println(set)      //Set(5, 1, 6, 2, 3, 4)

    //创建一个可变的Set
    import scala.collection.mutable.Set
    val mutableSet = Set(3, 4, 5)

    //对可变的Set进行增加元素、删除元素的操作
    mutableSet.add(7)
    println(mutableSet)     // Set(5, 3, 7, 4)

    mutableSet.remove(7)
    println(mutableSet)     // Set(5, 3, 4)

    //通过使用 +=  -= 进行增加、删除元素的操作
    mutableSet += 8
    mutableSet -= 3
    println(mutableSet)     // Set(5, 4, 8)

    //对Set集合进行交集的操作(& intersect)
    println(Set(1, 2, 3) & Set(2, 3, 4))            // Set(2, 3)
    println(Set(1, 2, 3).intersect(Set(2, 3, 4)))   // Set(2, 3)

    //对Set集合进行并集的操作(++  |  union)
    println(Set(1,2,3) ++ Set(2,3,4))       // Set(1, 2, 3, 4)
    println(Set(1,2,3) | Set(2,3,4))        // Set(1, 2, 3, 4)
    println(Set(1,2,3).union(Set(2,3,4)))   // Set(1, 2, 3, 4)

    //对Set集合进行差集的操作(--  &~  diff) 
    println(Set(1,2,3) -- Set(2,3,4))      // Set(1)
    println(Set(1,2,3) &~ Set(2,3,4))      // Set(1)
    println(Set(1,2,3).diff(Set(2,3,4)) )  // Set(1)
  }
}

 

第4节 Map

Map(映射)是一系列键值对的容器;Scala 提供了可变的和不可变的两种版本的Map,分别定义在包 scala.collection.mutable 和 scala.collection.immutable 里;
默认情况下,Scala中使用不可变的 Map;
如果要使用可变Map,必须导入scala.collection.mutable.Map;
在Map中,键的值是唯一的,可以根据键来对值进行快速的检索。

package com.ch.part09

import scala.collection.mutable

object MapDemo {
  def main(args: Array[String]): Unit = {

    //使用两种方式定义Map
    val map1 = Map("a" -> 1, "b" -> 2)
    val map2 = Map(("a", 1), ("b", 2))

    map1.keys.foreach(println(_))       // a b
    map1.values.foreach(println(_))     // 1 2

    //如果访问不存在的Key值时,会抛出异常
    //    println(map1("c"))

    //也可以使用get方法,来获取与Key值相对应的Value值。
    //get方法会返回一个Option对象,要么要是Some(有值),要么是None(无值)
    val num: Option[Int] = map1.get("c")
    num match {
      case None => println("None")      // None
      case Some(x) => println(x)        //
    }

    //获取Key值所对应的Value值,如果键Key不存在,那么就返回指定的默认值
    val num2: Int = map1.getOrElse("d", 0)
    println(num2)         // 0

    //创建一个可变的Map
    val map3 = scala.collection.mutable.Map("a" -> 1, "b" -> 2)
    println(map3)     // Map(b -> 2, a -> 1)
    map3("a") = 10
    println(map3)     // Map(b -> 2, a -> 10)

    //增加一个元素
    map3("c") = 3
    println(map3)     // Map(b -> 2, a -> 10, c -> 3)

    //通过+=添加元素,-=删除元素
    map3 += ("d" -> 4, "f" -> 5)
    println(map3)     // Map(b -> 2, d -> 4, a -> 10, c -> 3, f -> 5)
    map3 -= "d"
    println(map3)     // Map(b -> 2, a -> 10, c -> 3, f -> 5)

    //将Key与Value的值互换
    val kv: mutable.Map[Int, String] = for ((k, v) <- map3) yield (v, k)
    println(kv)       // Map(2 -> b, 5 -> f, 10 -> a, 3 -> c)
    //推荐使用下面的方式将Key与value的值互换
    map3.map(x=>(x._2,x._1)).foreach(println(_))    // (2,b)   (5,f)   (10,a)   (3,c)

    //通过拉链操作创建Map
    val a=Array(1,2,3)
    val b=Array("a","b","c")
    val c: Array[(Int, String)] = a.zip(b)
    val d: Map[Int, String] = a.zip(b).toMap
    println(d)        //  Map(1 -> a, 2 -> b, 3 -> c)
  }
}

 

第5节 集合常用算子

5.1 遍历: map、foreach & mapValues

集合对象都有 foreach、map 算子。
两个算子的共同点在于:都是用于遍历集合对象,并对每一项执行指定的方法;
两个算子的差异点在于:
foreach无返回值(准确说返回void),用于遍历集合
map返回集合对象,用于将一个集合转换成另一个集合

// 使用 foreach 打印集合元素
val numlist = (1 to 10).toList
numlist.foreach(elem=>print(elem+" "))
numlist.foreach(print _)
numlist.foreach(print)

// 使用 map 对集合进行转换
numlist.map(_ > 2)       // res6: List[Boolean] = List(false, false, true, true, true, true, true, true, true, true)
numlist.map(_ * 2)       // res7: List[Int] = List(2, 4, 6, 8, 10, 12, 14, 16, 18, 20)

 

操作 Map集合时,mapValues用于遍历value,是map操作的一种简化形式;

// Range(20, 0, -2)用给定的步长值设定一个范围,从开始到结束(不包含)。
//Map(20 -> 0,18 -> 1,16 -> 2,14 -> 3,12 -> 4,10 -> 5,8 -> 6,6 -> 7,4 -> 8,2 ->9)
val map = Range(20, 0, -2).zipWithIndex.toMap

// 将map集合中的value值+100
map.map(elem => (elem._1, elem._2 + 100))
map.map{case (k,v) => (k, v+100)}

// mapValues的表达最简洁
map.mapValues(_+100)

 

5.2 展开: flatten & flatMap

flatten的作用是把嵌套的结构展开,把结果放到一个集合中;

在 flatMap 中传入一个函数,该函数对每个输入都返回一个集合(而不是一个元素),最后把生成的多个集合“拍扁”成为一个集合;

scala> val lst1 = List(List(1,2), List(3,4))
lst1: List[List[Int]] = List(List(1, 2), List(3, 4))

scala> lst1.flatten
res5: List[Int] = List(1, 2, 3, 4)

// flatten 把一个字符串的集合展开为一个字符集合,因为字符串本身就是字符的集合
scala> val lst4 = List("Java", "hadoop")
lst4: List[String] = List(Java, hadoop)

scala> lst4.flatten
res8: List[Char] = List(J, a, v, a, h, a, d, o, o, p)

// flatten 有效的处理 Some 和 None 组成的集合。它可以展开Some元素形成一个新的集合,同时去掉 None元素
scala> val x = Array(Some(1), None, Some(3), None)
x: Array[Option[Int]] = Array(Some(1), None, Some(3), None)

// 方法很多,flatten最简单
scala> x.flatten
res9: Array[Int] = Array(1, 3)

scala> x.collect{case Some(i) => i}
res10: Array[Int] = Array(1, 3)

scala> x.filter(!_.isEmpty).map(_.get)
res11: Array[Int] = Array(1, 3)
// 下面两条语句等价
val lst = List(List(1,2,5,6),List(3,4))

// 将 lst 中每个元素乘2,最后作为一个集合返回
// 此时 flatMap = flatten + map
//List(1,2,5,6,3,4)
lst.flatten.map(_*2)
lst.flatMap((x: List[Int]) => x.map(_*2))
lst.flatMap(_.map(_*2))

// 将字符串数组按空格切分,转换为单词数组
val lines = Array("Apache Spark has an advanced DAG execution engine",
"Spark offers over 80 high-level operators")

// 下面两条语句效果等价
//map算子产生的结果:Array(Array(Apache, Spark, has, an, advanced, DAG, execution, engine), Array(Spark, offers, over, 80, high-level, operators))
// flatten算子产生的结果:Array(Apache, Spark, has, an, advanced, DAG, execution, engine, Spark, offers, over, 80, high-level, operators)

lines.map(_.split(" ")).flatten

// 此时 flatMap = map + flatten
lines.flatMap(_.split(" "))

备注:flatMap = flatten + map 或 flatMap = map + flatten

 

5.3 偏函数并行计算: collect

collect通过执行一个并行计算(偏函数),得到一个新的数组对象

package com.ch.part09

/**
 * @author CH
 */
object CollectDemo {
  //通过下面的偏函数,把chars数组的小写a转换为大写的A
  val fun: PartialFunction[Char, Char] = {
    case 'a' => 'A'
    case x => x
  }

  def main(args: Array[String]): Unit = {
    val chars = Array('a', 'b', 'c')
    val newchars = chars.collect(fun)
    println("newchars:" + newchars.mkString(","))   // newchars:A,b,c
  }
}

 

5.4 归约: reduce

reduce可以对集合当中的元素进行归约操作;

还有 reduceLeft 和 reduceRight ,reduceLeft 从左向右归约,reduceRight 从右向左归约;

val lst1 = (1 to 10).toList
lst1.reduce(_+_)

// 为什么这里能出现两个占位符?
lst1.reduce(_+_)

// 我们说过一个占位符代表一个参数,那么两个占位符就代表两个参数。根据这个思路改写等价的语句
// x类似于buffer,缓存每次操作的数据;y每次操作传递新的集合元素
lst1.reduce((x, y) => x + y)

// 利用reduce操作,查找 lst1 中的最大值
lst1.reduce((x,y) => if (x>y) x else y)

// reduceLeft、reduceRight
lst1.reduceLeft((x,y) => if (x>y) x else y)
lst1.reduceRight((x,y) => if (x>y) x else y)

 

5.5 排序: sorted sortwith & sortby

Scala中对于集合的排序有三种方法:sorted、sortBy、sortWith

package com.ch.part09

/**
 * @author CH
 */
object SortDemo {
  def main(args: Array[String]): Unit = {

    val list = List(1, 9, 3, 8, 5, 6)
    
    //sorted方法对一个集合进行自然排序
    //sorted源码:def sorted[B >: A](implicit ord: Ordering[B]): Repr
    //源码中有两点值得注意的地方:
    // 1.sorted方法中有个隐式参数ord: Ordering。
    // 2.sorted方法真正排序的逻辑是调用的java.util.Arrays.sort
    val numSort: List[Int] = list.sorted
    println(numSort)
    
    //sortBy源码:def sortBy[B](f: A => B)(implicit ord: Ordering[B]): Repr = sorted(ord on f)
    //sortBy最后调用的sorted方法
    println(list.sortBy(x => x).reverse)
    
    //sortWith源码:def sortWith(lt: (A, A) => Boolean): Repr = sorted(Ordering fromLessThan lt)
    print(list.sortWith(_ > _))
  }
}

 

第6节 与Java集合的转换

使用 scala.collection.JavaConverters 与Java集合交互。它有一系列的隐式转换,添加了asJava和asScala的转换方法。

import scala.collection.JavaConverters._

val list: Java.util.List[Int] = List(1,2,3,4).asJava
val buffer: scala.collection.mutable.Buffer[Int] = list.asScala

 

 

第十部分 隐式机制

主要内容:
 1、隐式转换
 2、隐式转换函数
 3、隐式参数和隐式值

第1节 隐式转换

隐式转换和隐式参数是Scala中两个非常强大的功能,利用隐式转换和隐式参数,可以提供类库,对类库的使用者隐匿掉具体的细节。
Scala会根据隐式转换函数的签名,在程序中使用到隐式转换函数接收的参数类型定义的对象时,会自动将其传入隐式转换函数,转换为另外一种类型的对象并返回,这就是“隐式转换”。

  • 首先得有一个隐式转换函数
  • 使用到隐式转换函数接收的参数类型定义的对象
  • Scala自动传入隐式转换函数,并完成对象的类型转换

隐式转换需要使用implicit关键字。
使用Scala的隐式转换有一定的限制:

  • implicit关键字只能用来修饰方法、变量、参数
  • 隐式转换的函数只在当前范围内才有效。如果隐式转换不在当前范围内定义,那么必须通过import语句将其导入

Spark源码中有大量的隐式转换和隐式参数,因此必须掌握隐式机制。

 

第2节 隐式转换函数

Scala的隐式转换最核心的就是定义隐式转换函数,即implicit conversion function。
定义的隐式转换函数,只要在编写的程序内引入,就会被Scala自动使用。
隐式转换函数由Scala自动调用,通常建议将隐式转换函数的名称命名为“one2one”的形式。
示例1:下面代码中定义了一个隐式函数

class Num {}

class RichNum(num: Num) {
  def rich(): Unit = {
    println("Hello Implicit!")
  }
}

object ImplicitDemo {

  // 定义一个名称为num2RichNum的隐式函数
  implicit def num2RichNum(num: Num): RichNum = {
    new RichNum(num)
  }

  def main(args: Array[String]): Unit = {
    val num = new Num
    // num对象并没有rich方法,编译器会查找当前范围内是否有可转换的函数
    // 如果没有则编译失败,如果有则会调用。
    num.rich()
  }
}

 

示例2:导入隐式函数

package test.implicitdemo

object Int2String {
  implicit def int2String(num: Int):String = num.toString
}

 

下面代码中调用了String类型的length方法,Int类型本身没有length方法,但是在可用范围内定义了可以把Int转换为String的隐式函数int2String,因此函数编译通过并运行出正确的结果。
此示例中隐式函数的定义必须定义在使用之前,否则编译报错。

import test.implicitdemo.Int2String._

object ImplicitTest {
  def main(args: Array[String]): Unit = {
    println(20.length)
  }
}

通过import test.implicitdemo.Int2String._,将Int2StringTest内部的成员导入到相应的作用域内,否则无法调用隐式函数。

要实现隐式转换,只要在程序可见的范围内定义隐式转换函数即可,Scala会自动使用隐式转换函数。
隐式转换函数与普通函数的语法区别就是,要以implicit开头,而且最好要定义函数返回类型。

隐式转换案例:特殊售票窗口(只接受特殊人群买票,比如学生、老人等),其他人不能在特殊售票窗口买票。

package com.ch.part10

class SpecialPerson(var name: String)

class Older(var name: String)

class Student(var name: String)

class Worker(var name: String)

object ImplicitDemoTwo {

  // 调用这个方法时, 会判断传入的参数, 调用隐式转换函数
  def buySpecialTickWindow(person: SpecialPerson): Unit = {

    if (person != null) {
      println(person.name + "购买了一张特殊票!")
    } else {
      println("你不是特殊人群,不能在此购票!")
    }
  }

  //定义一个隐式转换函数
  implicit def any2SpecialPerson(any: Any): SpecialPerson = {
    any match {
      case any: Older => new SpecialPerson(any.asInstanceOf[Older].name)
      case any: Student => new SpecialPerson(any.asInstanceOf[Student].name)
      case _ => null
    }
  }

  def main(args: Array[String]): Unit = {

    val stu = new Student("jacky")
    val older = new Older("old man")
    val worker = new Worker("lisi")

    buySpecialTickWindow(stu)       // jacky购买了一张特殊票!
    buySpecialTickWindow(older)     // old man购买了一张特殊票!
    buySpecialTickWindow(worker)    // 你不是特殊人群,不能在此购票!
  }
}

 

第3节 隐式参数和隐式值

在函数定义的时候,支持在最后一组参数中使用 implicit ,表明这是一组隐式参数。在调用该函数的时候,可以不用传递隐式参数,而编译器会自动寻找一个 implicit 标记过的合适的值作为参数。
Scala编译器会在两个范围内查找:

  • 当前作用域内可见的val或var定义隐式变量
  • 隐式参数类型的伴生对象内隐式值
package com.ch.part10

object DoublyDemo {

  def print(num:Double)(implicit fmt:String): Unit ={
    println(fmt format(num))
  }

  def main(args: Array[String]): Unit = {

    print(3.245)("%.1f")    // 3.2

    //定义一个隐式变量
    implicit val printFmt="%.3f"
    print(3.24)                 // 3.240
  }
}

 

 

第十一部分 扩展部分

主要内容:
1、类型参数
         泛型类、泛型函数、协变和逆变
2、Akka

第1节 类型参数

Scala的类型参数与Java的泛型是一样的,可以在集合、类、函数中定义类型参数,从而保证程序更好的健壮性。

1.1 泛型类

泛型类,顾名思义,其实就是在类的声明中定义一些泛型类型,然后在类内部的字段或者方法,就可以使用这些泛型类型。

使用泛型类,通常是需要对类中的某些成员,比如某些字段和方法中的参数或变量进行统一的类型限制,这样可以保证程序更好的健壮性和稳定性。

如果不使用泛型进行统一的类型限制,那么在后期程序运行过程中难免会出现问题,比如传入了不希望的类型导致程序出问题。

在使用泛型类的时候,比如创建泛型类的对象,只需将类型参数替换为实际的类型即可。

Scala自动推断泛型类型特性:直接给使用泛型类型的字段赋值时,Scala会自动进行类型推断。

泛型类的定义如下:

package com.ch.part11

//定义一个泛型类
class Stack[T1, T2, T3](name: T1) {
  var age: T2 = _
  var address: T3 = _

  def getInfo: Unit = {
    println(s"$name,$age,$address")
  }
}

使用上述的泛型类,只需要使用具体的类型代替类型参数即可。

object GenericityDemo {
  def main(args: Array[String]): Unit = {
    //创建泛型类的对象, 下面的"lisi" 由scala自动推断类型
    val stack=new Stack[String,Int,String]("lisi")
    stack.age=20
    stack.address="北京"

    stack.getInfo
  }
}

 

1.2 泛型函数

泛型函数,与泛型类类似,可以给某个函数在声明时指定泛型类型,然后在函数体内,多个变量或者返回值之间,就可以使用泛型类型进行声明,从而对某个特殊的变量,或者多个变量,进行强制性的类型限制。

与泛型类一样,你可以通过给使用了泛型类型的变量传递值来让Scala自动推断泛型的实际类型,也可以在调用函数时,手动指定泛型类型。

 

案例:卡片售卖机,可以指定卡片的内容,内容可以是String类型或Int类型

object GenericityFunction {
  def getCard[T](content: T) = {
    content match {
      case content: Int => s"card:$content is Int "
      case content: String => s"card:$content is String"
      case _ => s"card:$content"
    }
  }

  def main(args: Array[String]): Unit = {
    println(getCard[String]("hello"))
    println(getCard(1001))
  }
}

 

1.3 协变和逆变

Scala的协变和逆变是非常有特色的,完全解决了Java中的泛型的一大缺憾!

举例来说,Java中,如果有Professional是Master的子类,那么Card[Professionnal]是不是Card[Master]的子类?

答案:不是。因此对于开发程序造成了很多的麻烦。

而Scala中,只要灵活使用协变和逆变,就可以解决Java泛型的问题。

 

协变定义形式如:trait List[+T] {}

当类型S是类型A的子类型时,则List[S]也可以认为是List[A}的子类型,即List[S]可以泛化为List[A],也就是被参数化,类型的泛化方向与参数类型的方向是一致的,所以称为协变(covariance)。

逆变定义形式如:trait List[-T] {}

当类型S是类型A的子类型,则Queue[A]反过来可以认为是Queue[S}的子类型,也就是被参数化类型的泛化方向与参数类型的方向是相反的,所以称为逆变(contravariance)。

小结:
如果A是B的子类,那么在协变中,List[A]就是List[B]的子类; 在逆变中,List[A]就是List[B]的父类

 

协变案例:只有大师以及大师级别以下的名片都可以进入会场

package com.ch.part11

//大师
class Master

//专家
class Professor extends Master

//讲师
class Teacher

//定义协变,
//class Card[+T]

//定义逆变
class Card[-T]

object ConvarianceDemo {
  //def enterMeet(card: Card[Master]): Unit = { // 协变
  def enterMeet(card: Card[Professor]): Unit = {  //逆变
      //只有Card[Master]和它的子类才能进入会场
      println("欢迎进入会场!")
  }

  def main(args: Array[String]): Unit = {
    val masterCard = new Card[Master]
    val professorCard = new Card[Professor]
    val teacherCard = new Card[Teacher]

    enterMeet(masterCard)
    enterMeet(professorCard)
//    enterMeet(teacherCard)    // 协变报错, 因为 Teacher 不是Master的子类
  }                             // 逆变报错, 因为 Teacher 不是 Professor 的父类
}

 

第2节 Akka 高并发、分布式和容错应用

Akka是Java虚拟机平台上构建高并发、分布式和容错应用的工具包和运行时。
Akka用Scala语言编写,同时提供了Scala和Java的开发接口。
Akka处理并发的方法基于Actor模型,Actor之间通信的唯一机制就是消息传递。

 

2.1 Actor

Scala的Actor类似于Java中的多线程编程。
但是不同的是,Scala的Actor提供的模型与多线程有所不同。Scala的Actor尽可能地避免锁和共享状态,从而避免多线程并发时出现资源争用的情况,进而提升多线程编程的性能。

Actor可以看作是一个个独立的实体,Actor之间可以通过交换消息的方式进行通信,每个Actor都有自己的收件箱(Mailbox)。

一个Actor收到其他Actor的信息后,根据需要作出各种相应。消息的类型可以是任意的,消息的内容也可以是任意的。

 

ActorSystem
在Akka中,ActorSystem是一个重量级的结构
它需要分配多个线程,所以在实际应用中,ActorSystem通常是一个单例对象,我们可以使用这个ActorSystem创建很多Actor。

 

Akka案例:

创建一个maven项目,在项目的pom文件中增加如下依赖:

(注意!! scala 的 sdk版本, 和 sccala.compat的版本要小版本一致!!)

    <!-- 定义一下常量-->
    <properties>
        <encoding>UTF-8</encoding>
        <scala.version>2.12.3</scala.version>
        <scala.compat.version>2.12</scala.compat.version>
        <akka.version>2.4.17</akka.version>
    </properties>


    <dependencies>
        <!-- 添加scala的依赖-->
        <dependency>
            <groupId>org.scala-lang</groupId>
            <artifactId>scala-library</artifactId>
            <version>${scala.version}</version>
        </dependency>

        <!-- 添加akka的actor依赖-->
        <dependency>
            <groupId>com.typesafe.akka</groupId>
            <artifactId>akka-actor_${scala.compat.version}</artifactId>
            <version>${akka.version}</version>
        </dependency>

        <!-- 多进程之间的Actor通信-->

        <dependency>
            <groupId>com.typesafe.akka</groupId>
            <artifactId>akka-remote_${scala.compat.version}</artifactId>
            <version>${akka.version}</version>
        </dependency>
    </dependencies>

    <!-- 指定插件-->
    <build>
        <!-- 指定源码包和测试包的位置-->
        <sourceDirectory>src/main/scala</sourceDirectory>
        <testSourceDirectory>src/test/scala</testSourceDirectory>
        <plugins>
            <!-- 指定编译scala的插件-->
            <plugin>
                <groupId>net.alchim31.maven</groupId>
                <artifactId>scala-maven-plugin</artifactId>
                <version>3.2.2</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>
                            <goal>testCompile</goal>
                        </goals>
                        <configuration>
                            <args>
                                <arg>-dependencyfile</arg>


                                <arg>${project.build.directory}/.scala_dependencies</arg>
                            </args>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

            <!-- maven打包的插件-->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>2.4.3</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>

                            <goal>shade</goal>
                        </goals>
                        <configuration>
                            <filters>
                                <filter>
                                    <artifact>*:*</artifact>
                                    <excludes>
                                        <exclude>META-INF/*.SF</exclude>
                                        <exclude>META-INF/*.DSA</exclude>
                                        <exclude>META-INF/*.RSA</exclude>
                                    </excludes>
                                </filter>
                            </filters>
                            <transformers>
                                <transformer  implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
                                    <resource>reference.conf</resource>
                                </transformer>
                                <!-- 指定main方法-->
                                <transformer  implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransfor mer">
                                    <mainClass></mainClass>
                                </transformer>
                            </transformers>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
import akka.actor.{Actor, ActorRef, ActorSystem, Props}

import scala.io.StdIn

class HelloActor extends Actor {

  // 重写
  //接收消息并进行处理
  override def receive: Receive = {
    case "吃了吗" => println("吃过了")
    case "吃的啥" => println("北京烤鸭")
    case "拜拜" => {

      //关闭自己
      context.stop(self)
      //关闭ActorSystem
      context.system.terminate()
    }
  }
}

object HelloActor {

  //通过ActorSystem创建线程池对象myFactory
  private val myFactory = ActorSystem("myFactory")

  //通过myFactory.actorOf来创建一个Actor
  private val helloActorRef: ActorRef = myFactory.actorOf(Props[HelloActor], "helloActor")

  def main(args: Array[String]): Unit = {

    var flag = true

    while (flag) {
      print("请输入发送的消息:")
      val consoleLine: String = StdIn.readLine()

      // ! 的作用: 发送消息
      helloActorRef ! consoleLine
      if (consoleLine.equals("拜拜")) {
        flag = false
        println("程序即将结束!")
      }

      //让程序休眠100毫秒
      Thread.sleep(100)
    }
  }
}

 

 

课程总结

  • 《Scala编程》课程共十一部分

课程目的:使用Scala进行Spark开发、阅读Spark源码

第一部分 Scala基础
第二部分 控制结构和函数
第三部分 数组和元组
第四部分 类与对象
第五部分 继承
第六部分 特质
第七部分 模式匹配和样例类
第八部分 函数及抽象化
第九部分 集合
第十部分 隐式机制
第十一部分 扩展部分

  • Scala功能强大,内容很多,还有一些内容课程中没有涉及
  • 这门课程作为学习Scala的起点,而非终点

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值