Ezekielx
Ezekielx
发布于 2025-10-16 / 12 阅读
0
0

Spark Chapter 3:Scala Foundation(Scala 基础)

一、Basic Scala Concept(Scala 基本概念)

Scala 结合了面向对象和函数式编程,是一种简洁的高级语言。

Scala 的静态类型系统有助于在复杂应用中避免错误,它既能运行在 JVM 上,也能运行在 JavaScript 平台上,从而可以轻松构建高性能系统,并访问庞大的类库生态。

它有六个主要特性:

  • 无缝的 Java 集成(Seamless Java interop)—— Java 与 Scala 可以混合使用。
  • 类型推断 —— 编译器可以自动推断类型。
  • 并发与分布式编程(Concurrency & Distribution)—— Scala 提供了多种并发模型(如 FuturePromiseActor),能方便地处理多线程任务。。
  • 特质(Traits)与特性 —— 类似于 Java 中的接口(interface)和抽象类(abstract)。
  • 模式匹配(Pattern Matching)—— 类似 Java 的 switch,但更强大,可以根据值、类型或结构选择不同逻辑。
  • 高阶函数(Higher-order Functions)—— 可以把函数作为参数或返回值,实现灵活的函数式编程。

1、Installing Scala in IntelliJ IDEA(在 IntelliJ IDEA 中安装 Scala)

在设置中选择插件,搜索 Scala 插件,下载。

eR6p8JP9-1.png

eR6p8JP9-2.png

选择创建一个 Scala 项目,选择对应版本的 Scala SDK 下载。

eR6p8JP9-3.png

eR6p8JP9-4.png

eR6p8JP9-5.png

二、Scala Objects(Scala 对象)

数据类型:

