Scala 快速入门(2)

特别说明,本文主要参考 尚硅谷大数据之韩顺平Scala视频及对应资料,本文是笔者对应的学习笔记。

1. Scala 概述

Scala 有些类是对 Java 类的封装,所以 Scala 依赖 JDK,使用 Scala,需要先装 JDK。Scala 编译成 Java 字节码,运行在 JVM 之上。

可去官网下载对应操作系统及对应版本的 JDK 和 Scala,安装/解压,并配置环境变量。一般在 Windows 开发,在 Linux 部署。一般使用 IDEA 开发(需要安装 Scala 插件)。

第一个 Scala 程序

方式一:Notepad++ 写程序

// HelloScala.scala
// 第一个 Scala 程序
object HelloScala {
    
    
	def main(args: Array[String]): Unit = {
    
    
		println("Hello, Scala!")
	}
}
// 编译+运行
scalac HelloScala.scala
scala HelloScala

在这里插入图片描述

方式二:IDEA 写程序

  1. 建立 Maven 项目
  2. 在 main 目录下建立 scala 文件夹并右键标记成 Sources Root
  3. 在项目上右键 Add Framework Support 添加 Scala 支持
  4. 书写 Scala 程序
    在这里插入图片描述

输出 Demo

package com.example.chapter01

object PrintDemo {
    
    
  def main(args: Array[String]): Unit = {
    
    
    val str1: String = "Hello"; val str2: String = "Scala"  // 同一行多条语句需要分号隔开
    println(str1 + ", " + str2)val name: String = "Tom"
    val age: Int = 29
    val salary: Float = 2.9f

    printf("name: %s\tage: %d\tsalary: %.2f\n", name, age, salary)  // 格式化输出

    println(s"name: $name\tage: $age\tsalary: $salary")

    println(s"name: $name\tage: ${age+10}\tsalary: ${salary*2}")
	
	println((10.0 / 3).formatted("%.2f"))
  }
}

输入 Demo

package com.example.chapter01

import scala.io.StdIn

object InputDemo {
    
    
  def main(args: Array[String]): Unit = {
    
    
    println("Input your name:")
    val name: String = StdIn.readLine

    println("Input your age:")
    val age: Int = StdIn.readInt

    println("Input your salary:")
    val salary: Double = StdIn.readDouble

    println(s"name=$name")
    println(s"age=$age")
    println(s"salary=$salary")
  }
}

关联源码

本文使用的是 2.11.12 版本,官方下载源码,IDEA 上点击关联源码的解压文件。

2. 变量

val/var

package com.example.chapter02.vars

object VarDemo01 {
    
    
  def main(args: Array[String]): Unit = {
    
    
    var num = 10  // 类型推断
    println(num.isInstanceOf[Int])  // true
    println(num.isInstanceOf[Double]) // false

    //类型确定后,就不能修改,说明 Scala 是强数据类型语言
//    num = 2.3 // 编译错误

    num = 10  // var 可变

    val num2 = 1
//    num2 = 2  // 编译错误,val 不可变
  }
}

数据类型

在这里插入图片描述

  • Scala 与 Java 有着相同的数据类型,在 Scala 中数据类型都是对象, Scala 没有 java 中的原生类型。
  • Unit 等同于 Java 的 void,只有一个实例值:()。
  • 在开发中通常可将 Nothing 类型的值返回给任意变量或函数,抛异常用的多。
  • Nothing 类型没有实例。它对于泛型结构是有用处的。比如,空列表 Nil 的类型是 List[Nothing],它是 List[T]的子类型,T 可以是任何类。
  • Null 类型的唯一实例就是 null 对象。可以将 null 赋值给任何引用,但不能赋值给值类型的变量。
    在这里插入图片描述
    在这里插入图片描述

3. 运算符

略(同 Java)

4. 程序流程控制

for

package com.example.chapter04

object ForDemo01 {
    
    
  def main(args: Array[String]): Unit = {
    
    
    for(i <- 1 to 5) {
    
      // 闭区间
      print(i + " ")
    } // 1 2 3 4 5
    println("\n--------------------")

    for(i <- 1 until 5) {
    
     // 左闭右开
      print(i + " ")
    } // 1 2 3 4
    println("\n--------------------")

    val list = List("hello", 1, 2, "hah")
    for(item <- list) {
    
    
      print(item + " ")
    }// hello 1 2 hah
    println("\n--------------------")

    /**
     * 等价代码:
     * for(i <- 1 to 5) {
     *  if(i % 2 == 0) {
     *    print(i + " ")
     *  }
     * }
     */
    for(i <- 1 to 5 if i % 2 == 0) {
    
    
      print(i + " ")
    }// 2 4
    println("\n--------------------")

    /**
    * 等价代码:
    * for(i <- 1 to 5) {
    *   val j = 5 - i
    *   print(j + " ")
    * }
    *
    * 等价代码:
    * for{
    *   i <- 1 to 5
    *   j = 5 - i
    * }{
    *
    * }
    * */
    for(i <- 1 to 5; j = 5 - i) {
    
    
      print(j + " ")
    }// 4 3 2 1 0
    println("\n--------------------")

    /**
     * 等价代码:
     * for(i <- 1 to 5) {
     *  for(j <- 1 to 5) {
     *
     *  }
     * }
     */
    for(i <- 1 to 5; j <- 1 to 5) {
    
    

    }
    println("\n--------------------")

    val res = for(i <- 1 to 5) yield {
    
    
      if(i % 2 == 0) {
    
    
        i
      } else {
    
    
        "odd"
      }
    }
    println(res)
    println("\n--------------------")

    for(i <- Range(1, 5, 2)) {
    
      // 控制步长
      print(i + " ")
    }// 1 3
    println("\n--------------------")
  }
}

实现类似 break/continue 的功能

Scala 内置控制结构特地去掉了 break 和 和 continue。

package com.example.chapter04

import scala.util.control.Breaks._

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

    /**
     * 类似代码:
     * for(i <- 1 to 5) {
     *  if(i != 3) {
     *    print(i + " ")
     *  }
     * }
     */
    for(i <- 1 to 5 if(i != 3)) {
    
     // 实现 Java 的 continue 功能
      print(i + " ")
    }
    println("\n--------------------------")

    /**
     * def breakable(op: => Unit)
     *
     * breakable 高阶函数,函数的参数就是一个函数 op,op 无参且返回值为 Unit
     * breakable 函数对 op 这段代码块捕获异常,如果不是 breakException 就抛出,否则不处理异常,中断当前程序执行
     */
    breakable {
    
     // 传入的参数是代码块时,可把 () 改成 {}
      for (i <- 1 to 5) {
    
    
        if (i == 3) {
    
    
          break() // def break(): Nothing = { throw breakException }
        }
        print(i + " ")
      }
    }
    println("\nbroken")
  }
}

5. 函数式编程(基础)

第一个函数

package com.example.chapter05.fun

object FunDemo01 {
    
    
  def main(args: Array[String]): Unit = {
    
    
    val n1 = 10
    val n2 = 20
    println(getRes(n1, n2, '+'))  // 30
    println(getRes(n1, n2, '-'))  // -20
    println(getRes(n1, n2, '*'))  // null
  }

  def getRes(n1: Int, n2: Int, oper: Char) = {
    
    
    if(oper == '+') {
    
    
      n1 + n2
    } else if (oper == '-') {
    
    
      n1 - n2
    } else {
    
    
      null
    }
  }
}

注意事项

注意事项

//如果写了 return ,返回值类型就不能省略
def getSum(n1: Int, n2: Int): Int = {
    
    
return n1 + n2
}

// 如果没有指定实参,则会使用默认值。如果指定了实参,则实参会覆盖默认值
def sayOk(name : String = "jack"): String = {
    
    
return name + " ok! "
}

// 递归函数未执行之前无法推断出来结果类型,在使用时必须有明确的返回值类型
def f(n: Int): Int = {
    
    
  if(n <= 1) {
    
    
    1
  } else {
    
    
    f(n-1) + f(n -2 )
  }
}

//支持 0 到多个参数
def sum(args :Int*) : Int = {
    
    
}
//支持 1 到多个参数
def sum(n1: Int, args: Int*) : Int = {
    
    
}

// 返回类型为 Unit 的函数称为过程(procedure),无返回值,可写成:
//  def f(name: String) {
    
    
//    println(name + " hello")
//  }
def f(name: String): Unit = {
    
    
  println(name + " hello")
}

// 未声明返回类型,但有等号,该函数实际有返回值(对最后一行代码类型推断),不是过程
def f(name: String) = {
    
    
  1
}
注意事项(函数多个参数并且有参数具备默认值)

package com.example.chapter05.fun

/**
 * 如果函数存在多个参数,每一个参数都可以设定默认值,那么这个时候,传递的参数到底是覆
 * 盖默认值,还是赋值给没有默认值的参数,就不确定了(默认按照声明顺序[从左到右])。在
 * 这种情况下,可以采用带名参数
 */
