一、Scala 基础语法、变量与数据类型

简介: 入门Scala,你会发现它从一开始就鼓励你写出更“结实”的代码。它推荐你多用val来定义“一次性”常量,少用var定义可变变量,这能减少很多潜在的bug。它的类型推断能让你少写很多代码,而s"你好, ${name}"这样的字符串插值,更是把繁琐的拼接变得无比优雅。再加上它的一切皆对象、.toInt等方便的类型转换,以及聪明的==值比较,让你能快速上手,写出简洁又安全的代码。

欢迎来到 Scala 的世界!Scala 是一门强大、简洁且富有表现力的多范式编程语言,它无缝集成了面向对象和函数式编程的特点。由于其在大数据领域 (尤其是 Apache Spark) 的核心地位,掌握 Scala 已成为许多开发者的必备技能。本节,我们将从最基础的语法开始,为你揭开Scala 的神秘面纱。

思维导图

image.png

image.png

image.png

image.png

一、基础语法与环境

输出语句: 学习如何向控制台打印信息。
println(): 打印内容自动换行 print(): 打印内容不换行

代码案例:
scala // 使用 println 换行输出 println("Hello, Scala!") println("This is a new line.") // 使用 print 不换行输出 print("This is the first part, ") print("this is the second part.")

分号规则: Scala 中行尾的分号是可选的,但在单行编写多条语句时需要用分号隔开。
scala // 行尾分号是可选的,推荐不写 val x = 10 val y = 20 // 在单行内写多条语句,必须用分号隔开 val a = 1; val b = 2; println(a + b)

标识符: 学习如何为变量、类等命名。
下表总结了 Scala 中标识符的命名规则与规范:

| 规则/规范 | 说明 | 示例 |
| :--- | :--- | :--- |
| 命名规则 | 由 字母、数字、下划线 _ 和美元符号 $ 组成。标识符 不能以数字开头,也 不能是 Scala 的 关键字 (如 val, var, class)。 | myVar, user_id, _value, name1 |
| 命名规范 | 变量和方法使用 小驼峰命名法 (lowerCamelCase)
类和特质 (Trait) 使用 大驼峰命名法 (UpperCamelCase)。 | myVariable, calculateSum
MyClass, UserService |

## 二、变量与常量 变量 (var): 其值在程序运行过程中可以改变。
定义格式: var 变量名: 类型 = 初始值
scala var myAge: Int = 30 myAge = 31 // 合法,因为 myAge 是一个 var println(myAge)

常量 (val): 也称为“不可变变量”,赋值后其引用不能再改变。Scala 中推荐优先使用 val
定义格式: val 变量名: 类型 = 初始值
scala val myName: String = "Alice" // myName = "Bob" // 这行代码会编译错误,因为 val 不能被重新赋值 println(myName)

类型推断: Scala 编译器可以根据初始值自动推断变量类型,允许省略类型声明,使代码更简洁。
简洁格式:
scala val pi = 3.14 // 编译器自动推断 pi 的类型为 Double val language = "Scala" // 编译器自动推断 language 的类型为 String

