引言:为什么选择Scala?
大家好,我是心海!
Scala(Scala ble La nguage)是一门融合面向对象与函数式编程的现代语言。它像瑞士军刀一样灵活------既能编写简洁的脚本,又能构建复杂的分布式系统。想象你正在组装乐高积木:Scala提供标准积木块(面向对象),同时允许你自定义连接器(函数式特性),让创作随心所欲。
当然,如果你已经有了 python 或者 Java 的编程语言基础,那学起来会更加轻松!
目录
[3.1 三大集合类型](#3.1 三大集合类型)
[3.2 实战示例](#3.2 实战示例)
[六、 类和对象:构建程序的蓝图和实例](#六、 类和对象:构建程序的蓝图和实例)
[七、样例类(Case Class):简洁的数据载体](#七、样例类(Case Class):简洁的数据载体)
[八、隐式转换(Implicit Conversions):让代码更灵活](#八、隐式转换(Implicit Conversions):让代码更灵活)
一、变量:数据的容器
变量就像一个个贴着标签的盒子,我们可以把数据放进这些盒子里,并通过标签找到它们。在 Scala 中,我们有两种主要的变量类型:val 和 var
-
val:声明后不可变,推荐默认使用,如宇宙中的物理常数
-
var:可变,谨慎使用,如同随时可能修改的日程表
Scala
val name = "Alice" // 不可变,像刻在石碑上的名字
var age = 25 // 可变,像白板上的数字
// name = "Bob" // 编译错误!
age = 26 // 允许修改
类型推断: Scala 拥有强大的类型推断能力,很多时候你可以省略变量的类型声明,让编译器自动帮你推断。
Scala
val name = "Alice" // 编译器会自动推断 name 是 String 类型
var age = 30 // 编译器会自动推断 age 是 Int 类型
二、函数:执行特定任务的代码块
函数是组织代码的基本单元。它可以接收输入(参数),执行一系列操作,并返回输出(返回值)。
定义函数:
Scala
def add(a: Int, b: Int): Int = {
a + b
}
-
参数类型必须声明
-
返回值类型可省略(但推荐显式声明)
在 Scala 中,我们使用 def 关键字来定义函数。
Scala
def greet(name: String): String = {
s"Hello, $name!"
}
val greeting = greet("Bob")
println(greeting) // 输出 Hello, Bob!
代码解析:
def greet: 声明一个名为 greet 的函数。
(name: String): 定义一个名为 name 的参数,类型为 String。
: String: 指定函数的返回值类型为 String。
= { ... }: 函数体,包含要执行的代码。
s"Hello, name!": 使用字符串插值(string interpolation)构建返回的字符串。s 前缀允许我们在字符串中使用 变量名 的方式嵌入变量的值。
省略返回值类型: 如果函数体比较简单,Scala 也可以进行返回值类型推断,你可以省略 : String。
Scala
def add(a: Int, b: Int) = a + b
val sum = add(5, 3)
println(sum) // 输出 8
三、集合操作:数据处理利器
3.1 三大集合类型
Scala 提供了强大且易用的集合库,可以帮助我们高效地处理列表(List)、映射(Map)、集合(Set)等数据结构。
集合类型 | 特点 | 示例 |
---|---|---|
List | 有序可重复 | List(1,2,3,3)→ 1,2,3,3 |
Set | 无序唯一 | Set(1,2,2,3) → 1,2,3 |
Map | 键值对 | Map("a"->1,"b"->2) |
-
List(列表):有序的元素集合,允许重复。
Scalaval numbers: List<Int> = List(1, 2, 2, 3, 4) println(numbers.head) // 输出第一个元素:1 println(numbers.tail) // 输出除第一个元素外的列表:List(2, 2, 3, 4) println(numbers.length) // 输出列表的长度:5
-
Map(映射):存储键值对的集合,键是唯一的。
Scalaval ages: Map<String, Int> = Map("Alice" -> 30, "Bob" -> 25, "Charlie" -> 35) println(ages("Alice")) // 输出 Alice 的年龄:30 println(ages.get("David")) // 输出 Option(None),因为 "David" 不存在 println(ages.keys) // 输出所有键:Set(Alice, Bob, Charlie) println(ages.values) // 输出所有值:Iterable(30, 25, 35)
-
Set(集合):不包含重复元素的集合,元素的顺序不保证。
Scalaval uniqueNumbers: Set<Int> = Set(1, 2, 2, 3, 4) println(uniqueNumbers) // 输出 Set(1, 2, 3, 4),重复的 2 被去掉了 println(uniqueNumbers.contains(3)) // 输出 true
3.2 实战示例
Scala
case class User(name: String, age: Int, active: Boolean)
val users = List(
User("Alice", 25, true),
User("Bob", 30, false),
User("Charlie", 28, true)
)
// 统计活跃用户的平均年龄
val avgAge = users
.filter(_.active) // 筛选活跃用户
.map(_.age) // 提取年龄
.sum.toDouble / // 计算总和
users.count(_.active) // 统计数量
println(f"平均年龄:$avgAge%.1f") // 输出:平均年龄:26.5
四、匿名函数:没有名字的函数
有时候,我们需要一个函数,但又不想给它起个名字。这时,匿名函数就派上用场了。在 Scala 中,匿名函数也称为函数字面量(function literal)。
语法: (参数列表) => 函数体
Scala
val addOne: Int => Int = (x: Int) => x + 1
println(addOne(5)) // 输出 6
val multiply = (a: Int, b: Int) => a * b
println(multiply(3, 4)) // 输出 12
类型推断简化: 在很多情况下,Scala 可以推断匿名函数的参数类型,你可以进一步简化语法。
Scala
val addOneSimplified: Int => Int = x => x + 1
val multiplySimplified = (a, b) => a * b
五、高阶函数:操作其他函数的函数
高阶函数是指那些接受一个或多个函数作为参数,或者返回一个函数的函数。它们是函数式编程的核心概念,让我们可以编写更灵活、更简洁的代码。
map:对集合中的每个元素应用一个函数,并返回一个新的集合。
Scala
val numbersList = List(1, 2, 3)
val doubledNumbers = numbersList.map(x => x * 2) // 对每个元素乘以 2
println(doubledNumbers) // 输出 List(2, 4, 6)
// 更简洁的写法:使用占位符 _ 代表每个元素
val tripledNumbers = numbersList.map(_ * 3)
println(tripledNumbers) // 输出 List(3, 6, 9)
filter:根据指定的条件过滤集合中的元素,返回满足条件的新集合.
Scala
val numbersToFilter = List(1, 2, 3, 4, 5, 6)
val evenNumbers = numbersToFilter.filter(x => x % 2 == 0) // 过滤出偶数
println(evenNumbers) // 输出 List(2, 4, 6)
val oddNumbers = numbersToFilter.filter(_ % 2 != 0)
println(oddNumbers) // 输出 List(1, 3, 5)
reduce:将集合中的元素通过一个二元操作符组合起来,最终得到一个单一的值。
Scala
val numbersToReduce = List(1, 2, 3, 4)
val sumOfNumbers = numbersToReduce.reduce((a, b) => a + b) // 将所有元素相加
println(sumOfNumbers) // 输出 10
// 更简洁的写法
val productOfNumbers = numbersToReduce.reduce(_ * _) // 将所有元素相乘
println(productOfNumbers) // 输出 24
综合案例:
Scala
val numbers = List(1, 2, 3, 4)
// 流水线处理
numbers
.map(_ * 2) // 放大:变成List(2,4,6,8)
.filter(_ > 3) // 筛选:List(4,6,8)
.reduce(_ + _) // 聚合:4+6+8=18
六、 类和对象:构建程序的蓝图和实例
在面向对象编程中,类是创建对象的蓝图,而对象是类的具体实例。
-
定义类:
Scalaclass Person(val name: String, var age: Int) { def greet(): Unit = { println(s"Hi, my name is $name and I am $age years old.") } } // 创建对象 val person1 = new Person("Alice", 30) val person2 = new Person("Bob", 25) person1.greet() // 输出 Hi, my name is Alice and I am 30 years old. person2.age = 26 // 修改 Bob 的年龄 person2.greet() // 输出 Hi, my name is Bob and I am 26 years old.
代码解析:
-
class Person(val name: String, var age: Int): 定义一个名为 Person 的类,它有两个参数:name(val,不可变)和 age(var,可变)。这两个参数同时也是类的属性。
-
def greet(): Unit = { ... }: 定义一个名为 greet 的方法,它没有参数,返回类型为 Unit(相当于 Java 中的 void),表示没有明确的返回值。
-
new Person("Alice", 30): 使用 new 关键字创建 Person 类的一个新对象。
-
-
伴生对象(Companion Object):
与类同名的 object 称为伴生对象。它可以包含类的静态成员(在 Scala 中没有静态成员的概念),通常用于存放工厂方法或者与类实例无关的工具方法。伴生对象和它的伴生类可以互相访问彼此的私有成员。
Scalaclass Circle(val radius: Double) { def area(): Double = Math.PI * radius * radius } object Circle { def apply(radius: Double): Circle = new Circle(radius) // 工厂方法 } val circle1 = Circle(5.0) // 通过伴生对象的 apply 方法创建 Circle 对象 println(circle1.area())
代码解析:
-
object Circle { ... }: 定义了 Circle 类的伴生对象。
-
def apply(radius: Double): Circle = new Circle(radius): 定义了一个名为 apply 的特殊方法。当像 Circle(5.0) 这样调用伴生对象时,实际上是调用了它的 apply 方法。这使得创建对象更加简洁。
-
七、样例类(Case Class):简洁的数据载体
样例类是一种特殊的类,它默认提供了许多有用的功能,例如:
-
构造函数的参数自动成为类的只读属性(除非显式声明为 var)。
-
自动生成 equals、hashCode 和 toString 方法。
-
自动生成 copy 方法用于创建对象的副本。
-
伴生对象会自动生成 apply 和 unapply 方法,方便对象的创建和模式匹配。
样例类非常适合用于表示不可变的数据结构。
普通类与样例类区别

-
定义样例类:
Scalacase class Point(x: Int, y: Int) val p1 = Point(1, 2) val p2 = Point(1, 2) val p3 = Point(3, 4) println(p1) // 输出 Point(1, 2) (toString 自动生成) println(p1 == p2) // 输出 true (equals 和 hashCode 自动生成,比较内容) println(p1 == p3) // 输出 false val p4 = p1.copy(y = 3) // 使用 copy 方法创建 p1 的副本,并修改 y 坐标 println(p4) // 输出 Point(1, 3) // 使用伴生对象的 apply 方法创建 val p5 = Point.apply(5, 6) println(p5) // 输出 Point(5, 6) // 使用伴生对象的 unapply 方法进行模式匹配 p1 match { case Point(a, b) => println(s"x: $a, y: $b") // 输出 x: 1, y: 2 }
八、隐式转换(Implicit Conversions):让代码更灵活
隐式转换是 Scala 中一个强大的特性,它允许编译器在特定的上下文中自动地将一种类型的值转换为另一种类型的值。这可以帮助我们编写更简洁、更具表达力的代码。
-
定义隐式转换: 需要使用 implicit 关键字来定义隐式转换函数。隐式转换函数必须只有一个参数。
Scalaobject StringToIntConverter { implicit def stringToInt(str: String): Int = str.toInt } import StringToIntConverter._ // 导入隐式转换 val number: Int = "123" // 编译器会自动调用 stringToInt("123") 进行转换 println(number + 1) // 输出 124
代码解析:
implicit def stringToInt(str: String): Int = str.toInt: 定义了一个隐式函数 stringToInt,它接受一个 String 类型的参数 str,并返回一个 Int 类型的值。implicit 关键字表示这是一个隐式转换函数。
import StringToIntConverter._: 导入 StringToIntConverter 对象中的所有隐式转换。
val number: Int = "123": 在这里,我们尝试将一个 String 类型的值赋给一个 Int 类型的变量。由于我们导入了可以将 String 转换为 Int 的隐式转换,编译器会自动调用 stringToInt("123"),将字符串 "123" 转换为整数 123,然后赋值给 number。
-
使用场景: 隐式转换常用于:
-
扩展现有类的功能。
-
提供更自然的语法。
-
支持类型之间的自动转换。
-
-
注意事项: 过度或不当使用隐式转换可能会导致代码难以理解和调试,因此需要谨慎使用。
九、类别系统
Scala 拥有一个静态的、强大的类型系统,可以在编译时捕获很多潜在的错误,提高代码的健壮性和可靠性。
-
泛型(Generics):编写可重用的代码
泛型允许我们编写可以应用于多种类型的代码,而无需为每种类型都编写单独的版本。
Scaladef getFirstElement<T>(list: List<T>): Option<T> = { if (list.isEmpty) None else Some(list.head) } val numbersListGeneric = List(1, 2, 3) val firstNumber = getFirstElement(numbersListGeneric) println(firstNumber) // 输出 Some(1) val stringListGeneric = List("apple", "banana", "cherry") val firstString = getFirstElement(stringListGeneric) println(firstString) // 输出 Some(apple)
代码解析:
def getFirstElement<T>(list: List<T>): Option<T>: 定义了一个泛型函数 getFirstElement,它接受一个类型为 List<T> 的参数 list,并返回一个类型为 Option<T> 的值。<T> 是类型参数,表示可以是任何类型。
-
Option 类型:处理可能缺失的值
Option 类型用于表示一个值可能存在(Some)也可能不存在(None)。这有助于我们避免空指针异常(NullPointerException),这是很多编程语言中常见的错误。
Scaladef findByName(name: String, people: Map<String, Int>): Option<Int> = { people.get(name) } val agesMap = Map("Alice" -> 30, "Bob" -> 25) val aliceAgeOption = findByName("Alice", agesMap) println(aliceAgeOption) // 输出 Some(30) aliceAgeOption match { case Some(age) => println(s"Alice is $age years old.") case None => println("Alice not found.") } val davidAgeOption = findByName("David", agesMap) println(davidAgeOption) // 输出 None davidAgeOption match { case Some(age) => println(s"David is $age years old.") case None => println("David not found.") }
代码解析:
-
def findByName(name: String, people: Map<String, Int>): Option<Int>: 函数 findByName 返回一个 Option<Int>,表示找到的年龄可能是一个整数 (Some(age)),也可能没有找到 (None).
-
people.get(name): Map 的 get 方法返回一个 Option。
-
match 表达式用于处理 Option 的两种可能情况:Some(age) 表示找到了值,并将值绑定到变量 age;None 表示没有找到值。
-
结语:开启Flink之旅
掌握这些Scala基础,就像获得了打开Flink大门的钥匙🔑。接下来我们将使用这些工具构建实时数据处理流水线,让数据像河流一样在程序中自然流动。准备好了吗?让我们进入分布式计算的精彩世界!
动手练习 :尝试实现一个函数,使用集合操作找出100以内所有素数的平方和。(提示:使用filter
和map
)
Scala
// 参考答案
def primeSquares(n: Int): Int = {
(2 to n).filter(isPrime).map(x => x * x).sum
}
def isPrime(num: Int): Boolean = {
!(2 until num).exists(x => num % x == 0)
}
如果这篇文章对你有所启发,期待你的点赞关注!