object FunParameterDetail {
    
    
  def main(args: Array[String]): Unit = {
    
    
    mysqlCon()
    mysqlCon("127.0.0.1", 7777) //从左到右覆盖
    mysqlCon(user = "tom", pwd = "123")
//    f6("v2") // 错误!从左到右覆盖,p1 是 "v2",但 p2 未被赋值
    f6(p2="v2") // (?)
  }

  def mysqlCon(add:String = "localhost",port : Int = 3306,
               user: String = "root", pwd : String = "root"): Unit = {
    
    
    println("add=" + add)
    println("port=" + port)
    println("user=" + user)
    println("pwd=" + pwd)
  }
  def f6 ( p1 : String = "v1", p2 : String ) {
    
    
    println(p1 + p2);
  }
}

lazy

package com.example.chapter05.fun

object LazyDemo01 {
    
    
  def main(args: Array[String]): Unit = {
    
    
    // lazy 不能修饰 var 类型的变量
    lazy val res = sum(1, 2)  // 此句代码不会执行 sum 函数
//     println(res) // 用到 res 才会执行 sum 函数

    // 变量值得分配也会推迟到首次用的时候,比如 lazy val i = 10
  }
  def sum(n1: Int, n2: Int): Int = {
    
    
    println("sum(n1: Int, n2: Int): Int 执行了...")
    n1 + n2
  }
}

异常

package com.example.chapter05.fun

object ExceptionDemo {
    
    
  def main(args: Array[String]): Unit = {
    
    
    try {
    
    
      val r = 1 / 0
    } catch {
    
     // 在 scala 中只有一个 catch
      case ex: ArithmeticException => {
    
     // 小异常写在大异常前面,否则虽然不会报错,但这不是好的编程风格
        println("ArithmeticException: divided by 0")
      }
      case ex: Exception => {
    
    
        println("Exception")
      }
    } finally {
    
    
      println("finally")
    }

    println("continue...")

    try {
    
     // 捕获方法调用可能遇到的异常,避免程序异常终止
      f
    } catch {
    
    
      case ex: NumberFormatException => {
    
    
        println("To deal with NumberFormatException...")
      }
      case ex: Exception => {
    
    
        println("Exception")
      }
    }

    println("continue...")
  }

  // 声明可能出现的异常,有助于该方法的调用者捕获异常,避免程序异常终止
  @throws(classOf[NumberFormatException])
  def f {
    
    
    "abc".toInt
  }
}

6. 面向对象编程(基础)

可以利用 jd-gui 等反编译工具把 Scala 代码编译成的 .class 文件反编译成 Java 代码,利用 Java 代码读懂 Scala 在背后干了啥。(如果 jd-gui 执行提示需要下载 jdk 1.7,那就根据对应指引下载安装即可)

第一个面向对象程序

package com.example.chapter06.oop

object CatDemo {
    
    
  def main(args: Array[String]): Unit = {
    
    
    val cat = new Cat
    cat.name = "小白" // 实际调用了对应的方法(见下面反编译代码)
    cat.age = 3 // 实际调用了对应的方法(见下面反编译代码)
    cat.color = "白色"  // 实际调用了对应的方法(见下面反编译代码)

    println(s"Cat[name=${cat.name}, age=${cat.age}, color=${cat.color}]")
  }
}
// 类默认就是 public,但不能写 public
class Cat {
    
    
  var name: String = ""
  var age: Int = _  // _ 表示给 age 默认值,Int 对应的默认值是 0 (Byte/Short/Int/Long 默认值是 0,Float/Double 默认值是 0.0)
  var color: String = _ // _ 表示给 color 默认值,String 对应的默认值是 null (引用类型默认是 null)
}

// 使用 jd-gui 反编译 Cat.class 得到下面 Java 代码:
/*
package com.example.chapter06.oop;

import scala.reflect.ScalaSignature;

@ScalaSignature(bytes="\006\001}2A!\001\002\001\027\t\0311)\031;\013\005\r!\021aA8pa*\021QAB\001\nG\"\f\007\017^3saYR!a\002\005\002\017\025D\030-\0349mK*\t\021\"A\002d_6\034\001a\005\002\001\031A\021Q\002E\007\002\035)\tq\"A\003tG\006d\027-\003\002\022\035\t1\021I\\=SK\032DQa\005\001\005\002Q\ta\001P5oSRtD#A\013\021\005Y\001Q\"\001\002\t\017a\001\001\031!C\0013\005!a.Y7f+\005Q\002CA\016\037\035\tiA$\003\002\036\035\0051\001K]3eK\032L!a\b\021\003\rM#(/\0338h\025\tib\002C\004#\001\001\007I\021A\022\002\0219\fW.Z0%KF$\"\001J\024\021\0055)\023B\001\024\017\005\021)f.\033;\t\017!\n\023\021!a\0015\005\031\001\020J\031\t\r)\002\001\025)\003\033\003\025q\027-\\3!\021%a\003\0011AA\002\023\005Q&A\002bO\026,\022A\f\t\003\033=J!\001\r\b\003\007%sG\017C\0053\001\001\007\t\031!C\001g\0059\021mZ3`I\025\fHC\001\0235\021\035A\023'!AA\0029BaA\016\001!B\023q\023\001B1hK\002B\021\002\017\001A\002\003\007I\021A\r\002\013\r|Gn\034:\t\023i\002\001\031!a\001\n\003Y\024!C2pY>\024x\fJ3r)\t!C\bC\004)s\005\005\t\031\001\016\t\ry\002\001\025)\003\033\003\031\031w\016\\8sA\001")
public class Cat
{
  public String name()
  {
    return this.name;
  }

  public void name_$eq(String x$1)
  {
    this.name = x$1;
  }

  private String name = "";
  private int age;
  private String color;

  public int age()
  {
    return this.age;
  }

  public void age_$eq(int x$1)
  {
    this.age = x$1;
  }

  public String color()
  {
    return this.color;
  }

  public void color_$eq(String x$1)
  {
    this.color = x$1;
  }
}
*/

构造器

class 类名(形参列表) {
    
     // 主构造器,会执行类定义中的所有语句
	// 类体
	def this(形参列表) {
    
     // 辅助构造器,
	}
	def this(形参列表) {
    
     //辅助构造器可以有多个,重载
	}
}
class Person {
    
      // 主构造器无参,可省略(),定义私有/保护主构造器可 class Person private/protected (Scala 中 protected 只有子类可访问,同包不可访问,与 Java 不同)
  var name: String = _
  var age: Int = _

  def this(name: String) {
    
    
    this()  // 辅助构造器一定得先调用主构造器
    this.name = name
  }
  def this(age: Int) {
    
    
    this("hah") // 通过调用 def this(name: String) 间接调用主构造器
    this.age = age
  }
  def this(name: String, age: Int) {
    
    
    this()
    this.name = name
    this.age = age
  }
}
主构造器参数
1) Scala 类的主构造器的形参未用任何修饰符修饰,那么这个参数是局部变量。
2) 如果参数使用 val 关键字声明,Scala 会将参数作为类的私有的只读属性使用(有 get 方法,无 set 方法)。
3) 如果参数使用 var 关键字声明,那么那么 Scala 会将参数作为类的成员属性使用(有 get 方法,也有 set 方法)。

7. 面向对象编程(中级)

  • Scala 为了简化开发,当声明属性 var 时,本身就自动提供了对应 setter/getter 方法,如果属性声明为 private 的,那么自动生成的 setter/getter 方法是 private 的,如果属性省略访问权限修饰符,那么自动生成的 setter/getter 方法是 public 的。

  • 子类继承了所有的属性,只是私有的属性不能直接访问。

包的引入

// 了解下包,Scala 里的包挺复杂,还有包对象,一个文件都能定义多重包。。暂且不深入研究

//这里我们增加一个包访问权限,下面 private[visit]:
// 1,仍然是 private 
// 2. 在 visit 包(包括子包)下也可以使用 name ,相当于扩大访问范围
protected[visit] val name = "jack"
  1. 在 Scala 中,import 语句可以出现在任何地方,并不仅限于文件顶部。import 语句的作用一直延伸到包含该语句的块末尾。这种语法的 好处是,在需要时在引入包, 缩小 import 包的作用范围,提高效率。

    class User {
          
          
    	import scala.beans.BeanProperty
    	// @BeanProperty 修饰后,可像 Java 一样用 setName/getName 访问属性,仅仅是提供和 Java 的形式一致性
    	@BeanProperty var name : String = ""
    }
    
  2. Java 中如果想要导入包中所有的类,可以通过通配符*,Scala 中采用_。

  3. 如果不想要某个包中全部的类,而是其中的几个类,可以采用大括号。

    def test(): Unit = {
          
          
    	//可以使用选择器,选择引入包的内容,这里,我们只引入 HashMap, HashSet
    	import scala.collection.mutable.{
          
          HashMap, HashSet}
    	var map = new HashMap()
    	var set = new HashSet()
    }
    
  4. 如果引入的多个包中含有相同的类,那么可以将不需要的类进行重命名进行区分。

    def test2(): Unit = {
          
          
    	//下面的含义是 将 java.util.HashMap 重命名为 JavaHashMap
    	import java.util.{
          
           HashMap=>JavaHashMap, List}
    	import scala.collection.mutable._
    	var map = new HashMap() // 此时的 HashMap 指向的是 scala 中的 HashMap
    	var map1 = new JavaHashMap() // 此时使用的 java 中 HashMap 的别名
    }
    
  5. 如果某个冲突的类根本就不会用到,那么这个类可以直接隐藏掉。

    import java.util.{
          
           HashMap=>_, _} // 含义为引入 java.util 包的所有类,但是忽略 HahsMap 类.
    var map = new HashMap() // 此时的 HashMap 指向的是 scala 中的 HashMap
    