惰性赋值 (lazy val): 变量的值直到第一次被访问时才会被计算和加载,适用于开销较大的初始化操作。
scala lazy val bigData = { println("正在进行昂贵的初始化操作...") // 这句话只在 bigData 第一次被访问时打印 "这是一个很大的数据集" } println("脚本已启动,但 bigData 尚未初始化。") println(bigData) // 此时 "正在进行昂贵的初始化操作..." 才会被打印 println(bigData) // 第二次访问,不会再执行初始化代码块

## 三、数据类型体系 值类型: Scala 中所有数据都是对象,以下是基础的值类型。
类型 位数 描述
Byte 8 8位有符号整数
Short 16 16位有符号整数
Int 32 32位有符号整数
Long 64 64位有符号整数, 以 Ll 结尾
Float 32 32位单精度浮点数, 以 Ff 结尾
Double 64 64位双精度浮点数
Char 16 16位Unicode字符
Boolean - truefalse
Scala 类型层次结构:
Scala 有一个 统一的类型系统,所有类型都继承自顶层父类。
> Any: 所有类型的 根父类
AnyVal: 所有 值类型 (如 Int, Double, Boolean) 的父类。
AnyRef: 所有 引用类型 (如 String、自定义类、集合) 的父类,类似于 Java 的 java.lang.Object

特殊类型:
> Unit: 表示 无值,只有一个实例 ()。类似于 Java 的 void,但 Unit 是一个 真实的类型。
Null: 所有 AnyRef 类型的 子类,其唯一实例是 null。它 不能赋值AnyVal 类型的变量。
Nothing: 所有类型的 子类,表示 没有正常的值。常用于 标记异常抛出的表达式。
## 四、字符串操作 双引号定义:
scala val greeting = "Hello, World!"

字符串插值 (String Interpolation): 在字符串前加 s,使用 ${} 嵌入变量或表达式,避免繁琐的拼接。
scala val name = "Bob" val age = 25 // 使用 s 插值器 val message = s"My name is $name, and my age is $age." // 可以在 ${} 中进行计算 val nextYearMessage = s"Next year, I will be ${age + 1} years old." println(message) println(nextYearMessage)

三引号定义: 用于创建包含换行符的多行字符串,常用于 SQL 语句或大段文本。
scala val sqlQuery = """ SELECT id, name FROM users WHERE country = 'CN' """ println(sqlQuery)

## 五、类型转换 自动类型转换: 范围小的数据类型(如 Int)与范围大的数据类型(如 Double)运算时,会自动向大范围转换。
scala val i: Int = 10 val d: Double = 3.14 val result = i + d // result 的类型会被自动推断为 Double println(result)

强制类型转换: 使用 .toXxx 方法将范围大的类型转换为范围小的类型,可能会有精度损失。
scala val doubleNum: Double = 5.9 val intNum: Int = doubleNum.toInt // 结果为 5,小数部分被截断 println(intNum) val longNum: Long = 1234567890123L val shortNum: Short = longNum.toShort // 可能会发生溢出,导致结果不正确 println(shortNum)

与 String 的转换:
任意类型转 String: value.toStringvalue + "" String 转其他类型: str.toInt, str.toDouble, str.toBoolean 等。

scala // 任意类型转 String val numToString = 100.toString val boolToString = true + "" println(numToString) // String 转其他类型 val stringToInt = "123".toInt val stringToDouble = "45.6".toDouble println(stringToInt + stringToDouble)

## 六、运算符

下表总结了 Scala 中的主要运算符:
| 运算符类型 | 符号 | 说明 |
| :--- | :--- | :--- |
| 算术运算符 | +, -, *, /, % | / 用于整数时只保留商% 用于取余。
没有 ++-- 运算符。 |
| 赋值运算符 | =, +=, -=, *=, /=, %= | 与 C/Java 类似。 |
| 关系运算符 | ==, !=, >, <, >=, <= | == 用于比较值 (类似Java的 .equals())。
.eq() 用于比较引用地址。 |
| 逻辑运算符 | && (与), || (或), ! (非) | 短路逻辑运算。 |
| 位运算符 | &, |, ^, ~, <<, >> | 按位进行二进制运算。 |

代码案例 (==.eq() 的区别):

val s1 = "hello"
val s2 = "hello"
val s3 = new String("hello")

println(s1 == s2) // true, 因为值相等
println(s1 == s3) // true, 因为值相等
println(s1.eq(s2)) // true, 因为字符串字面量指向常量池中的同一对象
println(s1.eq(s3)) // false, 因为 s3 是一个新对象,引用地址不同

七、用户输入

导入包:
scala import scala.io.StdIn

读取数据:
StdIn.readLine(): 读取一行字符串 StdIn.readInt(): 读取一个 整数
其他方法如 readDouble(), readBoolean() 等。

代码案例:
scala import scala.io.StdIn println("请输入您的名字:") val name = StdIn.readLine() println("请输入您的年龄:") val age = StdIn.readInt() println(s"你好, $name! 你明年将 ${age + 1} 岁。")

---

## *练习题

题目一:基础输出
编写一个 Scala 程序,使用 println 在控制台输出三行内容,分别是你的名字、你最喜欢的编程语言和你学习 Scala 的目标。

题目二:变量与常量
声明一个名为 favoriteBook常量,并赋值为你最喜欢的一本书名。再声明一个名为 booksReadThisYear变量,初始值为5。然后将 booksReadThisYear 的值加1,并分别打印出这两个量的值。

题目三:字符串插值
定义两个 valcity (值为 "Beijing") 和 temperature (值为 28.5)。使用 s 字符串插值器创建一个句子,如 "The current temperature in Beijing is 28.5 degrees Celsius.",并打印出来。

题目四:用户输入与计算
编写一个程序,提示用户输入他们的出生年份,然后读取该输入,计算并打印出他们的大致年龄 (假设当前年份为2024年)。

题目五:类型转换
定义一个字符串 val priceString = "99.99"。将其转换为 Double 类型,然后乘以 0.8 (打八折),最后将计算结果打印出来。

题目六:==.eq() 的区别
解释以下代码的输出结果,并说明原因:

val list1 = List(1, 2, 3)
val list2 = List(1, 2, 3)
val list3 = list1
println(list1 == list2)
println(list1.eq(list2))
println(list1.eq(list3))

题目七:多行字符串
使用三引号创建一个包含 JSON 格式数据的多行字符串变量,并将其打印出来。

题目八:惰性求值 (lazy val)
编写一小段代码,定义一个 lazy val resource,其初始化代码块会打印 "Resource is being initialized."。在定义之后,先打印 "Main logic started.",然后再访问 resource 变量。观察并解释输出的顺序。

题目九:数据类型与运算
定义一个 Int 类型的变量 a 值为 7,一个 Double 类型的变量 b 值为 2.0。计算 a / b 并打印结果。解释结果的数据类型为什么是那样的。

题目十:标识符命名
判断以下标识符命名是否合法,并说明原因。如果不合法或不符合规范,请给出修改建议。

  1. 1stPlace
  2. user-name
  3. val
  4. user_age
  5. Myvariable

题目十一:算术运算符
编写代码计算 25 除以 4 的商和余数,并分别打印出来。

题目十二:类型推断
声明三个常量 a, b, c,分别用 10, 10L, 10.0f 初始化,不显式指定类型。然后分别打印出这三个常量,并通过 getClass.getSimpleName 打印出它们的类型。

题目十三:与 Null 类型的交互
尝试将 null 赋值给一个 Int 类型的变量和一个 String 类型的变量。观察哪一个会编译错误,并解释为什么。

题目十四:用户输入组合
编写一个程序,先后提示用户输入商品名称(String)、单价(Double)和数量(Int),然后计算总价,并使用字符串插值打印出一条总结信息,如 "您购买的 [商品名称] 总价为: [总价] 元"。

题目十五:Any 类型
创建一个 List,其中包含一个整数、一个字符串和一个布尔值。Scala 会将这个 List 的类型推断为什么?编写代码验证你的猜想。

答案与解析

答案一:

println("我的名字是:[你的名字]")
println("我最喜欢的编程语言是:Scala")
println("我学习 Scala 的目标是:掌握大数据开发技术")
  • 解析: println() 函数会在打印完每行内容后自动换行。

答案二:

val favoriteBook = "The Three-Body Problem"
var booksReadThisYear = 5
booksReadThisYear += 1 // 或 booksReadThisYear = booksReadThisYear + 1

println(s"我最喜欢的书是: $favoriteBook")
println(s"我今年已经读了 $booksReadThisYear 本书。")
  • 解析: val 定义的 favoriteBook 是不可变的。var 定义的 booksReadThisYear 是可变的,可以使用 += 运算符进行自增操作。

答案三:

val city = "Beijing"
val temperature = 28.5
val sentence = s"The current temperature in $city is $temperature degrees Celsius."
println(sentence)
  • 解析: s 插值器可以直接在字符串中使用 $variable 来嵌入变量的值。

答案四:

import scala.io.StdIn

println("请输入您的出生年份:")
val birthYear = StdIn.readInt()
val currentYear = 2024
val age = currentYear - birthYear
println(s"您的大致年龄是: $age 岁。")
  • 解析: StdIn.readInt() 用于读取用户输入的整数。然后进行简单的数学运算并输出结果。

答案五:

val priceString = "99.99"
val priceDouble = priceString.toDouble
val discountedPrice = priceDouble * 0.8
println(s"折扣后的价格是: $discountedPrice")
  • 解析: 使用 .toDouble 方法可以将格式正确的字符串转换为 Double 类型,以便进行浮点数运算。

答案六:
输出结果:

true
false
true
  • 解析:
    • list1 == list2 (true): == 在 Scala 中比较的是内容值list1list2 的内容都是 (1, 2, 3),所以它们相等。
    • list1.eq(list2) (false): .eq() 比较的是对象的引用地址list1list2 是两个独立创建的对象,它们在内存中的地址不同。
    • list1.eq(list3) (true): list3 = list1 这条语句是引用赋值list3list1 指向内存中同一个 List 对象,所以它们的引用地址相同。

答案七:

val jsonData = """
{
  "name": "John Doe",
  "age": 30,
  "isStudent": false,
  "courses": ["History", "Math"]
}
"""
println(jsonData)
  • 解析: 三引号 """...""" 允许字符串跨越多行,并且保留内部的换行和格式,非常适合表示格式化的文本。

答案八:

lazy val resource = {
println("Resource is being initialized.")
"Some heavy resource"
}

println("Main logic started.")
println(s"Accessing resource for the first time: $resource")

输出顺序:

Main logic started.
Resource is being initialized.
Accessing resource for the first time: Some heavy resource
  • 解析: lazy val 声明的变量直到第一次被访问时(即在第二个 println 语句中 $resource 被求值时),其初始化代码块才会被执行。因此,"Main logic started." 会先被打印出来。

答案九:

val a: Int = 7
val b: Double = 2.0
val result = a / b
println(result) // 输出 3.5
  • 解析: 结果的数据类型是 Double。这是因为 Scala 的自动类型转换规则。当一个 Int 和一个 Double 进行运算时,为了避免精度损失Int 类型的值 a 会被自动提升 (widened)Double 类型,然后进行浮点数除法。

答案十:

  1. 1stPlace: 不合法。标识符不能以数字开头。建议修改为 firstPlace
  2. user-name: 不合法。标识符不能包含连字符 -。建议修改为 userName (小驼峰规范)。
  3. val: 不合法val 是 Scala 的关键字。建议修改为 value 或其他非关键字名称。
  4. user_age: 合法,但不符合 Scala 的小驼峰命名规范。建议修改为 userAge
  5. Myvariable: 合法,但不符合变量的小驼峰命名规范 (大驼峰通常用于类名)。建议修改为 myVariable

答案十一:

val dividend = 25
val divisor = 4
val quotient = dividend / divisor
val remainder = dividend % divisor
println(s"$dividend 除以 $divisor 的商是: $quotient")
println(s"$dividend 除以 $divisor 的余数是: $remainder")
  • 解析: / 在用于两个整数时执行整数除法,% 用于计算余数。

答案十二:

val a = 10
val b = 10L
val c = 10.0f

println(s"a = $a, type = ${a.getClass.getSimpleName}")
println(s"b = $b, type = ${b.getClass.getSimpleName}")
println(s"c = $c, type = ${c.getClass.getSimpleName}")
  • 解析: Scala的类型推断会根据字面值的形式来确定类型。10Int10LLong10.0fFloatgetClass.getSimpleName 是一个方便的方法来查看对象的运行时类型名称。

答案十三:

// var myInt: Int = null // 这行代码会编译错误
var myString: String = null // 这行代码是合法的
  • 解析: Int 是一个值类型 (Value Type),它的父类是 AnyValAnyVal 类型的变量不能被赋值为 null。而 String 是一个引用类型 (Reference Type),它的父类是 AnyRefNull 类型是所有 AnyRef 类型的子类,因此 null 可以赋值给任何 AnyRef 类型的变量。

答案十四:

import scala.io.StdIn

println("请输入商品名称:")
val itemName = StdIn.readLine()

println("请输入商品单价:")
val unitPrice = StdIn.readDouble()

println("请输入购买数量:")
val quantity = StdIn.readInt()

val totalPrice = unitPrice * quantity
println(s"您购买的 $itemName 总价为: $totalPrice 元")
  • 解析: 这个练习组合了 readLine, readDouble, readInt 来获取不同类型的用户输入,然后进行计算,并使用 s 插值器格式化输出。

答案十五:

val mixedList = List(42, "hello", true)
println(s"The list is: $mixedList")
println(s"The inferred type is: ${mixedList.getClass.getSimpleName}")
// 实际上,更精确的类型是 List[Any]
  • 解析: Scala 会将这个 List 的类型推断为 List[Any]。因为 List 中的元素类型必须是统一的,编译器会寻找所有元素的最近公共父类型。整数(Int)、字符串(String)和布尔(Boolean) 的最近公共父类型是 Any。因此,mixedList 是一个可以容纳任何类型元素的列表。
相关文章
|
1月前
|
人工智能 测试技术 Python
AI也有“智商”吗?我们到底该用什么标准来评估它?
AI也有“智商”吗?我们到底该用什么标准来评估它?
177 8
|
1月前
|
存储 编解码 JSON
PowerToys微软最强工具箱软件集!好用的Windows增强工具箱,降低内存和存储占用
Microsoft PowerToys是微软官方推出的免费开源工具集,专为Win10/11设计,集成20+实用功能,如高级粘贴、颜色拾取、窗口布局管理(FancyZones)、文本提取、屏幕缩放标注等,全面提升办公效率,支持开发者、设计师等高效操作,模块化自由配置,堪称Windows效率神器。
284 7
|
1月前
|
设计模式 算法 机器人
六、Scala特质
特质就像一盒随取随用的拼装零件:类能一次混入好几个,拿来补充行为很方便;还能在创建对象时临时加上功能。它甚至能继承类,对混入者提出限制。多个特质一起用时有线性化执行顺序,不乱套。再配合设计模式,像适配器、模板方法、职责链这些套路,都能用 trait 玩得很自然。
147 14
|
1月前
|
Web App开发 网络协议 Java
Windows 终端命令详解:PowerShell 初学者指南
Windows 终端是一个命令行工具,允许用户通过文本命令与系统交互,执行文件管理、系统配置和网络诊断等操作。PowerShell 是 Windows 终端的现代版本,相比传统的命令提示符(CMD),它功能更强大,支持脚本编写和复杂任务处理。本文将以 PowerShell 为主,带你从零开始学习。
|
1月前
|
Java Scala
二、Scala流程控制:分支与循环
在Scala里,连if判断和for循环这些基本功都被赋予了“超能力”。if语句不仅能做判断,还能像三元运算符一样直接“返回值”,让代码更紧凑。而for循环更是个“全能选手”,不仅能遍历范围,还能加上if守卫做筛选,最厉害的是配合yield关键字,能直接把循环结果“变”成一个全新的集合。至于while循环虽然还在,但像break这种命令,Scala则更推荐你用函数式的思路去解决。
|
1月前
|
缓存 并行计算 算法
TensorRT 和 ONNX Runtime 推理优化实战:10 个降低延迟的工程技巧
模型性能优化关键在于细节:固定输入形状、预热、I/O绑定、精度量化、图优化与CUDA Graph等小技巧,无需重构代码即可显著降低延迟。结合ONNX Runtime与TensorRT最佳实践,每个环节节省几毫秒,累积提升用户体验。生产环境实测有效,低延迟从此有据可依。
203 9
|
1月前
|
安全 Scala
五、Scala继承与多态
继承能让子类直接拿到父类的方法和属性,还能用override改写、super调父类原版,final则用来堵继承的路。抽象类像个契约,子类必须补上没写完的部分。再配合isInstanceOf、asInstanceOf做类型判断和转换,甚至还能整匿名内部类来写个一次性小工具,挺灵活的。
|
1月前
|
应用服务中间件 Shell nginx
七、Docker核心技术:深入理解网络模式 (Bridge, Host, None, Container)
容器不仅仅是孤立的运行环境,它们需要相互通信,也需要与外部世界进行交互。理解 Docker 的不同网络模式,是构建和部署复杂多容器应用的关键。本节将深入探讨 Docker 原生提供的四种网络模式以及强烈推荐使用的自定义网络。要让它们通信,需要将其中一个容器也连接到另一个网络上。默认 bridge 网络不支持容器名DNS解析,只能通过IP地址通信。容器没有自己的独立IP地址,它共享宿主机的IP。网络模式启动一个容器后,如何查看该容器的IP地址?时,该容器默认会连接到哪个网络?模式运行,并且其内部的应用监听。
|
1月前
|
Java Scala Python
四、Scala深入面向对象:类、对象与伴生关系
类是蓝图造对象,成员变量方法随手玩;主辅构造器+权限修饰搞封装,单例与伴生对象瞬间懂;main入口、下划线初始化的坑也亮了,读完就能撸代码。
|
1月前
|
编解码 安全 Linux
runc container breakouts via procfs writes: CVE-2025-31133, CVE-2025-52565, and CVE-2025-52881
There are 3 High severity vulnerabilities in runc. As maintainers of OCI Runtime, I strongly recommend updating.
257 7