2 数据类型与基本运算
笔记衔接前序《Scala 入门教程》,严格匹配课程体系,针对有 Java 基础的大数据专业,全程Java 语法对比 + 可运行代码案例 + 随堂练习 + 避坑指南 + 作业全解析,贴合 Spark/Flink 大数据 ETL 开发的实际应用,所有案例可直接复制运行,学完可直接衔接 Scala 控制结构与面向对象编程。
前置知识:Scala 入门基础、val/var 变量定义、Java 基础数据类型
开发环境:JDK1.8+、Scala2.12.x、IDEA + Scala 插件
核心学习目标:
- 掌握 Scala 全量数据类型,明确与 Java 数据类型的核心差异
- 熟练使用 Scala 字符串操作、插值、正则处理(大数据 ETL 核心技能)
- 掌握 Scala 各类运算符的使用、底层原理与优先级规则
- 彻底规避 Scala 数据类型与运算的高频坑点(浮点数精度、相等性判断等)
第一章 Scala 数据类型体系
1.1 与 Java 的核心差异(必背)
Scala 是纯面向对象语言,这是和 Java 最核心的区别:
- Java 分为基本数据类型(int/char 等)和包装类(Integer/Character 等),两套体系需要自动装箱 / 拆箱;
- Scala 中所有数据类型都是对象,没有基本类型的概念,所有类型都继承自顶级父类
Any,统一的对象体系避免了装箱拆箱的性能损耗,更适合大数据的海量数据计算场景。
1.2 Scala 数据类型总览
Scala 数据类型分为数值类型和非数值类型两大类,完整类型体系如下表,所有类型的取值范围与 Java 对应类型完全一致,Scala 编译器会在字节码中自动转为 Java 的基本类型,保证性能:
| 大类 | 数据类型 | 核心描述 | 取值范围 |
|---|---|---|---|
| 数值类型 | 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 | |
| Boolean | 布尔类型 | true / false | |
| 非数值类型 | String | 字符串,字符序列 | 任意长度字符 |
| Unit | 无值,等同于 Java 的 void | 唯一实例为() | |
| Null | 空引用类型 | 唯一实例为null | |
| Nothing | 所有类型的子类,Scala 类层级最底端 | 无实例 | |
| Any | 所有类型的顶级超类 | - | |
| AnyRef | 所有引用类型的基类,等同于 Java 的 Object | - |

1.3 数值类型详解
Scala的基本类型与Java的基本类型对应的取值区间完全相同,使得Scala编译器可以在产生的字节码中将Scala的值类型转成Java的基本类型。