Scala 中类型检查和转换

classOf[String] 就如同 Java 的 String.class
obj.isInstanceOf[T] 就如同 Java 的 obj instanceof T 判断 obj 是不是 T 类型
obj.asInstanceOf[T] 就如同 Java 的 (T)obj 将 obj 强转成 T 类型
println(classOf[String])  // class java.lang.String
println("Scala".getClass.getName) // java.lang.String (反射)
package com.example.chapter07

object TypeConvertCase {
    
    
  def main(args: Array[String]): Unit = {
    
    
    val student = new Student
    val employee = new Employee
    test(student)
    test(employee)
    test(null)
    test(new Person)
  }
  def test(p: Person): Unit = {
    
    
    if(p == null) {
    
    
      return
    }
    if(p.isInstanceOf[Student]) {
    
    
      p.asInstanceOf[Student].info
    } else if (p.isInstanceOf[Employee]) {
    
    
      p.asInstanceOf[Employee].info
    } else {
    
    
      p.info
    }
  }
}

class Person {
    
    
  def info = println("Person")
}
class Student extends Person {
    
    
  override def info = println("Student")
}
class Employee extends Person {
    
    
  override def info = println("Employee")
}

Scala 中对象的构造过程

只有主构造器可以调用父类的构造器,辅助构造器不能直接调用父类的构造器。

package com.example.chapter07

object ObjectMadeProcess {
    
    
  def main(args: Array[String]): Unit = {
    
    
    val employee1 = new Employee66
    println("------------------------")
    val employee2 = new Employee66("Tom")
  }
}
class Person66(pName:String) {
    
    
  var name = pName
  println("父类主构造器...")
  def this() {
    
    
    this("父类辅助构造器...")
    println("父类辅助构造器...")
  }
}
class Employee66 extends Person66 {
    
    
  println("子类主构造器....")

  def this(name: String) {
    
    
    this // 必须调用主构造器
    this.name = name
    println("子类辅助构造器...")
  }
}
/*
父类主构造器...
父类辅助构造器...
子类主构造器....
------------------------
父类主构造器...
父类辅助构造器...
子类主构造器....
子类辅助构造器...
*/
package com.example.chapter07

object ObjectMadeProcess02 {
    
    
  def main(args: Array[String]): Unit = {
    
    
    val employee1 = new Employee67("Tom")
    println("------------------------")
    val employee2 = new Employee67("Jack", 99)
    employee2.info
  }
}
class Person67(pName:String) {
    
    
  var name = pName
  println("父类主构造器...")
  def this() {
    
    
    this("父类辅助构造器...")
    println("父类辅助构造器...")
  }
}
class Employee67(eName: String, age: Int) extends Person67(eName) {
    
    
  println("子类主构造器....")

  def this(name: String) {
    
    
    this(name, 10) // 必须调用主构造器
    this.name = name
    println("子类辅助构造器...")
  }

  def info: Unit = {
    
    
    println(s"雇员的名字是${this.name}")
  }
}
/*
父类主构造器...
子类主构造器....
子类辅助构造器...
------------------------
父类主构造器...
子类主构造器....
雇员的名字是Jack
*/

字段重写

Java 中只有方法重写,没有字段重写。

package test;
/**
 * Java 动态绑定机制
 * 1.如果调用的是方法,则 JVM 会将该方法和对象的内存地址绑定
 * 2.如果调用的是一个属性,则没有动态绑定机制,在哪里调用,就返回对应值
 */
public class DynamicBind {
    
    
    public static void main(String[] args) {
    
    
        AA obj = new BB();
        System.out.println(obj.sum()); //40 //? 30
        System.out.println(obj.sum1()); //30 //? 20
    }
}

class AA {
    
    
    public int i = 10;
    public int sum() {
    
      // 取决于调用的是 AA 的 getI 还是 BB 的 getI,分别对应 10+10 和 20+10
        return getI() + 10;
    }
    public int sum1() {
    
     // 20
        return i + 10;
    }
    public int getI() {
    
     // 10
        return i;
    }
}
class BB extends AA {
    
    
    public int i = 20;  // 父类的 i 可用 super 获取
//    public int sum() {    // 40
//        return i + 20;
//    }
    public int getI() {
    
     // 20
        return i;
    }
//    public int sum1() {   // 30
//        return i + 10;
//    }
}

Scala 覆盖/重写方法/字段

package com.example.chapter07

object ScalaFieldOverride01 {
    
    
  def main(args: Array[String]): Unit = {
    
    
    val a:A = new B
    val b: B = new B
    println(s"a.n=${a.n}\tb.n=${b.n}")  // a.n=11	b.n=11
    println(s"a.f=${a.f}\tb.f=${b.f}")  // a.f=22 b.f=22
    println(s"a.f=${a.fun}\tb.f=${b.fun}")  // a.fun=33 b.fun=33
  }
}
class A {
    
    
  val n: Int = 1
  def f(): Int = {
    
    
    2
  }
  def fun(): Int = {
    
    
    3
  }
}
class B extends A {
    
    
  override val n: Int = 11 // val 覆盖 val(val 只能覆盖 val 或 无参 def)
  override val f: Int = 22 // val 覆盖无参 def(val 只能覆盖 val 或 无参 def)
  override def fun: Int = 33  // def 只能覆盖 def
}

/**
 * 反编译,下面的 class C 的 name 实际生成了两个抽象方法(set/get)
 * public abstract class C {
 *  public abstract String name();
 *  public abstract void name_$eq(String paramString);
 * }
 */
abstract class C {
    
    
  var name: String  // 抽象字段(未初始化的字段)
}

/**
 * public class D extends C {
 *  public void name_$eq(String x$1) {
 *      this.name = x$1;
 *  }
 *  public String name() {
 *      return this.name;
 *  }
 *  private String name = "hah";
 * }
 */
class D extends C {
    
    
  // var 只能重写/覆盖抽象的 var
  override var name: String = "hah" // override 可省略,本质是对抽象方法的实现,非覆盖/重写
}

抽象类

package com.example.chapter07

/**
 * 1. 抽象类不能实例化
 * 2. 抽象类可以没有抽象方法
 * 3. 一旦类包含了抽象方法或者抽象字段,这个类必须声明为 abstract
 * 4. 抽象方法不能有主体,不允许使用 abstract 修饰
 * 5. 如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法和抽象字段,除非它自己也声明为 abstract 类
 * 6. 抽象方法和抽象字段不能使用 private、final 来修饰,因为这些关键字都是和重写/实现相违背的
 * 7. 抽象类中可以有普通方法
 * 8. 子类重写(实现)抽象方法不需要 override,写上也不会错
 */
object AbstractClassDemo {
    
    
  def main(args: Array[String]): Unit = {
    
    

    // 抽象类的匿名实现
    new Animal {
    
    
      override var name: String = "Dog Shi"
      override var age: Int = 3

      override def say(): Unit = {
    
    
        println(s"$name, $age years old, is saying...")
      }
    }.say()
  }
}
//抽象类
abstract class Animal{
    
    
  var name : String //抽象字段
  var age : Int // 抽象字段
  var color : String = "black" //普通属性
  def say() //抽象方法,不要标记 abstract
}

8. 面向对象编程(高级)

伴生类/伴生对象

package com.example.chapter08

object AccompanyObject {
    
    
  def main(args: Array[String]): Unit = {
    
    
    println(ScalaPerson.sex)
    ScalaPerson.hi()
  }
}

/**
 * 1. 同一个文件中,有 class ScalaPerson 和 object ScalaPerson
 * 2. class ScalaPerson 是伴生类,object ScalaPerson 是伴生对象
 * 3. 伴生对象中是静态内容,伴生类中是非静态内容
 * 4. 伴生类编译成 ScalaPerson.class
 * public class ScalaPerson {
 *  private String name;
 *
 *  public void name_$eq(String x$1) {
 *     this.name = x$1;
 *  }
 *
 *  public String name() {
 *    return this.name;
 *  }
 *
 *  public static boolean sex() {
 *    return ScalaPerson..MODULE$.sex();
 *  }
 *
 *  public static void sex_$eq(boolean paramBoolean) {
 *    ScalaPerson..MODULE$.sex_$eq(paramBoolean);
 *  }
 *
 *  public static void hi() {
 *     ScalaPerson..MODULE$.hi();
 *  }
 * }
 * 5. 伴生对象编译成 ScalaPerson$.class
 * public final class ScalaPerson$ {
 *   public static final  MODULE$;
 *   private boolean sex;
 *
 *   public boolean sex() {
 *    return this.sex;
 *   }
 *
 *   public void sex_$eq(boolean x$1) {
 *     this.sex = x$1;
 *   }
 *
 *   public void hi() {
 *     Predef..MODULE$.println("Hi...");
 *   }
 *
 *   private ScalaPerson$() {
 *     MODULE$ = this;this.sex = true;
 *   }
 *
 *   static {
 *     new ();
 *   }
 * }
 */
