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




一、基础语法与环境
输出语句: 学习如何向控制台打印信息。
println():打印内容并自动换行。print():打印内容但不换行。
代码案例:
// 使用 println 换行输出 println("Hello, Scala!") println("This is a new line.")// 使用 print 不换行输出 print("This is the first part, ") print("this is the second part.")分号规则: Scala 中行尾的分号是可选的,但在单行编写多条语句时需要用分号隔开。
// 行尾分号是可选的,推荐不写val x =10val 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, calculateSumMyClass, UserService |
二、变量与常量
变量 (var): 其值在程序运行过程中可以改变。
定义格式:var 变量名: 类型 = 初始值
var myAge:Int=30 myAge =31// 合法,因为 myAge 是一个 var println(myAge)常量 (val): 也称为“不可变变量”,赋值后其引用不能再改变。Scala 中推荐优先使用 val。
定义格式:val 变量名: 类型 = 初始值
val myName:String="Alice"// myName = "Bob" // 这行代码会编译错误,因为 val 不能被重新赋值 println(myName)类型推断: Scala 编译器可以根据初始值自动推断变量类型,允许省略类型声明,使代码更简洁。
简洁格式:
val pi =3.14// 编译器自动推断 pi 的类型为 Doubleval language ="Scala"// 编译器自动推断 language 的类型为 String惰性赋值 (lazy val): 变量的值直到第一次被访问时才会被计算和加载,适用于开销较大的初始化操作。
lazyval bigData ={ println("正在进行昂贵的初始化操作...")// 这句话只在 bigData 第一次被访问时打印"这是一个很大的数据集"} println("脚本已启动,但 bigData 尚未初始化。") println(bigData)// 此时 "正在进行昂贵的初始化操作..." 才会被打印 println(bigData)// 第二次访问,不会再执行初始化代码块三、数据类型体系
值类型: Scala 中所有数据都是对象,以下是基础的值类型。
| 类型 | 位数 | 描述 |
|---|---|---|
Byte | 8 | 8位有符号整数 |
Short | 16 | 16位有符号整数 |
Int | 32 | 32位有符号整数 |
Long | 64 | 64位有符号整数, 以 L 或 l 结尾 |
Float | 32 | 32位单精度浮点数, 以 F 或 f 结尾 |
Double | 64 | 64位双精度浮点数 |
Char | 16 | 16位Unicode字符 |
Boolean | - | true 或 false |
Scala 类型层次结构:
Scala 有一个统一的类型系统,所有类型都继承自顶层父类。
Any: 所有类型的根父类。AnyVal: 所有值类型(如Int,Double,Boolean) 的父类。AnyRef: 所有引用类型(如String、自定义类、集合) 的父类,类似于 Java 的java.lang.Object。
特殊类型:
Unit: 表示无值,只有一个实例()。类似于 Java 的void,但Unit是一个真实的类型。Null: 所有AnyRef类型的子类,其唯一实例是null。它不能赋值给AnyVal类型的变量。Nothing: 所有类型的子类,表示没有正常的值。常用于标记异常抛出的表达式。
四、字符串操作
双引号定义:
val greeting ="Hello, World!"字符串插值 (String Interpolation): 在字符串前加 s,使用 ${} 嵌入变量或表达式,避免繁琐的拼接。
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 语句或大段文本。
val sqlQuery =""" SELECT id, name FROM users WHERE country = 'CN' """ println(sqlQuery)五、类型转换
自动类型转换: 范围小的数据类型(如 Int)与范围大的数据类型(如 Double)运算时,会自动向大范围转换。
val i:Int=10val d:Double=3.14val result = i + d // result 的类型会被自动推断为 Double println(result)强制类型转换: 使用 .toXxx 方法将范围大的类型转换为范围小的类型,可能会有精度损失。
val doubleNum:Double=5.9val intNum:Int= doubleNum.toInt // 结果为 5,小数部分被截断 println(intNum)val longNum:Long=1234567890123Lval shortNum:Short= longNum.toShort // 可能会发生溢出,导致结果不正确 println(shortNum)与 String 的转换:
- 任意类型转 String:
value.toString或value + ""。 - String 转其他类型:
str.toInt,str.toDouble,str.toBoolean等。
// 任意类型转 Stringval 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 =newString("hello") println(s1 == s2)// true, 因为值相等 println(s1 == s3)// true, 因为值相等 println(s1.eq(s2))// true, 因为字符串字面量指向常量池中的同一对象 println(s1.eq(s3))// false, 因为 s3 是一个新对象,引用地址不同七、用户输入
导入包:
importscala.io.StdIn 读取数据:
StdIn.readLine(): 读取一行字符串。StdIn.readInt(): 读取一个整数。- 其他方法如
readDouble(),readBoolean()等。
代码案例:
importscala.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,并分别打印出这两个量的值。
题目三:字符串插值
定义两个 val:city (值为 “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 valresource,其初始化代码块会打印 “Resource is being initialized.”。在定义之后,先打印 “Main logic started.”,然后再访问 resource 变量。观察并解释输出的顺序。
题目九:数据类型与运算
定义一个 Int 类型的变量 a 值为 7,一个 Double 类型的变量 b 值为 2.0。计算 a / b 并打印结果。解释结果的数据类型为什么是那样的。
题目十:标识符命名
判断以下标识符命名是否合法,并说明原因。如果不合法或不符合规范,请给出修改建议。
1stPlaceuser-namevaluser_ageMyvariable
题目十一:算术运算符
编写代码计算 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.5val sentence =s"The current temperature in $city is $temperature degrees Celsius." println(sentence)- 解析:
s插值器可以直接在字符串中使用$variable来嵌入变量的值。
答案四:
importscala.io.StdIn println("请输入您的出生年份:")val birthYear = StdIn.readInt()val currentYear =2024val 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类型,以便进行浮点数运算。
答案六:
输出结果:
truefalsetrue- 解析:
list1 == list2(true):==在 Scala 中比较的是内容值。list1和list2的内容都是(1, 2, 3),所以它们相等。list1.eq(list2)(false):.eq()比较的是对象的引用地址。list1和list2是两个独立创建的对象,它们在内存中的地址不同。list1.eq(list3)(true):list3 = list1这条语句是引用赋值,list3和list1指向内存中同一个List对象,所以它们的引用地址相同。
答案七:
val jsonData =""" { "name": "John Doe", "age": 30, "isStudent": false, "courses": ["History", "Math"] } """ println(jsonData)- 解析: 三引号
"""..."""允许字符串跨越多行,并且保留内部的换行和格式,非常适合表示格式化的文本。
答案八:
lazyval 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=7val b:Double=2.0val result = a / b println(result)// 输出 3.5- 解析: 结果的数据类型是
Double。这是因为 Scala 的自动类型转换规则。当一个Int和一个Double进行运算时,为了避免精度损失,Int类型的值a会被自动提升 (widened)为Double类型,然后进行浮点数除法。
答案十:
1stPlace:不合法。标识符不能以数字开头。建议修改为firstPlace。user-name:不合法。标识符不能包含连字符-。建议修改为userName(小驼峰规范)。val:不合法。val是 Scala 的关键字。建议修改为value或其他非关键字名称。user_age:合法,但不符合Scala 的小驼峰命名规范。建议修改为userAge。Myvariable:合法,但不符合变量的小驼峰命名规范(大驼峰通常用于类名)。建议修改为myVariable。
答案十一:
val dividend =25val divisor =4val quotient = dividend / divisor val remainder = dividend % divisor println(s"$dividend 除以 $divisor 的商是: $quotient") println(s"$dividend 除以 $divisor 的余数是: $remainder")- 解析:
/在用于两个整数时执行整数除法,%用于计算余数。
答案十二:
val a =10val b =10Lval 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的类型推断会根据字面值的形式来确定类型。
10是Int,10L是Long,10.0f是Float。getClass.getSimpleName是一个方便的方法来查看对象的运行时类型名称。
答案十三:
// var myInt: Int = null // 这行代码会编译错误var myString:String=null// 这行代码是合法的- 解析:
Int是一个值类型 (Value Type),它的父类是AnyVal。AnyVal类型的变量不能被赋值为null。而String是一个引用类型 (Reference Type),它的父类是AnyRef。Null类型是所有AnyRef类型的子类,因此null可以赋值给任何AnyRef类型的变量。
答案十四:
importscala.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是一个可以容纳任何类型元素的列表。
日期:2025年9月2日
专栏:Scala教程