1.3.1 整数类型
Scala 整数类型包括 Byte、Short、Int、Long,核心规则与 Java 一致,但有 2 个关键差异:
- 默认整数类型:整数字面量默认是 Int 类型,若要声明 Long 类型,需在数值后加
L/l后缀; - 字面量规则:Scala不支持八进制和以 0 开头的字面量(Java 支持),仅支持十进制、十六进制(0x 开头)、二进制(0B 开头,Scala2.12 + 支持)。
代码案例与避坑
object IntTypeDemo {
def main(args: Array[String]): Unit = {
// 1. 基础声明
val a: Byte = 123 // 合法,Byte取值范围内
// val b: Byte = 128 // 编译报错,超出Byte最大值127
val c: Short = 32767 // 合法,Short最大值
// val d: Short = 32768 // 编译报错,超出Short最大值32767
val e: Int = 100 // 十进制,默认Int类型
val f: Long = 100L // 声明Long类型,必须加L后缀
val g: Int = 0x000F // 十六进制,值为15
val h: Int = 0B1010 // 二进制,值为10
// 2. 非法字面量(与Java核心差异)
// val i = 038 // 编译报错,Scala不支持0开头的八进制字面量
// 3. 打印取值范围
println(s"Byte最大值:${Byte.MaxValue},最小值:${Byte.MinValue}")
println(s"Short最大值:${Short.MaxValue},最小值:${Short.MinValue}")
println(s"Int最大值:${Int.MaxValue},最小值:${Int.MinValue}")
}
}1.3.2 浮点类型
Scala 浮点类型包括 Float、Double,核心规则:
- 默认浮点类型:小数字面量默认是 Double 类型,若要声明 Float 类型,需在数值后加
F/f后缀; - 科学计数法:支持
E/e表示科学计数法,例如12E2= 1200.0; - 精度问题:浮点数存在二进制精度丢失问题,是所有编程语言的共性,金融 / 金额计算场景需用 BigDecimal 解决。
代码案例
object FloatTypeDemo {
def main(args: Array[String]): Unit = {
val a: Double = 1.2 // 默认Double类型
val b: Float = 1.2f // 声明Float类型,必须加f后缀
val c: Double = 12E2 // 科学计数法,值为1200.0
val d: Float = 12E2f // Float类型科学计数法
println(s"a=$a, b=$b, c=$c, d=$d")
// 浮点数精度问题演示
println(s"0.1 + 0.2 = ${0.1 + 0.2}") // 输出0.30000000000000004,而非0.3
}
}1.3.3 字符类型 Char
Scala 的 Char 是 16 位无符号 Unicode 字符,核心特性:
- 字面量表示:单引号包裹单个字符,例如
'a'; - Unicode 编码:支持
\uXXXX格式的 Unicode 编码表示,且\uXXXX可出现在 Scala 代码的任意位置(不仅限于字符字面量); - 转义字符:支持标准的转义字符,用于表示特殊字符;
- 数值运算:Char 类型可直接参与数值运算,底层使用对应的 Unicode 编码值计算。
常用转义字符表
表格
| 转义字符 | Unicode | 描述 |
|---|---|---|
\b | \u0008 | 退格 |
\t | \u0009 | 水平制表符(Tab) |
\n | \u000a | 换行 |
\r | \u000d | 回车 |
\" | \u0022 | 双引号 |
\' | \u0027 | 单引号 |
\\ | \u005c | 反斜杠 |
代码案例
object CharTypeDemo {
def main(args: Array[String]): Unit = {
// 1. 基础字符声明
val c1: Char = 'a'
val c2: Char = '\u0041' // Unicode编码,对应'A'
println(s"c1=$c1, c2=$c2")
// 2. Unicode编码可出现在代码任意位置
val \u0041\u0042 = 10 // 等价于 val AB = 10
println(s"AB=$AB")
// 3. 转义字符
println("hello\tscala\n换行")
println("双引号:\",单引号:\',反斜杠:\\")
// 4. 字符参与数值运算
println(s"'a' + 7 = ${'a' + 7}") // 'a'的Unicode值是97,输出104
println(s"97.toChar = ${97.toChar}") // 数值转字符,输出'a'
}
}1.3.4 布尔类型 Boolean
Scala 的 Boolean 类型只有两个值:true和false,与 Java 完全一致,仅用于逻辑判断,不能参与数值运算(Java 中 boolean 和 int 可强制转换,Scala 禁止)。
1.4 非数值类型详解
非数值类型是大数据开发的核心,尤其是 String 类型,ETL 数据清洗 90% 的场景都在处理字符串,本节重点讲解。
1.4.1 String 字符串类型
Scala 的 String 本质就是 Java 的java.lang.String,完全兼容 Java 的所有 String 方法,同时扩展了更强大的特性,更适合大数据场景的字符串处理。
特性 1:字符串的两种声明方式
表格
| 声明方式 | 语法 | 适用场景 |
|---|---|---|
| 双引号 | "内容" | 普通单行字符串,与 Java 一致 |
| 三引号 | """内容""" | 多行字符串、包含转义字符 / 引号的字符串,无需手动转义 |
代码案例
object StringDeclareDemo {
def main(args: Array[String]): Unit = {
// 1. 双引号单行字符串
val s1 = "hello scala"
println(s1)
// 2. 三引号多行字符串,无需转义
val s2 =
"""
|hello scala
|hello java
|"这是双引号",'这是单引号',\这是反斜杠,无需转义
|""".stripMargin // stripMargin自动对齐,默认以|为边界
println(s2)
// 3. 按要求输出特殊字符串
println("hello scala") // 输出hello scala字符串
println("\"hello scala\"") // 输出“hello scala”字符串
println("\\hello scala\\") // 输出\hello scala\字符串
}
}特性 2:字符串常用方法(大数据 ETL 高频)
完全兼容 Java 方法,重点掌握以下高频方法:
表格
| 方法 | 作用 | 大数据场景 |
|---|---|---|
charAt(index) | 返回指定索引的字符 | 提取固定位置字符(如身份证号省份编码) |
indexOf(ch) | 返回指定字符首次出现的索引 | 查找分隔符位置 |
split(regex) | 按正则 / 分隔符拆分字符串,返回数组 | 文本拆分、单词统计、字段分隔 |
startsWith(prefix) | 判断是否以指定前缀开头 | 数据筛选(如筛选北京身份证号) |
matches(regex) | 判断是否匹配正则表达式 | 数据格式校验(手机号、身份证) |
replaceAll(regex, replacement) | 替换所有匹配正则的内容 | 数据脱敏、内容替换 |
replaceFirst(regex, replacement) | 替换第一个匹配的内容 | 局部内容修改 |
substring(start, end) | 截取子字符串 | 提取固定长度内容(身份证生日) |
大数据场景案例
object StringMethodDemo {
def main(args: Array[String]): Unit = {
// 案例1:判断身份证号是否为北京身份证(北京身份证前两位11)
val idCard = "110101199001011234"
val isBeijing = idCard.startsWith("11")
println(s"是否为北京身份证:$isBeijing") // 输出true
// 案例2:身份证号后四位脱敏(用*代替)
val idCard2 = "210404197681232113"
val desensitizedId = idCard2.substring(0, idCard2.length - 4) + "****"
println(s"脱敏后身份证:$desensitizedId") // 输出210404197681232****
// 案例3:主机名匹配,不区分大小写
val hostStr = "host01,host03,hadoop01,hadoop010"
val targetHost = "HADOOP01"
val isExist = hostStr.toLowerCase.split(",").contains(targetHost.toLowerCase)
if (isExist) println(s"find $targetHost") else println(s"unfind $targetHost")
// 案例4:薪资字符串替换,将数字用*代替
val salaryStr = "his basic salary is 10000,and bonus is 20000"
val replaceStr = salaryStr.replaceAll("\\d+", "*")
println(s"替换后:$replaceStr")
// 输出:his basic salary is *,and bonus is *
}
}特性 3:字符串遍历与转换
Scala 提供 3 种遍历方式,可对每个字符进行处理,是数据清洗的常用操作:
for循环foreach遍历map映射转换
代码案例:字符大写转换
object StringTraverseDemo {
def main(args: Array[String]): Unit = {
val str = "scala"
// 方式1:for循环遍历
println("=== for循环遍历 ===")
for (c <- str) {
print(c + " ")
}
println()
// 方式2:foreach遍历
println("=== foreach遍历 ===")
str.foreach(c => print(c.toUpper + " "))
println()
// 方式3:map转换,返回新字符串
println("=== map转换大写 ===")
val upperStr = str.map(c => c.toUpper)
println(upperStr) // 输出SCALA
// 练习:提取字符串"123456"偶数部分,返回新字符串
val numStr = "123456"
val evenStr = for (c <- numStr if c.toInt % 2 == 0) yield c
println(s"偶数部分:$evenStr") // 输出246
// 练习:将字符串"123456"变成"234567"
val newStr = numStr.map(c => (c.toInt + 1).toChar)
println(s"转换后:$newStr") // 输出234567
}
}特性 4:字符串插值(Scala 独有,开发首选)
Scala 提供 3 种字符串插值方式,彻底替代 Java 的字符串拼接,代码更简洁、可读性更高,是开发中必用的特性。
表格
| 插值方式 | 语法 | 核心作用 |
|---|---|---|
| s 插值 | s"内容$变量名" | 直接在字符串中嵌入变量、表达式 |
| f 插值 | f"内容$变量名%格式化" | 格式化字符串,支持精度控制,等同于 Java 的 String.format |
| raw 插值 | raw"内容$变量名" | 原样输出字符串,不解析转义字符 |
代码案例
object StringInterpolateDemo {
def main(args: Array[String]): Unit = {
// 定义变量
val name = "jason"
val age = 10
val weight = 180.0
// 1. s插值:嵌入变量、表达式
val sStr = s"name is $name, $age years old and weighs is $weight kg"
println(sStr)
// 嵌入表达式
val exprStr = s"are you 10 ? ${age == 10}"
println(exprStr) // 输出are you 10 ? true
// 2. f插值:格式化输出,控制精度
val fStr = f"name is $name, $age years old and weighs is $weight%.2f kg"
println(fStr) // 保留2位小数,输出weighs is 180.00 kg
// 3. raw插值:原样输出,不解析转义字符
val rawStr = raw"name is $name, $age years old and weighs is $weight%.2f kg\n1000g"
println(rawStr) // \n不会被解析为换行,直接原样输出
}
}特性 5:正则表达式处理
Scala 提供极简的正则操作方式,用于字符串的查找、匹配、替换,是大数据日志解析的核心技能。
- 正则对象创建:
"正则表达式".r或new Regex("正则表达式") - 查找所有匹配:
regex.findAllIn(str) - 查找第一个匹配:
regex.findFirstIn(str)
代码案例
import scala.util.matching.Regex
object StringRegexDemo {
def main(args: Array[String]): Unit = {
val str = "his basic salary is 10000,and bonus is 20000"
// 1. 创建正则对象:匹配数字
val numRegex: Regex = "\\d+".r
// 2. 查找所有匹配的数字
println("=== 所有匹配的数字 ===")
numRegex.findAllIn(str).foreach(println)
// 3. 查找第一个匹配的数字
val firstNum = numRegex.findFirstIn(str)
println(s"第一个数字:$firstNum") // 输出Some(10000)
}
}1.4.2 其他非数值类型
Unit 类型:等同于 Java 的
void,表示方法无返回值,唯一实例是()。Null 类型:所有引用类型(AnyRef)的子类,唯一实例是
null,不能赋值给数值类型。Nothing 类型:所有类型的子类,无实例,主要用于异常抛出的返回值类型。
元组类型:Scala 特有的类型,是不同类型值的集合,最多支持 22 个元素,语法为
(元素1, 元素2, ...),访问方式为元组名._索引(索引从 1 开始)。scala// 元组案例 val tuple = ("张三", 20, 98.5) println(s"姓名:${tuple._1},年龄:${tuple._2},分数:${tuple._3}") // 元组遍历 tuple.productIterator.foreach(println)
1.5 随堂练习(课堂练习全解析)
判断以下语句是否合法
表格
语句 是否合法 原因 val a\u0042\u0046 = 10合法 Unicode 编码可出现在标识符中,等价于 val aBF = 10val octonray=038不合法 Scala 不支持 0 开头的八进制字面量 val binary=0B10101010合法(Scala2.12+) 0B 开头表示二进制字面量 val little:Short=123合法 123 在 Short 的取值范围内 val big:Short = 32768不合法 超出 Short 最大值 32767 提取字符串 “123456” 偶数部分,返回新字符串
scalaval str = "123456" val result = for (c <- str if c.toInt % 2 == 0) yield c // 结果:"246"判断身份证号是否为北京身份证
scalaval idCard = "110101199001011234" val isBeijing = idCard.startsWith("11")将字符串 "123456" 变成 "234567"
scalaval str = "123456" val result = str.map(c => (c.toInt + 1).toChar) // 结果:"234567"
第二章 Scala 的基本运算
Scala 的运算符本质上都是对象的方法调用,这是 Scala 纯面向对象的核心体现,例如1 + 2本质是1.+(2),3 > 2本质是3.>(2)。
2.1 算术运算符
算术运算符用于数值计算,与 Java 完全一致,核心规则如下:
表格
| 运算符 | 描述 | 示例 | 方法调用本质 |
|---|---|---|---|
+ | 加法 | 1 + 2 | 1.+(2) |
- | 减法 | 5 - 3 | 5.-(3) |
* | 乘法 | 2 * 3 | 2.*(3) |
/ | 除法 | 6 / 2 | 6./(2) |
% | 取余(模运算) | 7 % 3 | 7.%(3) |
关键注意点
- 整数除法:两个整数相除,结果只保留整数部分,舍弃小数,例如
5 / 2 = 2,而非 2.5; - 除法 / 取余运算中,除数不能为 0,否则运行时抛出算术异常。
2.2 关系运算符
关系运算符用于两个值的比较,返回 Boolean 类型的 true/false,与 Java 完全一致:
表格
| 运算符 | 描述 | 示例 |
|---|---|---|
== | 等于(值相等) | 5 == 5 → true |
!= | 不等于 | 5 != 3 → true |
> | 大于 | 5 > 3 → true |
< | 小于 | 5 < 3 → false |
>= | 大于等于 | 5 >= 5 → true |
<= | 小于等于 | 5 <= 3 → false |
重点:Scala 的
==与 Java 完全不同,后续相等性判断章节会详细讲解。
2.3 逻辑运算符与短路运算
逻辑运算符用于多个 Boolean 表达式的组合运算,核心规则与 Java 一致,重点掌握短路运算。
表格
| 运算符 | 描述 | 运算规则 | ||
|---|---|---|---|---|
&& | 逻辑与(短路与) | 所有表达式为 true,结果才为 true;第一个表达式为 false,直接返回 false,后续表达式不执行 | ||
| ` | ` | 逻辑或(短路或) | 任意一个表达式为 true,结果就为 true;第一个表达式为 true,直接返回 true,后续表达式不执行 | |
! | 逻辑非 | 取反,true 变 false,false 变 true | ||
& | 逻辑与(非短路) | 所有表达式为 true,结果才为 true;无论第一个表达式结果如何,所有表达式都会执行 | ||
| ` | ` | 逻辑或(非短路) | 任意一个表达式为 true,结果就为 true;无论第一个表达式结果如何,所有表达式都会执行 |
代码案例:短路运算演示
object LogicDemo {
def main(args: Array[String]): Unit = {
// 定义两个测试方法
def isChinese(): Boolean = {
println("执行isChinese方法")
false
}
def isSoldier(): Boolean = {
println("执行isSoldier方法")
true
}
println("=== 短路与 && ===")
val result1 = isChinese() && isSoldier()
println(s"结果:$result1")
// 输出:仅执行isChinese方法,isSoldier方法不执行,结果false
println("\n=== 短路或 || ===")
val result2 = isSoldier() || isChinese()
println(s"结果:$result2")
// 输出:仅执行isSoldier方法,isChinese方法不执行,结果true
println("\n=== 非短路与 & ===")
val result3 = isChinese() & isSoldier()
println(s"结果:$result3")
// 输出:两个方法都执行,结果false
}
}2.4 赋值运算符
赋值运算符用于给变量赋值,分为基础赋值和复合赋值,与 Java 基本一致,但有 2 个核心差异。
基础赋值运算符
=:将右边的值赋值给左边的变量,例如val a = 10。
复合赋值运算符
表格
| 运算符 | 描述 | 等价于 | |||
|---|---|---|---|---|---|
+= | 相加后赋值 | a += b → a = a + b | |||
-= | 相减后赋值 | a -= b → a = a - b | |||
*= | 相乘后赋值 | a *= b → a = a * b | |||
/= | 相除后赋值 | a /= b → a = a / b | |||
%= | 取余后赋值 | a %= b → a = a % b | |||
<<= | 左移后赋值 | a <<= 2 → a = a << 2 | |||
>>= | 右移后赋值 | a >>= 2 → a = a >> 2 | |||
&= | 按位与后赋值 | a &= 2 → a = a & 2 | |||
^= | 按位异或后赋值 | a ^= 2 → a = a ^ 2 | |||
| ` | =` | 按位或后赋值 | `a | = 2→a = a | 2` |
与 Java 的核心差异(必背)
- Scala 没有
++和--自增自减运算符,用+=1和-=1替代; - Scala 不支持连续赋值
a = b = c,因为 Scala 中赋值表达式的返回值是 Unit,无法赋值给 Int 类型的变量。
代码案例与随堂练习解析
object AssignDemo {
def main(args: Array[String]): Unit = {
// 练习1:计算a、b、c的值
var a = 2
var b = 3
var c = 4
a += b // a = 2 + 3 = 5
b -= c // b = 3 - 4 = -1
c *= a // c = 4 * 5 = 20
println(s"a=$a, b=$b, c=$c") // 输出a=5, b=-1, c=20
// 练习2:连续赋值a=b=c,编译报错
// var a2=2; var b2=3; var c2=4; a2 = b2 = c2;
// 练习3:交换a和b的值
var x = 2
var y = 3
// 方式1:临时变量
val tmp = x
x = y
y = tmp
println(s"x=$x, y=$y") // 输出x=3, y=2
// 方式2:Scala极简写法(元组解构)
var m = 2
var n = 3
val (m1, n1) = (n, m)
println(s"m=$m1, n=$n1") // 输出m=3, n=2
// 替代++自增
var num = 0
num += 1 // 等同于Java的num++
println(s"num=$num") // 输出1
}
}2.5 位运算符
位运算符直接对二进制位进行操作,运算效率极高,大数据开发中常用于性能优化、权限控制等场景。
位运算基础规则
表格
| 运算符 | 描述 | 运算规则(p、q 为二进制位) | |
|---|---|---|---|
& | 按位与 | 两位都为 1,结果为 1,否则为 0 | |
| ` | ` | 按位或 | 两位有一个为 1,结果为 1,否则为 0 |
^ | 按位异或 | 两位不同为 1,相同为 0 | |
~ | 按位取反 | 0 变 1,1 变 0 | |
<< | 左移 | 二进制位整体左移,低位补 0,高位溢出舍弃 | |
>> | 有符号右移 | 二进制位整体右移,高位用符号位填充(正数补 0,负数补 1) | |
>>> | 无符号右移 | 二进制位整体右移,高位统一补 0 |
随堂练习全解析(手动计算原理)
object BitDemo {
def main(args: Array[String]): Unit = {
// 练习1:5 & 9
// 5的二进制:00000101
// 9的二进制:00001001
// 按位与: 00000001 → 结果1
println(s"5 & 9 = ${5 & 9}") // 输出1
// 练习2:5 | 9
// 5的二进制:00000101
// 9的二进制:00001001
// 按位或: 00001101 → 结果13
println(s"5 | 9 = ${5 | 9}") // 输出13
// 练习3:5 ^ 9
// 5的二进制:00000101
// 9的二进制:00001001
// 按位异或:00001100 → 结果12
println(s"5 ^ 9 = ${5 ^ 9}") // 输出12
// 练习4:~(-5)
// -5的补码:11111011
// 按位取反:00000100 → 结果4
println(s"~(-5) = ${~(-5)}") // 输出4
// 练习5:5 << 2 和 -5 << 2
// 5 << 2:左移2位,00000101 → 00010100 → 20
// -5 << 2:左移2位,结果-20
println(s"5 << 2 = ${5 << 2}") // 输出20
println(s"-5 << 2 = ${-5 << 2}") // 输出-20
// 练习6:-5 >> 2 和 -5 >>> 2
// -5 >> 2:有符号右移,高位补1,结果-2
// -5 >>> 2:无符号右移,高位补0,结果1073741822
println(s"-5 >> 2 = ${-5 >> 2}") // 输出-2
println(s"-5 >>> 2 = ${-5 >>> 2}") // 输出1073741822
}
}2.6 运算符优先级
运算符优先级决定了表达式的执行顺序,与 Java 基本一致,优先级从高到低如下表,无需死记,不确定时直接用()改变优先级即可:
表格
| 优先级 | 运算符 | 关联性 | ||
|---|---|---|---|---|
| 1 | () [] | 从左到右 | ||
| 2 | ! ~ | 从右到左 | ||
| 3 | * / % | 从左到右 | ||
| 4 | + - | 从左到右 | ||
| 5 | >> >>> << | 从左到右 | ||
| 6 | > >= < <= | 从左到右 | ||
| 7 | == != | 从左到右 | ||
| 8 | & | 从左到右 | ||
| 9 | ^ | 从左到右 | ||
| 10 | ` | ` | 从左到右 | |
| 11 | && | 从左到右 | ||
| 12 | ` | ` | 从左到右 | |
| 13 | = += -= *= /= %= 等赋值运算符 | 从右到左 | ||
| 14 | , | 从左到右 |
第三章 数据类型与运算的高频坑点与应用
3.1 Scala 的相等性判断(与 Java 核心差异,必背)
Scala 的相等性判断分为值相等和引用相等,与 Java 的规则完全不同,是新手最高频的踩坑点。
表格
| 判断方式 | 关键字 | 作用 | 与 Java 的差异 | null 安全 |
|---|---|---|---|---|
| 值相等 | == / != | 判断两个对象的值是否相等,等同于 Java 的equals()方法 | Java 中==对基本类型是值相等,对引用类型是引用相等;Scala 中==统一判断值相等 | 安全,null 比较不会抛空指针异常 |
| 引用相等 | eq / ne | 判断两个对象的内存地址是否相等,等同于 Java 的==对引用类型的作用 | Java 无此关键字,用==判断引用相等 | 安全 |
代码案例与课堂练习解析
object EqualDemo {
def main(args: Array[String]): Unit = {
val s1 = "hello"
val s2 = "he" + "llo" // 编译期常量拼接,指向常量池的同一个对象
val s3 = "he"
val s4 = "llo"
val s5 = s3 + s4 // 运行期拼接,创建新对象
val s6 = new String("hello") // 强制创建新对象
// 1. 值相等判断 ==
println("=== 值相等 == ===")
println(s1 == s2) // true
println(s1 == s5) // true
println(s1 == s6) // true
println(s5 == s6) // true
// 2. 引用相等 eq
println("\n=== 引用相等 eq ===")
println(s1 eq s2) // true,常量池同一个对象
println(s1 eq s5) // false,不同对象
println(s1 eq s6) // false,不同对象
println(s5 eq s6) // false,不同对象
// 3. null安全,不会抛异常
println("\n=== null安全 ===")
val a: String = null
val b: String = null
println(a == b) // true,不会抛空指针
println(a eq b) // true
}
}3.2 浮点数精度问题与解决方案
问题成因
计算机用二进制存储浮点数,部分十进制小数无法用二进制精确表示,导致精度丢失,例如0.1 + 0.2的结果是0.30000000000000004,而非0.3,这是所有编程语言的共性问题。
解决方案
- 自定义精度阈值:判断两个浮点数的差值的绝对值是否小于指定精度(如 0.0001);
- 使用 BigDecimal:金融、金额等高精度场景,必须用 BigDecimal 进行计算。
代码案例
object FloatPrecisionDemo {
def main(args: Array[String]): Unit = {
// 问题复现
println(s"0.1 + 0.2 = ${0.1 + 0.2}")
println(s"0.1 + 0.2 == 0.3 ? ${0.1 + 0.2 == 0.3}") // 输出false
// 解决方案1:自定义精度阈值
def isEqual(d1: Double, d2: Double, precision: Double = 0.0001): Boolean = {
(d1 - d2).abs < precision
}
println(s"自定义精度判断:${isEqual(0.1 + 0.2, 0.3)}") // 输出true
// 解决方案2:使用BigDecimal(推荐)
val bd1 = BigDecimal(0.1)
val bd2 = BigDecimal(0.2)
val bd3 = BigDecimal(0.3)
println(s"BigDecimal判断:${bd1 + bd2 == bd3}") // 输出true
}
}3.3 大数处理
当数值超出 Long/Double 的取值范围时,使用 Scala 提供的BigInt和BigDecimal类,支持任意精度的整数和小数运算,完全兼容 Java 的 BigInteger/BigDecimal。
object BigNumberDemo {
def main(args: Array[String]): Unit = {
// 超大整数
val bigInt1 = BigInt("9223372036854775808") // 超出Long最大值
val bigInt2 = BigInt("10000000000000000000")
println(s"大数相加:${bigInt1 + bigInt2}")
println(s"大数相乘:${bigInt1 * bigInt2}")
// 高精度小数
val bigDecimal1 = BigDecimal("123456789.123456789123456789")
val bigDecimal2 = BigDecimal("987654321.987654321987654321")
println(s"高精度小数相加:${bigDecimal1 + bigDecimal2}")
}
}3.4 随机数生成
Scala 通过scala.util.Random类生成随机数,大数据开发中常用于生成测试数据,核心用法如下:
import scala.util.Random
object RandomDemo {
def main(args: Array[String]): Unit = {
// 创建随机数对象
val random = new Random()
// 设置随机种子,保证每次运行生成相同的随机序列
// val random = new Random(100)
// 1. 生成0-100的伪随机序列,3个元素
val numSeq = (1 to 3).map(_ => random.nextInt(101))
println(s"0-100随机序列:$numSeq")
// 2. 随机长度的数字集合,最多5个元素
val numList = (1 to random.nextInt(6)).map(_ => random.nextInt(100)).toList
println(s"随机长度数字集合:$numList,长度:${numList.length}")
// 3. 随机长度的字符集合,最多10个元素
val charList = (1 to random.nextInt(11)).map(_ => random.nextPrintableChar()).toList
println(s"随机长度字符集合:$charList")
// 4. 长度为8的随机字符序列(随机密码)
val password = (1 to 8).map(_ => random.nextPrintableChar()).mkString
println(s"8位随机密码:$password")
}
}第四章 课后作业全解析
一、填空题
(1)'a'+7+"hello"的值是104hello,"hello"+'a'+7的值是helloa7。
解析:
'a'的 Unicode 值是 97,97+7=104,数字在前先做数值运算,再拼接字符串;字符串在前,后续所有内容都做字符串拼接。
二、简答题
(1)简述 5&9 和 5|9 求解原理
答:
- 5&9 是按位与运算,先将两个数转为二进制:5 的二进制是
00000101,9 的二进制是00001001;按位与规则是两位都为 1 结果才为 1,逐位运算后得到00000001,转为十进制结果为 1。 - 5|9 是按位或运算,规则是两位有一个为 1 结果就为 1,对 5 和 9 的二进制逐位运算后得到
00001101,转为十进制结果为 13。
(2)简述 5<<2 和 - 5<<2 求解原理
答:
- 5<<2 是左移 2 位运算,5 的二进制是
00000101,整体左移 2 位,低位补 0,得到00010100,转为十进制结果为 20;左移 n 位等价于乘以 2 的 n 次方,5*2²=20。 - -5<<2 是负数左移 2 位,-5 的补码是
11111011,整体左移 2 位,低位补 0,得到11110100,其原码对应的十进制值为 - 20,等价于 - 5*2²=-20。
(3)使用输入值 2.7255,生成字符串”you own $2.73” 可以使用插值字符串实现么?
答:可以,使用 Scala 的 f 插值即可实现,通过格式化控制保留 2 位小数,自动四舍五入。代码实现如下:
val money = 2.7255
val result = f"you own $$$money%.2f"
println(result) // 输出you own $2.73解析:
$$用于输出 $ 符号,%.2f将数值格式化为保留 2 位小数,2.7255 四舍五入后为 2.73。
三、编程题
(1)将数字 128 转换成 Char、String 和 Double,然后再转成 Int
object TypeConvertDemo {
def main(args: Array[String]): Unit = {
val num: Int = 128
println(s"原始数字:$num")
// 1. 转成Char
val charNum: Char = num.toChar
println(s"转Char:$charNum")
// 2. 转成String
val strNum: String = num.toString
println(s"转String:$strNum")
// 3. 转成Double
val doubleNum: Double = num.toDouble
println(s"转Double:$doubleNum")
// 4. 分别转回Int
val intFromChar: Int = charNum.toInt
val intFromStr: Int = strNum.toInt
val intFromDouble: Int = doubleNum.toInt
println(s"Char转回Int:$intFromChar")
println(s"String转回Int:$intFromStr")
println(s"Double转回Int:$intFromDouble")
}
}(2)统计字符串 “hello,scala,hello ,word” 中每个单词的个数
object WordCountDemo {
def main(args: Array[String]): Unit = {
val str = "hello,scala,hello ,word"
// 单词统计完整流程
val wordCount = str
.split("[,,\\s]+") // 按中文逗号、英文逗号、空格拆分
.map((_, 1)) // 每个单词标记为(单词,1)
.groupBy(_._1) // 按单词分组
.map(kv => (kv._1, kv._2.size)) // 统计每个单词的数量
// 打印结果
println("单词统计结果:")
wordCount.foreach(println)
}
}运行结果:
plaintext单词统计结果: (hello,2) (scala,1) (word,1)
学习总结
数据类型体系:Scala 是纯面向对象语言,所有数据类型都是对象,统一继承自 Any,分为数值类型和非数值类型,与 Java 对应类型取值范围完全一致,核心差异是取消了基本类型与包装类的区分。
字符串处理:Scala 完全兼容 Java 的 String 方法,同时扩展了三引号多行字符串、s/f/raw 三种插值、极简正则操作,是大数据 ETL 数据清洗的核心技能,必须熟练掌握。
运算符本质:Scala 中所有运算符都是对象的方法调用,与 Java 核心差异是取消了
++/--运算符,用+=/-=替代,不支持连续赋值。高频坑点:
==判断值相等,eq判断引用地址相等,与 Java 完全相反,且 null 判断安全;- 浮点数存在精度丢失问题,高精度场景必须用 BigDecimal;
- Scala 不支持 0 开头的八进制字面量,与 Java 不同。
大数据应用:字符串处理、正则匹配、随机数生成、位运算优化,都是 Spark/Flink 开发中高频使用的技能,需重点掌握。
下一章预习内容:Scala 的内建控制结构、Scala 的类与面向对象编程。