class ScalaPerson {
    
    
  var name: String = _
}
object ScalaPerson {
    
    
  var sex: Boolean = true

  def hi() = {
    
    
    println("Hi...")
  }
}

package com.example.chapter08

object ApplyDemo {
    
    
  def main(args: Array[String]): Unit = {
    
    
    val pig = new Pig("Pig")

    // 使用 apply 方法创建对象
    val pig11 = Pig() // () 不可省略
    val pig22 = Pig("Pig22")

    println(s"pig.name=${pig.name}")
    println(s"pig11.name=${pig11.name}")
    println(s"pig22.name=${pig22.name}")
  }
}

class Pig(pName: String) {
    
    
  var name: String = pName
}
object Pig {
    
    
  def apply(pName: String): Pig = new Pig(pName)
  def apply(): Pig = new Pig("Anonymous")
}

trait

  • 理解 trait 等价于 Java 的 interface + abstract class。
  • 在 scala 中,java 的接口都可以当做 trait 来使用。
  • 所有混入某 trait (B)的类(A),会自动成为 B 所继承的父类(C)的子类,class A extends D with B,此时,A 继承了 D 和 C,要求 D 是 C 的子类,否则 A 就多继承了。
1) 没有父类
class 类名 extends 特质 1 with 特质 2 with 特质 3 ..
2) 有父类
class 类名 extends 父类 with 特质 1 with 特质 2 with  特质 3

混入 trait

package com.example.chapter08

object MixInDemo {
    
    
  def main(args: Array[String]): Unit = {
    
    
    val oracleDB = new OracleDB with Operate  // 混入 trait
    oracleDB.insert(100)

    val mySQLDB = new MySQLDB with Operate  // 混入 trait
    mySQLDB.insert(200)

    val mySQLDB_ = new MySQLDB_ with Operate {
    
      // 混入 trait
      override def say(): Unit = {
    
    
        println("say...")
      }
    }
    mySQLDB_.insert(999)
    mySQLDB_.say()
  }
}

trait Operate {
    
    
  def insert(id: Int): Unit = {
    
    
    println(s"insert $id")
  }
}
class OracleDB
abstract class MySQLDB
abstract class MySQLDB_ {
    
    
  def say()
}

继承多个 trait

package com.example.chapter08

object AddTraits {
    
    
  def main(args: Array[String]): Unit = {
    
    
    // 1. 构建动态混入的对象时,从左到右执行
    // 2. 执行动态混入对象的方法时,从后面的 trait 执行(即从右到左)
    // 3. 当执行到 super 时,指的是其左边的 trait,如果左边没有 trait,则 super 就是父 trait
    //    本例中,File4 中调用 super.insert 会执行左边 DB4 的 insert,DB4 左边没 trait,就执行 Data4 的 insert
    val mysql = new MySQL4 with DB4 with File4
    println(mysql)

    mysql.insert(666)
  }
}

trait Operate4 {
    
    
  println("Operate4...")

  def insert(id: Int)
}
trait Data4 extends Operate4 {
    
    
  println("Data4...")

  override def insert(id: Int): Unit = {
    
    
    println(s"insert $id")
  }
}
trait DB4 extends Data4 {
    
    
  println("DB4")

  override def insert(id: Int): Unit = {
    
    
    println("into database")
    super.insert(id)
  }
}

trait File4 extends Data4 {
    
    
  println("File4")

  override def insert(id: Int): Unit = {
    
    
    println("into file")
    super.insert(id)
  }
}

class MySQL4

trait 构造顺序

package com.example.chapter08

object MixInSeq {
    
    
  def main(args: Array[String]): Unit = {
    
    
    // 此方式实际是构建类对象, 在混入特质时,该对象还没有创建。
    new FF
    println("----------------------")
    // 此方式实际是构造匿名子类,可以理解成在混入特质时,对象已经创建了。
    new KK with CC with DD
  }
}
trait AA {
    
    
  println("AA")
}
trait BB extends AA {
    
    
  println("BB")
}
trait CC extends BB {
    
    
  println("CC")
}
trait DD extends CC {
    
    
  println("DD")
}
class EE {
    
    
  println("EE")
}
class FF extends EE with CC with DD {
    
    
  println("FF")
}
class KK extends EE {
    
    
  println("KK")
}
/*
EE
AA
BB
CC
DD
FF
----------------------
EE
KK
AA
BB
CC
DD
*/

嵌套类

9. 隐式转换和隐式值

隐式函数

package com.example.chapter09

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

    /**
     * 1. 隐式转换函数的函数名可以是任意的, 隐式转换与函数名称无关,只与函数参数类型和返回值类型有关。
     * 2. 隐式函数可以有多个,但是需要保证在当前环境下,只有一个隐式函数能被识别。
     */
    implicit def Double2Int1(d: Double): Int = {
    
    
      d.toInt
    }
    // 如果下面再定义一个参数是 Double 返回值是 Int 的函数,后面的 3.5 无法完成隐式转换,不知道选哪个隐式函数!
//    implicit def Double2Int2(d: Double): Int = {
    
    
//      1
//    }

    val num: Int = 3.5  // 隐式函数 Double2Int 自动把 3.5 转成 3
    println(num)
  }
}


用隐式函数丰富类库

package com.example.chapter09

object ImplicitDemo02 {
    
    
  def main(args: Array[String]): Unit = {
    
    
    val mysql: MySQL = new MySQL
    mysql.insert

    // 搞个 MySQL 类型的,就能自动生成一个 DB,就具备了 DB 的 delete 方法和 update 方法
    implicit def enhanceMySQL(mysql: MySQL): DB = {
    
    
      new DB
    }
    mysql.delete
    mysql.update
  }
}
class MySQL {
    
    
  def insert(): Unit = {
    
    
    println("insert")
  }
}
class DB {
    
    
  def delete(): Unit = {
    
    
    println("delete")
  }
  def update(): Unit = {
    
    
    println("update")
  }
}

隐式值

入门示例

package com.example.chapter09

object ImplicitValDemo03 {
    
    
  def main(args: Array[String]): Unit = {
    
    
    implicit val str1: String = "Tom" //这个就是隐式值

    // 调用时,若不传参数就使用隐式值 str1
    def hello(implicit name: String): Unit = {
    
    
      println(s"Hello, $name!")
    }

    hello
  }
}

一个案例说明隐式值 ,默认值,传值的优先级

  1. 当程序中同时有隐式值,默认值,传值
  2. 编译器的优先级为传值 > 隐式值 > 默认值
  3. 在隐式值匹配时,不能有二义性
  4. 如果隐式值,默认值,传值都没有,报错
package com.example.chapter09

object ImplicitValDemo02 {
    
    
  def main(args: Array[String]): Unit = {
    
    
    // 隐式变量(值)
    implicit val name: String = "Scala"
    implicit val number: Int = 10

    def hello1(implicit content: String = "Tom"): Unit = {
    
    
      println(s"Hello1, $content")
    }
    // 1. 优先使用传值
    hello1("Marry") // Hello1, Marry
    // 2. 没有传值,就优先使用默认值
    hello1  // Hello1, Scala

    def hello2(implicit content: Int): Unit = {
    
    
      println(s"Hello2, $content")
    }
    // 3. 没有传值,没有默认值,那就使用隐式值
    hello2  // Hello2, 10

    // 4. 没有隐式值,没有默认值,又没有传值,就会报错
    def hello3(implicit content: Double ): Unit = {
    
    
      println(s"Hello3, $content")
    }
//    hello3  // error
  }
}

隐式类

  1. 其所带的构造参数有且只能有一个。
  2. 隐式类必须被定义在“ 类 ” 或 “ 伴生对象 ” 或 “ 包对象”里。
  3. 隐式类不能是 case class。
  4. 作用域内不能有与之相同名称的标识符。
package com.example.chapter09

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

    implicit class DB66(val m: MySQL6) {
    
    
      def addSuffix(): String = {
    
    
        s"${m}.scala"
      }
    }

    val mysql = new MySQL6
//    println(mysql)
    mysql.say
    println(mysql.addSuffix)
  }
}
class DB6
class MySQL6 {
    
    
  def say(): Unit = {
    
    
    println("say")
  }
}

隐式的转换时机

  1. 当方法中的参数的类型与目标类型不一致时, 或者是赋值时。
  2. 当对象调用其对应类不存在的方法或成员时,编译器会自动将对象进行隐式转换。

10. 集合

Scala 默认采用不可变集合,对于几乎所有的集合类,Scala 都同时提供了可变(mutable)和不可变(immutable)的版本。

图片来源
scala.collection.immutable
在这里插入图片描述
scala.collection.mutable
在这里插入图片描述

数组

定长数组:Array
变长数组:ArrayBuffer

// 多维数组示例