数据类型 (Data type) 描述 (Description)
Byte 8 位有符号整数,范围:-128 到 127
Short 16 位有符号整数,范围:-32768 到 32767
Int 32 位有符号整数,范围:-2147483648 到 2147483647
Long 64 位有符号整数,范围:-9223372036854775808 到 9223372036854775807
Float 32 位 IEEE 754 单精度浮点数
Double 64 位 IEEE 754 双精度浮点数
Char 16 位无符号 Unicode 字符,范围:U+0000 到 U+FFFF
String 字符序列(由多个 Char 组成)
Boolean 布尔值:truefalse
Unit 表示“无值”,类似 Java 的 void
Null 表示空引用或空值
Nothing 所有其他类型的子类型,没有任何实例值
Any 所有类型的超类型,所有对象都属于 Any
AnyRef 所有引用类型的超类型(相当于 Java 中的 Object

变量与常量的声明:

/**
 * 定义变量和常量
 * 变量:使用 var 定义,可变
 * 常量:使用 val 定义,不可变
 */
var name = "zhangsan"
println(name)

name = "lisi"
println(name)

val gender = "m"
// gender = "f" // 错误:常量不能被重新赋值

1、Create the Class(创建类)

类可以被定义为一个模板,用来描述与该类相关的行为和状态。

下面的代码示例:

class Person {
  val name = "zhangsan"
  val age = 18

  def sayName() = {
    "my name is " + name
  }
}

2、Create Objects(创建对象)

对象具有状态和行为,是类的一个实例。

object Lesson_class {
  def main(args: Array[String]): Unit = {
    val person = new Person()
    println(person.age)
    println(person.sayName())
  }
}

3、Apply methods in the object(调用对象中的方法)

当创建一个对象时,如果传入了参数,会自动找到对象中与参数个数对应的 apply 方法。

/**
 * Object(单例对象)本身不能直接传递构造参数,
 * 如果在创建对象时传入了参数,
 * Scala 会根据参数的数量自动找到对象中合适的 apply 方法。
 */
object Lesson_ObjectWithParam {
  def apply(s: String) = {
    println("name is " + s)
  }

  def apply(s: String, age: Int) = {
    println("name is " + s + ", age = " + age)
  }

  def main(args: Array[String]): Unit = {
    Lesson_ObjectWithParam("Ezekielx")
    Lesson_ObjectWithParam("Ezekielx", 18)
  }
}

4、Associated classes and associated objects(伴生类和伴生对象)

类与对象 (Class & Object):

class Person(xname: String, xage: Int) {
  var name = Person.name
  val age = xage
  var gender = "m"

  // 辅助构造函数
  def this(name: String, age: Int, g: String) {
    this(name, age)     // 必须先调用主构造函数
    gender = g
  }

  def sayName() = {
    "my name is " + name
  }
}

object Person {
  val name = "zhangsanfeng"

  def main(args: Array[String]): Unit = {
    val person = new Person("wagnwu", 10, "f")
    println(person.age)
    println(person.sayName())
    println(person.gender)
  }
}

注意事项:

  • 类名建议首字母大写,方法名建议小写,并且类名、方法名推荐使用驼峰命名法。
  • 在 Scala 中,每行结尾都有 自动分号推断,无需手写 ;
  • 对象 (object) 在 Scala 中是 单例对象,相当于 Java 的工具类,可以看作是一种定义。
  • 静态类方法object 实现;object 不能传递参数。trait 也不能传递参数。
  • 类 (class) 在 Scala 中默认可以传参,这就是默认构造器。
  • 如果覆盖(重写)构造函数,必须调用默认构造器
  • 类的属性会自动带有 getter / setter
  • 使用 object 时不需要 new;使用 class 时必须 new
  • class 里,除了方法体之外的所有代码都会立即执行。
  • 如果在同一文件中 classobject 名字相同,则这个 object 就是该类的 伴生对象,两者可以互相访问私有成员。

if else 语句:

/**
 * if else
 */
val age = 18
if (age < 18) {
  println("allowed")
} else if (18 <= age && age <= 20) {
  println("allow with other")
} else {
  println("allowed")
}

for、while、do…while 语句:

  • tountil 的用法:
/**
 * to 和 until
 * 案例:
 * 1 to 10 返回从 1 到 10 的范围数组,包含 10
 * 1 until 10 返回从 1 到 10 的范围数组,不包含 10
 */
println(1 to 10)         // 打印 1 到 10
println(1.to(10))        // 同上
println(1 to (10, 2))    // 步长为 2,打印 1,3,5,7,9
println(1 until 10)      // 不包含最后一个,打印 1 到 9
println(1.until(10))     // 同上
println(1 until (10, 3)) // 步长为 3,打印 1,4,7
  • 创建 for 循环:
/**
 * for 循环
 */
for (i <- 1 to 10) {
  println(i)
}
  • 创建多层 for 循环:
// 多个变量赋值用分号分隔,形成多层 for 循环
// 注意:Scala 中不能写 count++,只能写 count += 1
var count = 0
for (i <- 1 to 10; j <- 1 until 10) {
  println("i = " + i + " j = " + j)
  count += 1
}
println(count)

// 示例:打印九九乘法表
for (i <- 1 until 10; j <- 1 until 10) {
  if (i >= j) {
    print(i + " * " + j + " = " + i * j + " ")
  }
  if (i == j) {
    println()
  }
}
  • 带条件的 for 循环:
// for 循环中可以添加条件判断
for (i <- 1 to 10; if (i % 2) == 0; if (i == 4)) {
  println(i)
}
  • Scala 不使用 count++

​ 在 Scala 中,递增要写成:count = count + 1count += 1

  • for 循环结合 yield 返回集合:
// 使用 yield 将符合条件的元素收集到集合中
val list = for (i <- 1 to 10; if (i > 5)) yield i
for (w <- list) {
  println(w)
}
  • while 与 do…while 循环:
/**
 * while 循环
 */
var index = 0
while (index < 100) {
  println("loop count: " + index)
  index += 1
}

// do…while 循环
var idx = 0
do {
  idx += 1
  println("loop " + idx + " run successfully")
} while (idx < 100)

三、Scala Functional Programming(Scala 函数式编程)

函数式编程是一种编程范式,它将计算机操作视为函数运算,并避免使用程序状态和可变对象。

其中,λ 演算 (lambda calculus) 是该语言最重要的理论基础。

在 λ 演算中,函数既可以接收函数作为输入(参数),也可以将函数作为输出(返回值)。

与命令式编程相比,函数式编程更强调 程序执行的结果 而非 执行的过程

它提倡使用若干个简单的执行单元,通过逐步推导与组合,得到复杂的计算结果,而不是事先设计一个复杂的执行流程。

1、Concept of Functional Programming(函数式编程的概念)

在学习 Scala 中的方法、函数和函数式编程时,需要解释以下几个概念:

  • 方法 (Method) 和函数 (Function) 在 Scala 中几乎是相同的
    它们的定义、使用和运行方式基本一致。
    只是函数的使用更加灵活。

  • 函数式编程 (Functional Programming) 是一种编程风格(编程范式)
    可以理解为:函数式编程把函数当作普通值一样使用,充分利用函数,并支持多种使用方式。
    在 Scala 中,函数可以像变量一样使用:

    • 作为参数传递给其他函数;

    • 作为返回值从函数中返回。

      函数还可以直接赋值给变量来创建,而不依赖类或对象(不像 Java,函数必须依赖类、抽象类或接口)。

  • 面向对象编程 (OOP) 是一种以对象为核心的编程方式。

  • 在 Scala 中,函数式编程和面向对象编程是融合在一起的。

可以这样理解函数式编程

  • 函数式编程是一种编程范式。
  • 它属于“结构化编程”的一种,核心思想是:尽可能把操作过程写成一系列嵌套的函数调用。
  • 在函数式编程中,函数也被视为数据类型,所以函数既可以作为输入参数传递,也可以作为返回值输出。
  • 在函数式编程中,函数是最核心的组成部分

什么是函数?为什么需要函数?

举个例子:需求是输入两个数字和一个运算符(+-),输出计算结果。

没有函数时的 Scala 代码:

val n1 = 10
val n2 = 20
var oper = "-"

if (oper == "+") {
  println("res=" + (n1 + n2))
} else if (oper == "-") {
  println("res=" + (n1 - n2))
}

println("------ does other work.....")

val n3 = 10
val n4 = 20
oper = "+"

if (oper == "+") {
  println("res=" + (n3 + n4))
} else if (oper == "-") {
  println("res=" + (n3 - n4))
}

问题:传统写法中,逻辑重复,代码冗余,不易维护。

使用函数改写:

def calculate(a: Int, b: Int, oper: String): Unit = {
  if (oper == "+") {
    println("res=" + (a + b))
  } else if (oper == "-") {
    println("res=" + (a - b))
  }
}

calculate(10, 20, "-")
println("------ does other work.....")
calculate(10, 20, "+")

是否发现,用传统方式解决这个问题时存在一些问题?

代码出现了冗余,而且不容易维护,所以我们将重复的逻辑抽取出来,形成一个函数。

函数就是:为了完成某个功能而编写的一组语句的集合。

函数的定义可以参考下图所示。

eR6p8JP9-6.png

  • 方法定义语法使用 def 关键字;
  • 可以定义传入的参数,并指定参数的类型;
  • 方法的返回值类型可以写也可以不写。Scala 会自动推断,但在某些情况下(比如递归方法,或返回值是函数类型时)必须显式写出返回值类型;
  • 在 Scala 中,如果方法有返回值,可以使用 return,也可以不写。当不写 return 时,方法中最后一行的表达式就是返回结果。如果写了 return,则必须写明方法的返回值类型;
  • 如果方法体只有一行逻辑,可以省略 {}
  • 方法参数在方法体中可以使用,并且 Scala 规定传入的方法参数是 val,而不是 var
  • 如果在方法定义前去掉等号 =, 那么该方法的返回类型一定是 Unit,无论方法体的逻辑如何。Scala 会自动将任何返回类型转换为 Unit。例如,即使方法体返回一个字符串,最终返回值也会被丢弃并转换为 Unit

递归方法(Recursive Method):

一个函数在函数体内调用自己,这种调用就称为递归调用

def test(n: Int) {
  if (n > 2) {
    test(n - 1)
  }
  println("n=" + n)
}

def test2(n: Int) {
  if (n > 2) {
    test2(n - 1)
  } else {
    println("n=" + n)
  }
}

test(5)
test2(5)

递归函数需要遵循的重要原则:

  • 程序在执行一个函数时,会创建一个新的受保护的独立空间(新的函数栈);
  • 函数的局部变量是独立的,互不影响;
  • 递归必须逐步接近递归出口条件,否则会陷入无限递归;
  • 当函数执行完毕或遇到 return 时,就会返回,结果也会返回给调用它的地方。

匿名函数(Anonymous Functions):

/**
 * 匿名函数
 * 1. 带参数的匿名函数
 * 2. 无参数的匿名函数
 * 3. 带返回值的匿名函数
 *
 * 注意:
 * 匿名函数可以赋值给一个变量
 */

// 带参数的匿名函数
val value1 = (a: Int) => {
  println(a)
}
value1(1)

// 无参数的匿名函数
val value2 = () => {
  println("I love Fashion School")
}
value2()

// 带返回值的匿名函数
val value3 = (a: Int, b: Int) => {
  a + b
}
println(value3(4, 4))

嵌套方法(Nested Method):

/**
 * 嵌套方法
 * 示例:嵌套方法计算 5 的阶乘
 */
def fun5(num: Int) = {
  def fun6(a: Int, b: Int): Int = {
    if (a == 1) {
      b
    } else {
      fun6(a - 1, a * b)
    }
  }
  fun6(num, 1)
}

println(fun5(5))

2、Higher-Order Function(高阶函数)

函数的参数是函数,或者函数的返回值是函数,或者函数的参数和返回值都是函数。

函数的参数是函数。

// 函数的参数是函数
def hightFun(f: (Int,Int) => Int, a: Int): Int = {
  f(a, 100)
}

def f(v1: Int, v2: Int): Int = {
  v1 + v2
}

println(hightFun(f, 1))

函数的返回值是函数。

// 函数的返回值是函数
def hightFun2(a: Int, b: Int): (Int,Int)=>Int = {
  def f2(v1: Int, v2: Int): Int = {
    v1 + v2 + a + b
  }
  f2
}

println(hightFun2(1, 2)(3, 4))

函数的参数是函数,返回值也是函数。

// 函数的参数是函数,返回值也是函数
def hightFun3(f: (Int,Int) => Int): (Int,Int) => Int = { f }

println(hightFun3(f)(100, 200))
println(hightFun3((a, b) => { a + b })(200, 200))

// 上一句也可以写成这样:
// 如果函数参数在方法体中只用了一次,可以写成 _
println(hightFun3(_ + _)(200, 200))

柯里化函数(Coriolis Function):

柯里化是高阶函数的一种简化形式。

// 柯里化函数
def fun7(a: Int, b: Int)(c: Int, d: Int) = {
  a + b + c + d
}

println(fun7(1, 2)(3, 4))

函数的注意事项和细节:

  • 函数的形式参数列表可以有多个;如果函数没有形式参数,可以在调用时不写 ()

  • 参数列表和返回值列表的数据类型既可以是值类型,也可以是引用类型。

  • Scala 函数可以根据函数体最后一行代码推断返回值类型,因此在这种情况下 return 关键字可以省略。

  • 因为 Scala 有类型推断,所以在省略 return 关键字时,返回值类型也可以省略。

  • 如果函数显式使用了 return 关键字,则函数的返回值类型不能被推断

  • 如果函数显式声明无返回值(Unit),即使函数体中写了 return 也不会有返回值。

  • 如果函数显式无返回值,或者不确定返回值类型,返回值类型可以省略(或者声明为 Any)。

  • Scala 语法很灵活,任何结构都可以嵌套在其他语法中。也就是说:函数可以在函数中定义,类可以在类中定义,方法可以在方法中定义。

  • Scala 函数参数在声明时可以直接赋默认值,调用时如果没有传参,就会使用默认值。

  • 如果函数有多个参数,每个参数都可以设置默认值,此时传入的参数可能会导致歧义(是覆盖默认值,还是赋值给没有默认的参数)。

  • Scala 函数参数默认是 val,不能在函数体内修改。

  • 递归函数的返回值类型在执行前无法推断,因此必须显式写明返回值类型。

  • Scala 函数支持可变参数

    // 支持 0 个到多个参数
    def sum(args: Int*): Int = {}
    
    // 支持至少 1 个参数
    def sum(n1: Int, args: Int*): Int = {}
    
    • args 是一个集合,可以通过 for 循环访问每个值;
    • 可变参数必须写在参数列表的最后。

四、Scala Collections(Scala 集合)

基本介绍:

  • Scala 支持 不可变集合可变集合,它们都可以被安全地并发访问;

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

  • 两个主要的包:

    • 不可变集合:scala.collections.immutable
    • 可变集合:scala.collections.mutable
  • Scala 集合分为三大类:序列(Seq)、集(Set)、映射(Map),这三大类都继承自 Iterable 特质。

下图显示了scala.collection.immutable中所有的集合类。

eR6p8JP9-7.png

下图显示了scala.collection.mutable中所有的集合类。

eR6p8JP9-8.png

1、Array(数组)

创建数组:

// 创建一个长度为 3 的整型数组
val Arr1 = new Array 

// 创建一个字符串数组并直接赋值
val Arr2 = Array[String]("s1", "s2", "s3")

// 赋值操作
Arr1(0) = 100
Arr1(1) = 200
Arr1(2) = 300

数组遍历:

// 使用 for 遍历
for (i <- Arr1) {
  println(i)
}

// 使用 foreach 遍历
Arr1.foreach(i => println(i))

for (s <- Arr2) {
  println(s)
}

Arr2.foreach(x => println(x))

一维与二维数组:

// 创建二维数组
val Arr3 = new Array 
Arr3(0) = Array("1", "2", "3")
Arr3(1) = Array("4", "5", "6")
Arr3(2) = Array("7", "8", "9")

// 普通 for 循环遍历
for (i <- 0 until Arr3.length) {
  for (j <- 0 until Arr3(i).length) {
    print(Arr3(i)(j) + " ")
  }
  println()
}

也可以写成嵌套遍历:

var count = 0
for (arr <- Arr3; i <- arr) {
  if (count % 3 == 0) println()
  print(i + " ")
  count += 1
}

// foreach 写法
Arr3.foreach(arr => arr.foreach(println))

再看一个二维整型数组的例子:

val arr4 = Array[Array[Int]](
  Array(1, 2, 3),
  Array(4, 5, 6)
)

arr4.foreach(arr => arr.foreach(i => println(i)))

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

for (arr <- arr4; i <- arr) {
  println(i)
}

数组常用方法:

// 合并数组
Array.concat(Arr1, Arr1)

// 创建一个固定长度并初始化的数组
val arr5 = Array.fill(5)("Ezekielx")

可变数组 ArrayBuffer:

import scala.collection.mutable.ArrayBuffer

val arr = ArrayBuffer[String]("a", "b", "c")

arr.append("Hello", "Scala")  // 添加多个元素
arr += "end"                  // 末尾添加
arr.+=:("start")              // 开头添加

arr.foreach(println)

2、Collection Tuples(集合与元组)

1. List(列表)

创建列表:

val list = List(1, 2, 3, 4)

Nil 表示空列表。

遍历列表:

for (i <- list) println(i)
list.foreach(println)

常用方法:

val list = List(1, 2, 3, 4, 5)

// filter:过滤
val list1 = list.filter(x => x > 3)
list1.foreach(println)

// count:计数
val value = list1.count(x => x > 3)
println(value)

// map:对每个元素操作
val nameList = List(
  "Hello Scala",
  "Hello Ezekielx",
  "Hello Tom"
)
val mapResult: List[Array[String]] = nameList.map(x => x.split(" "))
mapResult.foreach(arr => println(arr.mkString(",")))

// flatMap:扁平化
val flatMapResult: List[String] = nameList.flatMap(x => x.split(" "))
flatMapResult.foreach(println)

2. Set(集合)

创建集合:

val numbers = Set(1, 2, 3, 4, 4)
val set2 = Set(1, 2, 5)

Set 会自动去重。

遍历:

numbers.foreach(println)
for (n <- numbers) println(n)

常用方法:

// 交集
val intersection = numbers.intersect(set2)
intersection.foreach(println)

// 差集
numbers.diff(set2).foreach(println)

// 是否子集
println(numbers.subsetOf(set2))

// 最大最小
println(numbers.max)
println(numbers.min)

// 转换
numbers.toArray.foreach(println)
numbers.toList.foreach(println)

// mkString:转为字符串
println(numbers.mkString(", "))

可变集合:

import scala.collection.mutable.Set

val set = Set[Int](1, 2, 3, 4, 5)
set.add(100)
set += 200
set ++= Seq(300, 400)
set.foreach(println)

3. Map(映射)

创建 Map:

val map = Map("1" -> "Ezekielx", 2 -> "Scala", 3 -> "Tom")

取值:

println(map.get("1").get)
val result = map.get(8).getOrElse("no value")
println(result)

遍历 Map:

for ((k, v) <- map) {
  println(s"key: $k, value: $v")
}

遍历键和值:

// 键
for (key <- map.keys) {
  println(s"key: $key, value: ${map.get(key).get}")
}

// 值
for (value <- map.values) {
  println(s"value: $value")
}

合并 Map:

val map1 = Map((1, "A"), (2, "B"), (3, "C"))
val map2 = Map((1, "AA"), (2, "BB"), (4, "DD"))
val merged = map1 ++ map2
merged.foreach(println)

常用方法:

// count:统计满足条件的键值对数量
val countResult = map.count(_._2 == "Scala")
println(countResult)

// filter:过滤出符合条件的元素
map.filter(_._2 == "Scala").foreach(println)

// contains:判断是否包含键
println(map.contains(2))

// exists:是否存在符合条件的元素
println(map.exists(_._2 == "Tom"))

可变 Map:

import scala.collection.mutable.Map

val mutableMap = Map[String, Int]()
mutableMap.put("hello", 100)
mutableMap.put("world", 200)
mutableMap.foreach(println)

4. Tuple(元组)

定义:

元组与列表类似,但可以包含不同类型的元素。

创建与访问:

val tuple1 = new Tuple1(1)
val tuple2 = ("Ezekielx", 25)
val tuple3 = (1, "Scala", true)
val tuple4 = (1, 2, 3, 4)

// 访问元素
println(tuple2._1 + "\t" + tuple2._2)

// 嵌套元组
val t = ((1, 2), ("Tom", "Jerry"))
println(t._1, t._2)

遍历:

val tupleIterator = tuple4.productIterator
while (tupleIterator.hasNext) {
  println(tupleIterator.next())
}

常用方法:

// swap:交换(仅限二元组)
println(tuple2.swap)

// toString:转字符串
println(tuple3.toString())

五、Advanced Features of Scala(Scala 高级特性)

以下是一些在开发中经常使用的 Scala 高级特性

1、Trait Characteristics(Trait 的特性)

Trait 是 Scala 中代码复用的基本单元。

它封装了方法和字段的定义,并且可以通过“混入(mixin)”的方式复用到类中。

Scala 的 Trait 类似于 Java 的接口(interface),但功能更强大。

Trait 不仅可以定义抽象方法,也可以包含具体的实现。

Trait 的定义方式与类相似,只是使用关键字 trait

trait Philosophical {
  def philosophize() {
    println("I consume memory, therefore I am!")
  }
}

定义好 Trait 之后,可以使用 extendswith 关键字将其混入类中。

在 Scala 中,这种用法被称为“混入”,而不是“继承”,因为它与其他语言中的多重继承机制不同。

如果多个 Trait 中定义了相同名称的方法或属性,必须在类中使用 override 重新定义。

此外,Trait 中不能定义带参数的构造器。

示例:

class Animal

class Frog extends Animal with Philosophical {
  override def toString = "green"
}

也可以混入多个 Trait:

trait HasLegs

class Frog extends Animal with Philosophical with HasLegs {
  override def toString = "green"
}

Trait 的定义在语法上与类基本相同,但有两个重要区别:

第一,Trait 不能带构造参数。

例如:

class Point(x: Int, y: Int)

是合法的,而:

trait NoPoint(x: Int, y: Int) // 无法编译

是非法的。

第二,super 的调用方式不同。

在类中,super 调用是静态绑定的,也就是说明确知道要调用哪个父类的方法。

而在 Trait 中,super 调用是动态绑定的,具体调用哪个实现要等到 Trait 被混入具体类时才确定。

这种机制使得 Trait 可以“堆叠”使用,即多个 Trait 混入时可以依次修改行为,这种特性称为“可堆叠修改(stackable modifications)”。

下面的代码展示了在 Trait 中定义带方法实现的属性:

trait Read {
  val readType = "Read"
  val gender = "m"
  def read(name: String): Unit = {
    println(name + " is reading")
  }
}

trait Listen {
  val listenType = "Listen"
  val gender = "m"
  def listen(name: String): Unit = {
    println(name + " is listening")
  }
}

class Person extends Read with Listen {
  override val gender = "f"
}

object Test {
  def main(args: Array[String]): Unit = {
    val person = new Person()
    person.read("Ezekielx")
    person.listen("Scala")
    println(person.listenType)
    println(person.readType)
    println(person.gender)
  }
}

还可以在 Trait 中定义未实现的方法,由子类去实现。

trait Equal {
  def isEqual(x: Any): Boolean
  def isNotEqual(x: Any): Boolean = {
    !isEqual(x)
  }
}

类中实现该 Trait:

class Point(x: Int, y: Int) extends Equal {
  val xx = x
  val yy = y
  def isEqual(p: Any): Boolean = {
    p.isInstanceOf[Point] && p.asInstanceOf[Point].xx == xx
  }
}

object TraitExample {
  def main(args: Array[String]): Unit = {
    val p1 = new Point(1, 2)
    val p2 = new Point(1, 3)
    println(p1.isEqual(p2))
    println(p1.isNotEqual(p2))
  }
}

2、Implicit Conversion(隐式转换)

你的代码与别人编写的库函数之间有一个根本区别:

你可以随意修改或扩展自己的代码,但无法直接修改他人提供的类或函数。

为了解决这个问题,不同语言提出了不同方案:

  • Ruby 引入了模块(module),
  • Smalltalk 允许不同的包向类中动态添加方法,
  • C# 3.0 提供了静态扩展方法(static extension methods)。

这些方法虽然强大,但也很危险,因为它们直接改变了类的行为,可能导致难以预料的问题。

Scala 的解决方案是:

隐式转换(implicit conversion)隐式参数(implicit parameters)

它们的作用是让代码更简洁、更具表达性。

当使用得当时,Scala 编译器会自动帮你完成类型推断和参数传递,让代码更关注业务逻辑而不是细节。

1. 隐式值与隐式参数(Implicit Values and Parameters)

当 Scala 编译器在匹配类型时,如果找不到合适的类型,就会在作用域中查找是否有可用的隐式值(implicit value),自动完成匹配。

隐式值(implicit value)

是指用 implicit 修饰的变量或常量。

隐式参数(implicit parameter)

是方法定义中标记为 implicit 的参数。

当调用方法时,如果没有显式传入这些参数,编译器会自动在当前作用域中寻找相应的隐式值并传入。

注意事项:

  1. 同一作用域中,同类型的隐式值只能出现一次
  2. implicit 关键字必须出现在参数定义最前面。
  3. 如果方法只有一个隐式参数,可以直接创建类型实例而不显式传参。
  4. 如果方法有多个参数,其中部分需要隐式转换,必须使用柯里化(Currying),并且 implicit 参数必须出现在最后一组参数中,只能出现一次。

示例:

object ImplicitValueExample {
  def Student(age: Int)(implicit name: String, score: Int): Unit = {
    println(s"Student: $name, age = $age, score = $score")
  }

  def Teacher(implicit name: String): Unit = {
    println(s"Teacher name is $name")
  }

  def main(args: Array[String]): Unit = {
    implicit val studentName = "Ezekielx"
    implicit val studentScore = 100

    Student(18)
    Teacher
  }
}

运行结果:

Student: Ezekielx, age = 18, score = 100
Teacher name is Ezekielx

2. 隐式转换函数(Implicit Conversion Functions)

隐式转换函数是使用 implicit 修饰的函数。

当编译器在某类型上调用不存在的方法时,会自动在作用域中寻找合适的隐式函数,将该类型转换为包含该方法的另一类型。

换句话说:

如果对象 A 没有某方法,而对象 B 有该方法,并且存在一个 隐式函数 能将 A 转换为 B,那么 Scala 编译器会自动调用该函数,从而让 A 也能调用这个方法。

注意:

隐式转换函数只与参数类型和返回类型相关,与函数名无关。

因此,在同一作用域内不能定义两个参数类型相同、返回类型不同的隐式函数。

示例:

class Animal(name: String) {
  def canFly(): Unit = {
    println(s"$name can fly...")
  }
}

class Rabbit(xname: String) {
  val name = xname
}

object ImplicitFunctionExample {
  implicit def rabbitToAnimal(rabbit: Rabbit): Animal = {
    new Animal(rabbit.name)
  }

  def main(args: Array[String]): Unit = {
    val rabbit = new Rabbit("Rabbit")
    rabbit.canFly()
  }
}

运行结果:

Rabbit can fly...

这里,Rabbit 类型并没有 canFly() 方法,但编译器在作用域内找到隐式转换函数 rabbitToAnimal,自动将 Rabbit 转换成 Animal,因此能调用 canFly()

3. 隐式类(Implicit Class)

implicit 修饰的类称为隐式类。当某个类型的变量没有特定的方法或属性时,如果存在与该类型对应的隐式类,Scala 会自动创建该类的实例,从而“扩展”该类型的功能。

注意事项:

  1. 隐式类必须定义在 类、伴生对象(companion object)包对象(package object) 中。
  2. 隐式类的构造函数只能有 一个参数
  3. 同一个作用域中,不能定义多个针对同一类型的隐式类。

示例:

class Rabbit(name: String) {
  val n = name
}

object ImplicitClassExample {
  implicit class Animal(rabbit: Rabbit) {
    val tp = "Animal"
    def canFly(): Unit = {
      println(rabbit.n + " can fly...")
    }
  }

  def main(args: Array[String]): Unit = {
    val rabbit = new Rabbit("Rabbit")
    rabbit.canFly()
    println(rabbit.tp)
  }
}

运行结果:

Rabbit can fly...
Animal

这里,Rabbit 类本身并没有 canFly() 方法或 tp 属性。但通过隐式类 Animal,编译器在编译时自动为 Rabbit 类型添加了这些功能。

3、Pattern Matching(模式匹配)

概念理解:

Scala 提供了一种功能强大的模式匹配机制(Pattern Matching),在开发中被广泛使用。

模式匹配由一系列分支组成,每个分支以关键字 case 开头。

每个分支包含一个匹配模式(pattern)和一个或多个表达式(expression),两者之间用箭头符号 => 分隔。

注意事项:

  • 模式匹配不仅可以匹配值(value),也可以匹配类型(type)
  • Scala 按从上到下的顺序进行匹配,一旦匹配成功,就不会再继续向下匹配
  • 如果没有任何分支匹配成功,就会执行 case _,相当于 Java 的 default 分支。
  • 在语法上,最外层的 {} 可以省略,直接将 match 结构作为一个语句使用。

示例代码:

object Lesson_Match {
  def main(args: Array[String]): Unit = {
    // 定义一个包含多种类型的元组
    val tuple = (1, 2, 3f, 4, "ABC", 55d) // 实际类型:Tuple6(Int, Int, Float, Int, String, Double)

    // 通过迭代器遍历元组中的每个元素
    val tupleIterator = tuple.productIterator
    while (tupleIterator.hasNext) {
      matchTest(tupleIterator.next())
    }
  }

  // 定义模式匹配函数
  def matchTest(x: Any): Unit = {
    x match {
      case x: Int => println("type is Int")
      case 1 => println("result is 1")
      case 2 => println("result is 2")
      case 3 => println("result is 3")
      case 4 => println("result is 4")
      case x: String => println("type is String")
      case _: Float => println("type is Float")
      case _: Double => println("type is Double")
      case _ => println("no match")
    }
  }
}

代码说明:

  • tuple.productIterator 将元组转换为迭代器,逐个取出元素进行匹配。

  • matchTest() 函数中使用 match ... case 结构进行类型和值匹配:

    • case x: Int:匹配所有整数类型(包括 1、2、3、4 之前的匹配)。
    • case x: String:匹配字符串类型。
    • case _: Float:匹配浮点数。
    • case _: Double:匹配双精度浮点数。
    • case _:兜底分支,当以上都不匹配时执行。
  • 匹配是顺序执行的,所以 case x: Int 会先捕获所有整数类型,后面的 case 1case 2 等实际上不会执行到。

    若要优先匹配具体值,应将数值匹配的 case 放在前面。

程序输出示例:

type is Int
type is Int
type is Float
type is Int
type is String
type is Double

4、Sample Class(样例类)

概念理解:

使用 case 关键字定义的类称为 样例类(case class),它是一种特殊的类。

样例类会自动为构造参数实现 getter 方法(构造参数默认是 val 类型)。如果构造参数被声明为 var,Scala 也会自动生成相应的 settergetter

样例类默认实现以下方法:

  • toString:用于打印对象内容
  • equals:按字段值比较对象是否相等
  • copy:快速复制对象
  • hashCode:支持哈希集合存储

样例类对象可以用或不用 new 关键字创建。

示例:

case class Person1(name: String, age: Int)

object CaseClassExample {
  def main(args: Array[String]): Unit = {
    val p1 = new Person1("Ezekielx", 10)
    val p2 = Person1("Tom", 20)
    val p3 = Person1("Jerry", 30)

    val list = List(p1, p2, p3)

    list.foreach { x =>
      x match {
        case Person1("Ezekielx", 10) => println("Ezekielx")
        case Person1("Tom", 20) => println("Tom")
        case _ => println("no match")
      }
    }
  }
}

说明:

  • 定义样例类 Person1(name: String, age: Int),自动具备 toStringequalshashCodecopy 等功能。

  • 创建对象时可以写成:

    val p2 = Person1("Tom", 20)
    

​ 不需要加 new

  • 样例类支持 模式匹配(Pattern Matching)

    在上面的代码中,通过 match 语句判断不同对象并输出结果。

  • 程序运行输出:

    Ezekielx
    Tom
    no match
    

5、Actor Model(Actor 模型)

概念理解:

Actor 模型(Actor Model) 用于编写并行计算(parallel computing)和分布式系统(distributed system)的高级抽象结构。

它与 Java 的线程(Thread)类似,但让程序员不必再担心**共享锁(shared lock)多线程模式(multithreaded mode)**等复杂问题。

Actor 模型最早被用于 Erlang 语言,该语言以极高的可用性著称——每年可用率达 99.9999999%(即全年仅约 31 毫秒宕机时间)

在该模型中,Actor 封装了状态(state)和行为(behavior),它们存在于轻量级的进程或线程中,但不会与其他 Actor 共享状态。每个 Actor 都有自己的 “世界观”(world view)。当它们需要与其他 Actor 交互时,是通过**发送事件或消息(event and message)**来完成的。

消息的发送是异步且非阻塞的(asynchronous and non-blocking)——也就是说,发送后无需等待回复,也不需要暂停执行(称为 “fire-and-forget” 机制)。

每个 Actor 都有一个自己的消息队列(message queue),传入的消息按**先进先出(FIFO)**顺序排列处理。

Actor 的特征:

  • Actor 模型是基于消息传递的模型(messaging model),其核心特征就是“消息通信”。
  • 消息发送是异步且非阻塞的(asynchronous and non-blocking)
  • 消息一旦发送成功,就不可更改(immutable after sending)
  • 在 Actor 之间传递消息时,是否检查或处理消息由 Actor 自身决定,这种机制是异步的、非阻塞的。

什么是 Akka?

Akka 是一个用 Scala 编写的库,用于简化编写容错(fault-tolerant)、**高扩展性(highly scalable)**的 JavaScala Actor 模型应用程序。

它的底层实现基于 Actor 模型。Akka 既是一个开发库(development library),也是一个运行时环境(runtime environment),用于构建基于 JVM 的高并发、分布式、容错、事件驱动系统(event-driven system),使得开发此类系统更加简单高效。

Spark 1.6 之前,Spark 的分布式节点通信使用的就是 Akka(底层为 Actor 通信机制);从 1.6 版本之后,则改为使用 Netty 作为传输层(transport layer)。

**示例 1:**简单的 Actor 发送与接收消息:

import scala.actors.Actor

class myActor extends Actor {
  def act() {
    while (true) {
      receive {
        case x: String => println("get String = " + x)
        case x: Int => println("get int")
        case _ => println("get default")
      }
    }
  }
}

object Lesson_Actor {
  def main(args: Array[String]): Unit = {
    // 创建 actor 并启动消息接收与传递
    val actor = new myActor()
    actor.start()
    // 发送消息
    actor ! "i love you !"
  }
}

**示例 2:**Actor 之间的通信:

case class Message(actor: Actor, msg: Any)

class Actor1 extends Actor {
  def act() {
    while (true) {
      receive {
        case msg: Message => {
          println("i save msg! = " + msg.msg)
          msg.actor ! "i love you too !"
        }
        case msg: String => println(msg)
        case _ => println("default msg!")
      }
    }
  }
}

class Actor2(actor: Actor) extends Actor {
  actor ! Message(this, "i love you !")
  def act() {
    while (true) {
      receive {
        case msg: String => {
          if (msg.equals("i love you too !")) {
            println(msg)
            actor ! "could we have a date!"
          }
        }
        case _ => println("default msg!")
      }
    }
  }
}

object Lesson_Actor2 {
  def main(args: Array[String]): Unit = {
    val actor1 = new Actor1()
    actor1.start()
    val actor2 = new Actor2(actor1)
    actor2.start()
  }
}

评论