package com.example.chapter10

object ArrayDemo01 {
    
    
  def main(args: Array[String]): Unit = {
    
    
    val arr = Array.ofDim[Int](3, 4)
    for(i <- 0 until arr.length) {
    
    
      for(j <- 0 until arr(i).length) {
    
    
        arr(i)(j) = i + j
      }
    }
    for(items <- arr) {
    
    
      for(item <- items) {
    
    
        print(s"$item\t")
      }
      println
    }
  }
}

元组

元组可以理解为一个容器,可以存放各种相同或不同类型的数据。说的简单点,就是将多个无关的数据封装为一个整体,称为元组,。最大的特点是灵活,对数据没有过多的约束。注意:元组中最大只能有 22 个元素。

package com.example.chapter10

object TupleDemo01 {
    
    
  def main(args: Array[String]): Unit = {
    
    
    val tuple = (1, 1.0, true, "a")
    println(tuple)
    println(tuple._3)

    for(item <- tuple.productIterator) {
    
    
      print(s"$item ")
    }
    println
  }
}

列表 List(不可变)

package com.example.chapter10

object ListDemo01 {
    
    
  def main(args: Array[String]): Unit = {
    
    
    val list1 = List(1, 2, 3, "aaa")  // List(1, 2, 3, aaa)
    val list2 = list1 :+ 4  // List(1, 2, 3, aaa, 4)
    val list3 = 10 +:list2  // List(10, 1, 2, 3, aaa, 4)

    println(list1)
    println(list2)
    println(list3)

    println("----------------------")

    // 符号 :: 表示向集合中添加元素,集合对象一定要放置在最右边,运算规则是从右向左。
    val list4 = List(11, 22, 33, "xxx")
    val list5 = 4 :: 5 :: 6 :: list4 :: Nil
    println(list5)  // List(4, 5, 6, List(11, 22, 33, xxx))

    //  :::  运算符是将集合中的每一个元素加入到集合中
    val list6 = 4 :: 5 :: 6 :: list4 ::: Nil
    println(list6)  // List(4, 5, 6, 11, 22, 33, xxx)
  }
}

列表 ListBuffer(可变)

package com.example.chapter10

import scala.collection.mutable.ListBuffer

object ListBufferDemo01 {
    
    
  def main(args: Array[String]): Unit = {
    
    
    val listBuffer1 = ListBuffer[Int](1, 2, 3)
    for(item <- listBuffer1) {
    
    
      print(s"$item ")
    }
    println("\n--------------------")

    val listBuffer2 = new ListBuffer[Int]
    listBuffer2 += 4
    listBuffer2.append(5)
    listBuffer1 ++= listBuffer2
    println(listBuffer1)
    println("\n--------------------")

    val listBuffer3 = listBuffer1 ++ listBuffer2
    println(listBuffer3)
    println("\n--------------------")

    val listBuffer4 = listBuffer1 :+ 5
    println(listBuffer4)
    println("\n--------------------")

    listBuffer1.remove(1)
    println(listBuffer1)
  }
}

Queue

常用可变的 Queue

Map

Scala 中不可变的 Map 是有序的,可变的 Map 是无序的。

package com.example.chapter10

import scala.collection.mutable

object MapDemo01 {
    
    
  def main(args: Array[String]): Unit = {
    
    
    val map1 = Map("Alice" -> 10, "Bob" -> 20, "Kotlin" -> "北京")
    println(map1)
    // 注意比较打印顺序
    val map2 = mutable.Map("Alice" -> 10, "Bob" -> 20, "Kotlin" -> "北京")
    println(map2)

    val map3 = new scala.collection.mutable.HashMap[String, Int]
    println(map3)

    val map4 = mutable.Map(("Alice" , 10), ("Bob" , 20), ("Kotlin" , "北京"))
    println("map4=" + map4)

    println(map4("Bob"))  // 20
//    println(map4("xxx"))  // key 不存在就抛异常:NoSuchElementException

    // 映射.get(键) 这样的调用返回一个 Option 对象,要么是 Some,要么是 None
    println(map4.get("Bob").get)  // 20
//    println(map4.get("xxx").get)  // key 不存在就抛异常:NoSuchElementException

    println(map4.getOrElse("xxx", "hah")) // hah

    map4 += ("D" -> 4)
    map4 += ("D" -> 6)
    println(map4)

    val map5 = map4 + ("E"->7, "F"->9)
    println(map5)

    map5 -= ("E", "P")  // key 不存在不报错
    println(map5)

    // 遍历
    for((k, v) <- map5) print(s"$k->$v " ); println
    for(k <- map5.keys) print(s"$k "); println
    for(v <- map5.values) print(s"$v "); println
    for(kv <- map5) print(s"$kv "); println // Tuple2
  }
}

Set

11. 集合操作

高阶函数

def myPrint(): Unit = {
    
    
 println("hello,world!")
}
val f1 = myPrint()
f1
// val f1 = myPrint _
// f1()

def sum2(d: Double): Double = {
    
    
  d + d
}
def test(f: Double => Double, d: Double) = {
    
    
  f(d)
}
val res1 = test(sum2 _,  3.5)
val res2 = test(d => d + d, 3.5)
val res3 = test(sum2, 3.5)
println(res1)
println(res2)
println(res3)

val f2 = sum2 _
println(f2)  // <function1>

val f3 = sum2(3.5)
println(f3) // 7.0

val f4 = sum2 _
println(f4(3.5))  // 7.0

map/flatMap/filter 操作

package com.example.com.example.chapter11

object MapOperateDemo01 {
    
    
  def main(args: Array[String]): Unit = {
    
    
    // map (把列表各个元素都扩大为原来的 2 倍)
    val list = List(1, 2, 3, 4)
    val list2 = list.map(x => x + x)
    println(list2)  // List(2, 4, 6, 8)

    def process(x: Int): Int = {
    
    
      x + x
    }
    val list3 = list.map(process) // 其实就是传入一个函数,同简化代码 x=>x+x
    println(list3)  // List(2, 4, 6, 8)

    // flatMap
    val list4 = List(List(1,2,3), List(4,5,6))
    val list5 = list4.flatMap(x => x) // 不再单独定义个函数传进来了
    println(list5)  // List(1, 2, 3, 4, 5, 6)

    // filter
    val list6 = List("Bob", "Bad", "Yes")
    val list7 = list6.filter(s => s.startsWith("B"))  // 不再单独定义函数传进来了
    println(list7)  // List(Bob, Bad)
  }
}

reduce/fold/scan

package com.example.com.example.chapter11

object ReduceDemo01 {
    
    
  def main(args: Array[String]): Unit = {
    
    
    val list = List(1, 2, 3, 4, 5)

    val res1 = list.reduceLeft((x, y) => x + y) // ((((1 + 2) + 3) + 4) + 5) = 15
    println(res1)

    val res2 = list.reduceRight((x, y) => x - y)
    println(res2) // 3

    val res3 = list.reduceRight((x, y) => y - x)
    println(res3) // -5

    val res4 = list.reduce((x, y) => x + y)
    println(res4) // 15

    val min = list.reduce((x, y) => if(x < y) x else y)
    println(min)

    // fold (类似于 reduce,只是可设定初始值)
    val res5 = list.fold(0)((x, y) => x + y)  // (0 /: list)((x, y) => x + y)
    println(res5) // 15

    // 扫描(把 fold 操作的中间结果保留下来)
    val res6 = list.scan(0)((x, y) => x + y)
    println(res6) // List(0, 1, 3, 6, 10, 15)
  }
}

zip/iterator/stream

package com.example.chapter11

object ZipDemo01 {
    
    
  def main(args: Array[String]): Unit = {
    
    
    // zip
    val list1 = List(1, 2, 3)
    val list2 = List(11, 22, 33, 44)
    val list3 = list1.zip(list2)
    println(list3)  // List((1,11), (2,22), (3,33))

    for(item <- list3) {
    
    
      print(s"(${item._1},${item._2}) ")  // (1,11) (2,22) (3,33)
    }
    println

    // iterator
    val iterator = List(1,2,3,4,5,6).iterator
//    while (iterator.hasNext) {  // 迭代方式一
//      print(s"${iterator.next} ")
//    }
//    println

    for(item <- iterator) {
    
     // 迭代方式二
      print(s"$item ")
    }
    println

    // stream (自定义一个函数,第一个元素是 n,后续不断生成 n+1,遵循 lazy)
    def numsForm(n: BigInt) : Stream[BigInt] = n #:: numsForm(n + 1)
    val stream1 = numsForm(1)
    println(stream1)  // Stream(1, ?)

    println("head=" + stream1.head) // 取出第一个元素
    println(stream1.tail.head) // 2 执行 tail 时,会生成一个新数据.
    println(stream1) // Stream(1, 2, ?)
    println(stream1.tail.tail.head) // 3 执行 tail 时,会生成一个新数据.
    println(stream1) // Stream(1, 2, 3, ?)
  }
}

view

package com.example.chapter11

object ViewDemo {
    
    
  def main(args: Array[String]): Unit = {
    
    
    //如果这个数,逆序后和原来数相等,就返回 true,否则返回 false
    def eq(i: Int): Boolean = {
    
    
      println("eq 被调用..")
      i.toString.equals(i.toString.reverse)
    }

    // 常规方式(不用 view)
    val viewSquares1 = (1 to 100).filter(eq)
    println(viewSquares1)
    
    // 使用 view 来完成这个问题,程序中,对集合进行 map,filter,reduce,fold...
    // 你并不希望立即执行,而是在使用到结果才执行,则可以使用 view 来进行优化(lazy)
    val viewSquares2 = (1 to 100).view.filter(eq)
//    println(viewSquares2)
//    //遍历
//    for (item <- viewSquares2) {
    
    
//      println("item=" + item)
//    }
  }
}

模式匹配

入门示例

package com.example.chapter12

object MatchDemo01 {
    
    
  def main(args: Array[String]): Unit = {
    
    
    for (char <- "+-3a0") {
    
     //是对"+-3!" 遍历
      var sign = 0
      var digit = 0
      char match {
    
    
        case '+' => sign = 1
        case '-' => sign = -1
        // 如果 case 后有条件守卫即 if,那么这时的 _ 不是表示默认匹配,表示忽略传入的 ch
        case _ if char.toString.equals("3") => digit = 3
        case _ if (char > 48) => println("ch > 48 ")
        case _ => sign = 2
      }
      //分析
      // + 1 0
      // - -1 0
      // 3 0 3
      // ! 2 0
      println(s"char=$char,sign=$sign,digit=$digit")
    }
  }
}

匹配变量

package com.example.chapter12

// 如果在 case 关键字后跟变量名,那么 match 前表达式的值会赋给那个变量
object MatchVar {
    
    
  def main(args: Array[String]): Unit = {
    
    
    val ch = 'a'
    ch match {
    
    
      case 'k' => println("ok")
      case myChar => println(s"ok, ${myChar}")
      case _ => println ("ok?")
    }

    // match 是一个表达式,因此可以有返回值
    val res = ch match {
    
    
      case 'a' => "ok, a"
      case _ => println("ok?") // println 返回值为 (),默认匹配将会有 res = ()
    }
    println("res=" + res)

    val (x, y, z) = (1, 2, "hello")
    println("x=" + x)
    val (q, r) = BigInt(10) /% 3 // q = BigInt(10) / 3   r = BigInt(10) % 3
    val arr =Array(1, 7, 2, 9)
    val Array(first, second, _*) = arr // 提出 arr 的前两个元素
    println(first, second)
  }
}

匹配类型

package com.example.chapter12

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

    val a = 8
    // obj 实例的类型 根据 a 的值来返回
    val obj1 = if (a == 1) 1
    else if (a == 2) "2"
    else if (a == 3) BigInt(3)
    else if (a == 4) Map("aa" -> 1) //  Map[String, Int] 和 Map[Int, String]是两种不同的类型
    else if (a == 5) Map(1 -> "aa")
    else if (a == 6) Array(1, 2, 3)
    else if (a == 7) Array("aa", 1)
    else if (a == 8) Array("aa")

    // 根据 obj 的类型来匹配返回值
    val res1 = obj1 match {
    
    
      case a: Int => a
      case b: Map[String, Int] => "对象是一个字符串-数字的 Map 集合"
      case c: Map[Int, String] => "对象是一个数字-字符串的 Map 集合"
      case d: Array[String] => d // [Ljava.lang.String;@140d5f0
      case e: Array[Int] => "对象是一个数字数组"
      case f: BigInt => Int.MaxValue
      case _ => "啥也不是"
    }
    println(res1)

    val obj2 = 10
    val res2 = obj2 match {
    
    
      case a: Int => a  // a = obj1,再判断类型
//      case b: Map[String, Int] => "Map" // 编译出错,required: Int
//      case c: Long => c // 编译出错,required: Int
//      case _: Int => 1 // _ 出现在 match 中间某个位置,不表示默认匹配,而是隐藏变量名
      case _ => "nothing"
    }
    println(res2) // 10
  }
}

匹配数组

package com.example.chapter12

import scala.collection.mutable.ArrayBuffer

/**
 * 匹配数组
 * 1) Array(0) 匹配只有一个元素且为 0 的数组
 * 2) Array(x,y) 匹配数组有两个元素,并将两个元素赋值为 x 和 y。当然可以依次类推 Array(x,y,z) 匹
 * 配数组有 3 个元素的等等...
 * 3) Array(0,_*) 匹配以 0 开始的数组
 */
object MatchArr {
    
    
  def main(args: Array[String]): Unit = {
    
    
    val arrs =Array(Array(0), Array(1, 0), Array(0, 1, 0), Array(1, 1, 0), Array(1, 1, 0, 1))
    for (arr <- arrs) {
    
    
       val result = arr match {
    
    
         case Array(0) => "0"
         case Array(x, y) => x + "=" + y
         case Array(0, _*) => "以 0 开头和数组"
         case _ => "什么集合都不是"
       }
       println("result = " + result)
      // result = 0
      // result = 1=0
      // result = 以 0 开头和数组
      // result = 什么集合都不是
      // result = 什么集合都不是
    }
  }
}

匹配列表

package com.example.chapter12

object MatchList {
    
    
  def main(args: Array[String]): Unit = {
    
    
    for (list <- Array(List(0), List(1, 0), List(88), List(0, 0, 0), List(1, 0, 0))) {
    
    
      val result = list match {
    
    
        case 0 :: Nil => "0" //
        case x :: y :: Nil => x + " " + y //
        case 0 :: tail => "0 ..." //
        case x :: Nil => x
        case _ => "something else"
      }
      println(result)
      // 0
      // 1 0
      // 88
      // 0 ...
    }
  }
}

匹配元组

package com.example.chapter12

object MatchTuple {
    
    
  def main(args: Array[String]): Unit = {
    
    
    for (tuple <-Array((0, 1), (0, 1, 1), (1, 0), (10, 30), (1, 1), (1, 0, 2))) {
    
    
      val result = tuple match {
    
    
        case (0, _) => "0 ..."
        case (y, 0) => y
        case (x, y) => (y, x)
        case _ => "other"
      }
      println(result)
      // 0 ...
      // other
      // 1
      // (30,10)
      // (1,1)
      // other
    }
  }
}

匹配对象

package com.example.chapter12

object MatchObjectDemo01 {
    
    
  def main(args: Array[String]): Unit = {
    
    
    val number: Double = Square(5.0)
    number match {
    
    
      //说明 case Square(n) 的运行的机制
      //1. 当匹配到 case Square(n)
      //2. 调用 Square 的 unapply(z: Double),z 的值就是 number
      //3. 如果对象提取器 unapply(z: Double) 返回的是 Some(5.0) ,则表示匹配成功,同时
      //   将 5.0 赋给 Square(n) 的 n
      //4. 如果对象提取器 unapply(z: Double) 返回的是 None ,则表示匹配不成功
      case Square(n) => println("匹配成功 n=" + n)
      case _ => println("nothing matched")
    }
  }
}
object Square {
    
    
  //说明
  //1. unapply 方法是对象提取器
  //2. 接收 z:Double 类型
  //3. 返回类型是 Option[Double]
  //4. 返回的值是 Some(math.sqrt(z)) 返回 z 的开平方的值,并放入到 Some(x)
  def unapply(z: Double): Option[Double] = {
    
    
    println("Square.unapply 被调用,z=" + z)
    Some(math.sqrt(z))
//    None
  }
  def apply(z: Double): Double = z * z
}

for 中的匹配

package com.example.chapter12

object MatchFor {
    
    
  def main(args: Array[String]): Unit = {
    
    
    val map = Map("A" -> 1, "B" -> 0, "C" -> 3)
    for ((k, v) <- map) {
    
    
      println(k + " -> " + v) // 出来三个 key-value ("A"->1), ("B"->0), ("C"->3)
    }
    //说明 : 只遍历出 value =0 的 key-value ,其它的过滤掉
    println("--------------(k, 0) <- map-------------------")
    for ((k, 0) <- map) {
    
    
      println(k + " --> " + 0)
    }
    //说明, 这个就是上面代码的另外写法, 只是下面的用法灵活和强大
    println("--------------(k, v) <- map if v >= 1-------------------")
    for ((k, v) <- map if v >= 1) {
    
    
      println(k + " ---> " + v)
    }
  }
}

样例类

package com.example.chapter12

object CaseClassDemo01 {
    
    
  def main(args: Array[String]): Unit = {
    
    
    for (amt <- Array(Dollar2(1000.0), Currency2(1000.0, "RMB"), NoAmount2)) {
    
    
      val result = amt match {
    
    
        case Dollar2(v) => "$" + v // $1000.0
        case Currency2(v, u) => v + " " + u // 1000.0 RMB
        case NoAmount2 => ""
      }
      println(amt + ": " + result)
    }
  }
}
abstract class Amount2
case class Dollar2(value: Double) extends Amount2 //样例类
case class Currency2(value: Double, unit: String) extends Amount2 //样例类
case object NoAmount2 extends Amount2 //样例类
// case class 构造器中的每一个参数都是 val,除非显式地声明为 var(不建议这样做)

13. 函数式编程(高级)

偏函数

给你一个集合 val list = List(1, 2, 3, 4, “abc”) ,请完成如下要求:

  1. 将集合 list 中的所有数字+1,并返回一个新的集合。
  2. 要求忽略掉非数字元素,即返回的新的集合为 (2, 3, 4, 5)。
package com.example.chapter13

object PartialFunDemo01 {
    
    
  def main(args: Array[String]): Unit = {
    
    
    val list = List(1, 2, 3, 4, "Hello")
    // 方法1
    // 下面等价:val res1 = list.filter(x => x.isInstanceOf[Int]).map(x => x.asInstanceOf[Int]+1)
    val res1 = list.filter(f1).map(f2)
    println(res1)

    // 方法2,偏函数
    //1. PartialFunction[Any,Int] 表示偏函数接收的参数类型是 Any,返回类型是 Int
    //2. isDefinedAt(x:Any) 如果返回 true ,就会去调用 apply 构建对象实例,如果是 false,过滤
    //3. apply 构造器 ,对传入的值 + 1,并返回(新的集合)
    val partialFun = new PartialFunction[Any,Int] {
    
    
      override def isDefinedAt(x:Any) = {
    
    
        x.isInstanceOf[Int]
      }
      override def apply(v1:Any) = {
    
    
        v1.asInstanceOf[Int] + 1
      }
    }
    val res2 = list.collect(partialFun)
    println(res2)
    
    // 偏函数简写
    def partialFun2: PartialFunction[Any,Int] = {
    
    
      case i:Int => i + 1
      case j:Double => (j * 2).toInt
    }
    val res3 = list.collect(partialFun)
    println(res3)

    // 偏函数简写
    val res4 = list.collect{
    
    
      case i: Int => i+1
      case d: Double => (d*2).toInt
    }
    println(res4)
  }

  def f1(x: Any): Boolean = {
    
    
    x.isInstanceOf[Int]
  }
  def f2(x: Any): Int = {
    
    
    x.asInstanceOf[Int] + 1
  }
}

高阶函数

能够接受函数作为参数的函数,叫做高阶函数 (higher-order function)。

package com.example.chapter13

object HigherOrderFunctionDemo01{
    
    
  def main(args: Array[String]): Unit = {
    
    
    def test(f1: Double => Double, f2: Double =>Int , n1: Double) = {
    
    
      f1(f2(n1))
    }
    def sum(d: Double): Double = {
    
    
      d + d
    }
    def mod(d:Double): Int = {
    
    
      d.toInt % 2
    }
    val res = test(sum, mod, 5.0)
    println("res=" + res) // 2.0
  }
}

函数返回类型是函数

package com.example.chapter13

object HigherOrderFunctionDemo02{
    
    
  def main(args: Array[String]) = {
    
    
    def minusxy(x: Int) = {
    
    
      (y: Int) => x - y // minusxy 返回类型是函数 Int => Int
    }
    println(minusxy(9)(1))  // 8

    val f = minusxy(9)  // 等价:val f = (y: Int) => 9 - y
    println(f)  // <function1>
    println(f(1)) // 8
  }
}

闭包

// minusxy 返回一个匿名函数,因为该函数引用到到函数外的 x,该函数和 x 形成一个闭包
// 例如,val f = minusxy(9) 的 f 函数就是闭包
def minusxy(x: Int) = (y: Int) => x - y

编写一个函数 makeSuffix(suffix: String) 可以接收一个文件后缀名(比如.jpg), 并返回一个闭包。调用闭包,可以传入一个文件名,如果该文件名没有指定的后缀(比如.jpg) ,则返回文件名.jpg , 如果已经有.jpg 后缀,则返回原文件名。

package com.example.chapter13

object ClosureDemo {
    
    
  def main(args: Array[String]): Unit = {
    
    
    def makeSuffix(suffix: String) = {
    
    
      (fileName: String) => {
    
    
        if(fileName.endsWith(suffix)) {
    
    
          fileName
        } else {
    
    
          fileName + suffix
        }
      }
    }
    println(makeSuffix(".jpg")("xxx"))
    println(makeSuffix(".png")("file.png"))
  }
}

函数柯里化(curry)

函数编程中,接受多个参数的函数都可以转化为接受单个参数的函数,这个转化过程就叫柯里化。

// 常规方式
def mul(x: Int, y: Int) = x * y
println(mul(10, 10))
// 闭包
def mulCurry(x: Int) = (y: Int) => x * y
println(mulCurry(10)(9))
// 柯里化
def mulCurry2(x: Int)(y:Int) = x * y
println(mulCurry2(10)(8))
package com.example.chapter13

object CurryDemo {
    
    
  def main(args: Array[String]): Unit = {
    
    
    def eq(s1: String, s2: String): Boolean = {
    
    
      s1.equals(s2)
    }
    // 隐式类
    implicit class TestEq(s: String) {
    
    
      def checkEq(ss: String)(f: (String, String) => Boolean): Boolean = {
    
    
        f(s.toLowerCase, ss.toLowerCase)
      }
    }
    val str1 = "hello"
    println(str1.checkEq("HeLLO")(eq))
    // 简写形式
    println(str1.checkEq("HeLLO")(_.equals(_)))
  }
}

16. 并发编程模型 Akka

  1. Akka 是 JAVA 虚拟机 JVM 平台上构建高并发、分布式和容错应用的工具包和运行时,你可以理解成 Akka 是编写并发程序的框架。
  2. Akka 用 Scala 语言写成,同时提供了 Scala 和 JAVA 的开发接口。
  3. Akka 主要解决的问题是:可以轻松的写出高效稳定的并发程序,程序员不再过多的考虑线程、锁和资源竞争等细节。

Actor 模型

在这里插入图片描述

  1. Akka 处理并发的方法基于 Actor 模型。

  2. 在基于 Actor 的系统里,所有的事物都是 Actor,就好像在面向对象设计里面所有的事物都是对象一样。

  3. Actor 模型是作为一个并发模型设计和架构的。Actor 与 Actor 之间只能通过消息通信,如图的信封。

  4. Actor 与 Actor 之间只能用消息进行通信,当一个 Actor 给另外一个 Actor 发消息,消息是有顺序的(消息队列),只需要将消息投寄的相应的邮箱即可。

  5. 怎么处理消息是由接收消息的 Actor 决定的,发送消息 Actor 可以等待回复,也可以异步处理。

  6. ActorSystem 的职责是负责创建并管理其创建的 Actor, ActorSystem 是单例的(可以理解 ActorSystem 是一个工厂,专门创建 Actor),一个 JVM 进程中有一个即可,而 Acotr 是可以有多个的。

  7. Actor 模型是对并发模型进行了更高的抽象。

  8. Actor 模型是异步 、非阻塞、 高性能的事件驱动编程模型。

  9. Actor 模型是轻量级事件处理(1GB 内存可容纳百万级别个 Actor),因此处理高并发性能好。
    在这里插入图片描述

  10. ActorySystem 创建 Actor。

  11. ActorRef:可以理解成是Actor的代理或者引用。消息是通过ActorRef来发送,而不能通过Actor 发送消息,通过哪个 ActorRef 发消息,就表示把该消息发给哪个 Actor。

  12. 消息发送到 Dispatcher Message (消息分发器),它得到消息后,会将消息进行分发到对应的 MailBox。(注: Dispatcher Message 可以理解成是一个线程池, MailBox 可以理解成是消息队列,可以缓冲多个消息,遵守 FIFO)。

  13. Actor 可以通过 receive 方法来获取消息,然后进行处理。

Actor 模型快速入门

  1. 建立 Maven 项目,在 /src/main/ 和 /src/test/ 下均建立 scala 文件夹并标记为 Source Root。
  2. 注意在 File-Settings-Build,Execution,Deployment-Build Tools-Maven,勾选 Alaways update snapshots(如果 Maven 无法加载配置的包,笔者没遇到这种情况)
  3. Maven 加载不进来包,可以执行mvn clean/mvn package命令,重启 IDEA 试试。
    在这里插入图片描述

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>Akka</artifactId>
    <version>1.0-SNAPSHOT</version>

    <!-- 定义一下常量 -->
    <properties>
        <encoding>UTF-8</encoding>
        <scala.version>2.11.12</scala.version>
        <scala.compat.version>2.11</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>
                                <!-- 指定maven方法 -->
                                <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                                    <mainClass></mainClass>
                                </transformer>
                            </transformers>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>
package org.example.akka.actor

import akka.actor.{
    
    Actor, ActorRef, ActorSystem, Props}

// Actor 自己跟自己玩
class HelloActor extends Actor{
    
    
  // 查看源码 type Receive = scala.PartialFunction[scala.Any, scala.Unit]
  override def receive: Receive = {
    
     // PartialFunction 简写
    case "hello" => println("hello, too (respond hello)")
    case "ok" => println("ok, too (respond ok)")
    case "exit" => {
    
    
      println("Exiting...(respond exit)")
      context.stop(self)  // to stop ActorRef
      context.system.terminate()  // to terminate ActorSystem
    }
    case _ => println("match nothing")
  }
}

object HelloActorDemo {
    
    

  private val actorFactory = ActorSystem("actorFactory")
  // Props[HelloActor] 反射一个 HelloActor,也可 Props(new HelloActor)
  private val helloActorRef: ActorRef = actorFactory.actorOf(Props[HelloActor], "helloActor")

  def main(args: Array[String]): Unit = {
    
    
    helloActorRef ! "hello"
    helloActorRef ! "ok"
    helloActorRef ! "hah"
    helloActorRef ! "exit"
  }
}

Spark Master/Worker 进程通讯

源代码下载地址
在这里插入图片描述

  1. Worker 注册到 Master,Master 完成注册,并回复 Worker 注册成功
  2. Worker 定时发送心跳,Master 接收到 Worker 心跳后,更新该 Worker 的最近一次发送心跳的时间
  3. 给 Master 启动定时任务,定时检测注册的 Worker 有哪些没有更新心跳(死了),并将其从 hashmap 中删除

IDEA,Run-Edit Configurations,可对同一个程序配置不同参数分别运行,模仿多个 Worker。

在这里插入图片描述
SparkMaster.scala

package org.example.akka.sparkmasterworker.master

import java.util.concurrent.TimeUnit

import akka.actor.{
    
    Actor, ActorSystem, Props}
import com.typesafe.config.ConfigFactory
import org.example.akka.sparkmasterworker.common.{
    
    HeartBeat, RegisterWorkerInfo, RegisteredWorkerInfo, RemoveTimeOutWorker, SendHeartBeat, StartTimeOutWorker, WorkerInfo}

import scala.collection.mutable
import scala.concurrent.duration.FiniteDuration

class SparkMaster extends Actor{
    
    

  val workers = mutable.Map[String, WorkerInfo]()

  override def receive: Receive = {
    
    
    case "start" => {
    
    
      println("Master 启动了...")
      self ! StartTimeOutWorker
    }
    case StartTimeOutWorker => {
    
    
      println("master 开始定时检测 worker 心跳,看看有没有 worker 死了...")
      import context.dispatcher
      //说明
      //1. 0 不延时,立即执行定时器
      //2. 9000 表示每隔 9 秒执行一次
      //3. self: 表示发给自己
      //4. SendHeartBeat  发送的内容
      context.system.scheduler.schedule(new FiniteDuration(0, TimeUnit.MILLISECONDS), new FiniteDuration(9000, TimeUnit.MILLISECONDS), self, RemoveTimeOutWorker)
    }
    //检测哪些 worker 心跳超时(now - lastHeartBeat > 6000),并从 workers 中删除
    case RemoveTimeOutWorker => {
    
    
      val workerInfos = workers.values
      val nowTime = System.currentTimeMillis()
      workerInfos.filter(workerInfo => (nowTime - workerInfo.lastHeartBeat) > 6000)
                 .foreach(workerInfo => workers.remove(workerInfo.id))
      println(s"SparkMaster has ${workers.size} workers alive.")
    }
    case RegisterWorkerInfo(id, cpu, ram) => {
    
    
      // 接到 SparkWorker 的注册信息,注册其信息 WorkerInfo 到 hashMap 并给其返回注册成功信息 RegisteredWorkerInfo
      if(!workers.contains(id)) {
    
    
        val workerInfo = new WorkerInfo(id, cpu, ram)
        workers += ((id, workerInfo))
        println(s"SparkMaster has ${workers.size} workers.")
        sender ! RegisteredWorkerInfo
      }
    }
    case HeartBeat(id) => {
    
    
      // 更新对应的 Worker 的心跳时间
      val workerInfo = workers(id)
      workerInfo.lastHeartBeat = System.currentTimeMillis()
      println("Master 更新了 Worker " + id + " 的心跳时间...")
    }
  }
}

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

    if(args.length != 3) {
    
    
      println("Please input the arguments in the form of \"masterIP masterPort masterActorName\"!")
      System.exit(-1)
    }

    val masterIP = args(0)
    val masterPort = args(1)
    val masterActorName = args(2)

    val config = ConfigFactory.parseString(
      s"""
         |akka.actor.provider="akka.remote.RemoteActorRefProvider"
         |akka.remote.netty.tcp.hostname=$masterIP
         |akka.remote.netty.tcp.port=$masterPort
      """.stripMargin)

    val sparkMasterSystem = ActorSystem("SparkMaster", config)
    val sparkMasterRef = sparkMasterSystem.actorOf(Props[SparkMaster], masterActorName)
    sparkMasterRef ! "start"
  }
}

SparkWorker.scala

package org.example.akka.sparkmasterworker.worker

import java.util.UUID
import java.util.concurrent.TimeUnit

import akka.actor.{
    
    Actor, ActorSelection, ActorSystem, Props}
import com.typesafe.config.ConfigFactory
import org.example.akka.sparkmasterworker.common.{
    
    HeartBeat, RegisterWorkerInfo, RegisteredWorkerInfo, SendHeartBeat}

import scala.concurrent.duration.FiniteDuration

class SparkWorker(masterIP: String, masterPort: Int, masterActorName: String) extends Actor{
    
    

  // sparkMasterPorxy 是 SparkMaster 的代理/引用 ref
  var sparkMasterPorxy :ActorSelection = _
  val id = UUID.randomUUID().toString // SparkWorkder ID

  override def preStart(): Unit = {
    
    
    println("调用 preStart(): Unit,初始化 sparkMasterPorxy...")
    sparkMasterPorxy = context.actorSelection(s"akka.tcp://SparkMaster@${masterIP}:${masterPort}/user/${masterActorName}")
    println("sparkMasterPorxy=" + sparkMasterPorxy)
  }

  override def receive: Receive = {
    
    
    case "start" => {
    
    
      println("Worker 启动了...")
      // SparkWorker 启动后,就注册到 SparkMaster
      sparkMasterPorxy ! RegisterWorkerInfo(id, 16, 16*1024)
    }
    case RegisteredWorkerInfo => {
    
    
      println(s"Worker $id 注册成功!")
      // 当注册成功后,就定义一个定时器,每隔一定时间,发送 SendHeartBeat 给自己
      import context.dispatcher
      //说明
      //1. 0 不延时,立即执行定时器
      //2. 3000 表示每隔 3 秒执行一次
      //3. self: 表示发给自己
      //4. SendHeartBeat  发送的内容
      context.system.scheduler.schedule(new FiniteDuration(0, TimeUnit.MILLISECONDS), new FiniteDuration(3000, TimeUnit.MILLISECONDS), self, SendHeartBeat)
    }
    case SendHeartBeat =>{
    
    
      println("Worker = " + id + "给 Master 发送心跳...")
      sparkMasterPorxy ! HeartBeat(id)
    }
  }
}

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

    if(args.length != 6) {
    
    
      println("Please input the arguments in the form of \"workerIP workerPort workerActorName masterIP masterPort masterActorName\"!")
      System.exit(-1)
    }
    val workerIP = args(0)
    val workerPort = args(1)
    val workerActorName = args(2)
    val masterIP = args(3)
    val masterPort = args(4)
    val masterActorName = args(5)

    val config = ConfigFactory.parseString(
      s"""
         |akka.actor.provider="akka.remote.RemoteActorRefProvider"
         |akka.remote.netty.tcp.hostname=$workerIP
         |akka.remote.netty.tcp.port=$workerPort
      """.stripMargin)
    val sparkWorkerSystem = ActorSystem("SparkWorker", config)
    val sparkWorkerRef = sparkWorkerSystem.actorOf(Props(new SparkWorker(masterIP, masterPort.toInt, masterActorName)), workerActorName)
    sparkWorkerRef ! "start"
  }
}

MessageProtocol.scala

package org.example.akka.sparkmasterworker.common

// worker 注册信息
case class RegisterWorkerInfo(id: String, cpu: Int, ram: Int)
// 这个是 WorkerInfo, 这个信息将来是保存到 master 的 hm(该 hashmap 是用于管理 worker)
// 将来这个 WorkerInfo 会扩展(比如增加 worker 上一次的心跳时间)
class WorkerInfo(val id: String, val cpu: Int, val ram: Int) {
    
    
  var lastHeartBeat: Long = System.currentTimeMillis
}
// 当 worker 注册成功,服务器返回一个 RegisteredWorkerInfo 对象
case object RegisteredWorkerInfo


// worker 每隔一定时间由定时器发给自己的一个消息
case object SendHeartBeat
// worker 每隔一定时间收到定时器发的 SendHeartBeat 后,向 master 发送 HeartBeat 心跳
case class HeartBeat(id: String)


// master 给自己发消息,触发检查超时 worker 的任务
case object StartTimeOutWorker
// master 定时检查 worker 超时的任务给 master 自己发消息 RemoveTimeOutWorker,开始检测超时 worker 并删除
case object RemoveTimeOutWorker

参考资料

[1] 尚硅谷大数据之韩顺平Scala视频及对应笔记
[2] Scala 官方文档
[3] Scala 官方入门教程

猜你喜欢

转载自blog.csdn.net/ccnuacmhdu/article/details/107873453