Scala学习——基础语法学习

Scala 是一种运行在 JVM 上的函数式面向对象语言。它的命名源于其设计目标:随着用户需求一起成长,能够应用于各种编程任务,从小型脚本到大型系统,都能胜任。因此,Scala 提供了一些出众的特性,包括集成面向对象编程和面向函数式编程的各种特性,以及更高层的并发模型。
Why Scala
- Scala 基于 JVM,和 Java 完全兼容,同样具有跨平台、可移植性、方便的垃圾回收等特性;
- Scala比Java更加面向对象;
- Scala更适合大数据的处理。
- Scalā对集合类型数据处理有非常好的支持;
- Spark的底层使用Scala编写。
- Scala应用场景:
- Kafka:分布式消息队列,内部代码经常用来处理并发的问题,用Scala可以大大简化其代码。
- Spark:方便处理多线程场景,另外
caIa这种函数式编程语言
可以大大简化代码。
总结
- Scala 是兼容的:兼容 Java,可以访问庞大的 Java 类库;
- Scala 是精简的:Scala 表达能力强,一行代码抵得上多行 Java 代码,开发速度快。可以让程序短小精悍,看起来更简洁,更优雅;
- Scala 是静态类型的:Scala 拥有非常先进的静态类型系统,支持类型推断和模式匹配等;
- Scala 可以开发大数据应用程序:例如 Spark、Flink 等
基础语法
语句
- scala可以不使用分号结尾,但是使用多行语句时必须使用分号分隔。
//一行语句独占一行
println("Hello World!")
//多行语句在一行
println("Hello World!"); println("Hello Scala!")
打印输出
- 换行输出
println("Hello World!");println("Hello Scala!")
- 不换行输出
print("Hello World!");print("Hello Scala!")
- 不管是println还是print,都可以同时打印多个值
println("Hello Wor1d!","Hello Scala!")
常量(计算机中不变化的量)
- 在scala中推荐使用常量,不推荐使用变量
- 创建常量的关键字时
val,创建变量的关键字是var。 - 常量的完整语法是
val常量名:常量类型=初始值,但是可以直接写val age = 10scala会自己进行类型推断。 - 常量修改会报错。
变量
- 声明格式
var变量名:变量类型=初始值,也可以自动进行类型推断。
官方推荐使用val:更安全代码可读性更高
资源回收更快,方法执行完,val所定义的常量就会被回收
字符串
- 双引号(与大多数语言一致)
- 三引号
- 插值表达式
语法
三引号
val|var 变量名 = """
字符串1
字符串2
"""
插值表达式
- Scala中可以使用插值表达式来定义字符串,能有效避免大量字符串的拼接。
val|var 变量名 = s"${变量名|表达式}字符串"
惰性赋值
- 当有一些变量保存的数据较大时,且这些数据又不需要马上加载到内存中,就可以使用惰性赋值来提高效率。
// lazy 只支持val不支持var
lazy val 变量名 = 表达武
标识符
实际开发中,我们会编写大量的代码,这些代码中肯定会有变量、常量、方法、类等,那它们该如何命名呢?这就需
要标识符了,标识符就是用来给变量、常量、方法、类等起名字用的。
命名规则
- 支持大小写英文字母,数字,下划线,美元符$;
- 不能数字开头;
- 不能使用关键字;
- 遵循见名知意。
- 支持“撇撇”
//撇撇命名方式如下
val 'worker.heartbeat.interval' = 3
>>excute print('worker.heartbeat.interval')
>>3
命名规范
- 变量和方法:小驼峰,从第二个单词开始,每个单词的首字母大写。例如:maxSize、.selectUserByld。、
- 类和特征(Trait):大驼峰,每个单词的首字母都大写。例如:UserController、,WordCount。
- 包:全部小写,一般是公司或组织的域名反写,多级包之间用·隔开。例如:com.xxxxx.scala。
数据类型
常用类型
| 基础类型 | 类型说明 |
|---|---|
| Byte | 8位带符号整数 |
| Short | 16位带符号整数 |
| Int | 32位带符号整数 |
| Long | 64位带符号整数 |
| Char | 16位无符号Unicode字符 |
| String | Char类型的序列(字符串) |
| Float | 32位单精度浮点数 |
| Double | 64位双精度浮点数 |
| Boolean | 1位,true或false |
- Scala所有类型都使用大写字母开头
- 整形使用Int
- Scala中定义变量可以不写类型,会自动推断类型
- 默认整形为Int,默认浮点为Double
其他类型
| 类型 | 说明 |
|---|---|
| Any | 所有类型的父类,它有两个子类AnyVal与AnyRef。 |
| Anyval | 所有数值类型的父类。 |
| AnyRef | 所有引用类型的父类。 |
| Unit | 表示空,Unit是AnyVal的子类,它只有一个实例(),类似于Java的void。 |
| Null | AnyRef的子类,所有引用类型的子类,它的实例是null,可以将null赋值给任何引用类型。Null并不能 实例化,只是用来处理异常和错误的。 |
| Option | 平时不建议使用Null而是改用Option,在一些方法中最后要返回一个值,但这个返回值有时为空,有时不 为空,就可以将这个返回值类型设置为Option。Option中有Some子类用来封装有值的返回;用None来 处理没有值的返回。Option属于Scala的引l用类型。 |
| Nothing | 所有类型的子类,不能直接创建该类型实例,一般配合异常使用。某个方法抛出异常时,返回的就是 Nothing类型,因为Nothing是所有类的子类,那么它可以赋值为任何类型。 |
类型转换
当Scla程序在进行运算或者赋值操作时,范围小的数据类型会自动转换为范围大的数据类型。而有些时候,我们需
要将范围大的数据类型转换为小的数据类型,这时就需要进行强制转换。值得注意的是,如果是数值类型强转操作,会产
生丢失精度的问题。
自动类型转换
- 范围小的类型可以自动转换为大范围的类型
- 数值类型自动类型转换从小到大为:Byte,Shot,Char,lnt,Long,Float,Double。
//Scala中默认的整型是Int,默认的浮点型是Double
//所以相当于是Int和Double进行计算,根绝自动类型转换,最终结果为Double
val a:Double = 3 + 2.21
//以下代码会报错,因为最终计算结果是Int,最后将其赋值给Byte类型肯定不行
val b:Int = 3
val c:Byte = b + 1
<console>:12:error:type mismatch;
found :Int
required :Byte
var c:Byte = b + 1
强制类型转换
- 大范围的类型转换小范围的类型需要使用强制转换
- 强制转换可能会造成精度丢失
val|var 变量名 = 值.toXxx
例如:
val a:Double = 3.14
val b:Int = a.toInt
print(b)
//精度丢失
>>3
String类型转换
数值转换String语法
val|var 变量名 = 数值类型 + ""
val|var 变量名 = 数值类型.toString
键盘录入
类似于Java的Scanner方法
- 导包:import scala.io.StdIn
- 调用方法:通过StdIn.readXxx()接收用户键盘录入的数据
// 导包
import scala.io.StdIn
// 调用方法
println("请输入用户名")
val username = StdIn.readLine()
println("您的用户名为:" + username)
println("请输入年龄")
val age = StdIn.readInt()
println("您的年龄为:" + age)
运算符
算术运算符
| 运算符 | 功能 |
|---|---|
| + | 正号、加法、字符串拼接符 |
| – | 负号、减法 |
| * | 乘法 |
| / | 除法,除数不能为0 |
| % | 取余 |
赋值运算符
| 运算符 | 功能 |
|---|---|
| 基本赋值运算符 = | 例如:var a = 2,把2赋值给变量a |
| 扩展赋值运算符+=、-=、*=、/=、%= | 例如:a += 3,把变量a加3后再重新赋值回a |
Scala没有 ++ 与 — 操作因为+号是Scala中的一个方法
关系运算符
| 运算符 | 功能 |
|---|---|
| > | 判断左边是否大于右边 |
| >= | 判断左边是否大于或者等于右边 |
| < | 判断左边是否小于右边 |
| <= | 判断左边是否小于或者等于右边 |
| == | 判断左边是否等于右边 |
| != | 判断左边是否不等于右边 |
Scala与java语法有相反的地方
比较数值使用==或者!=,比较引用值使用eq方法
逻辑运算符
| 运算符 | 功能 |
|---|---|
| & | 逻辑与,要求所有条件都满足。简单理解:左右两边,有false 则整体为false |
| | | 逻辑或,要求只要满足任意一个条件即可。简单理解:左右两边,有true 则整体为 true |
| && | 逻辑短路与,要求所有条件都满足。简单理解:如果左边为 false 则右边不执行 |
| || | 逻辑短路或,要求只要满足任意一个条件即可。简单理解:如果左边为true 则右边不执行 |
| ! | 逻辑非,对表达式的结果进行取反操作。简单理解:表达式结果为 true 取反则为 false |
语句块
- Scala使用
{}来表示一个语句块 - 语句块默认将最后一个逻辑行当作返回值
val result = {
println("我是语句块")
1 + 1
}
println("result = " + result)
val result = {
println("我是语句块")
if (true) {
println("我是最后一个逻辑行")
2 + 2
} else {
1 + 1
}
}
println("result = " + result)
//默认最后一个逻辑行为返回值
>>result: Int = 4
流程控制

顺序结构
val a = 2
val b = 3
println(a+b)
选择结构
- 单分支
if (关系表达式) {
// 语句块
}
- 双分支
if (关系表达式) {
语句块1
} else {
语句块2
}
- 多分支
if (关系表达式1) {
// 语句块1
} else if (关系表达式2) {
// 语句块2
} else if (关系表达式n) { // else if 可以有多组
// 语句块n
} else { // 所有关系表达式都不成立的时候,执行这里的代码
// 语句块n+1
}
三元表达式
- 在scala中没有三元表达式
- 可以使用if来代替三元表达式
- 和java一样在
{}中只有一行代码时括号可以省略
// 定义变量,表示年龄
var age = 18
// 定义常量,接收 if 语句的返回值
val result = if (age >= 18) "已成年" else "未成年"
println("age:" + age + "," + result)
嵌套分支
- 和其他语言一样可以在选择分支中再嵌套使用分支。
循环结构
for循环
for (i <- 表达式|数组|集合){
//语句块
}
//打印10次hello world
for (i <- 1 to 10){
println(s"hello world ${i}")
}
>> hello world 1
>> hello world 2
>> hello world 3
>> hello world 4
>> hello world 5
>> hello world 6
>> hello world 7
>> hello world 8
>> hello world 9
>> hello world 10
嵌套循环
// 打印 5 * 5 的方形,方形图案用 # 进行填充
// 方案一:普通写法
for (i <- 1 to 5) {
for (j <- 1 to 5) {
print("#")
}
println()
}
// 方案二:压缩版
for (i <- 1 to 5) {
for (j <- 1 to 5) {
if (j == 5) println("#") else print("#")
}
}
// 方案三:合并版
for (i <- 1 to 5; j <- 1 to 5) {
if (j == 5) println("#") else print("#")
}
守卫
for (i <- 表达式|数组|集合 if 表达式){
//语句块
}
//打印1-10的偶数
for (i <- 1 to 10 if i % 2 == 0){
println(i)
}
yield生成器
- 类似于return
- 但是不会结束函数return会结束函数
- 如果在循环中使用了yield每次循环遇到yield时就会将后面的值放入一个集合
- 结束时返回这个集合
- 使用了yield的表达式叫做推导式
- yield可以用作函数的参数,只要函数支持被迭代
//将1~10的偶数返回
var result = for (i <- 1 to 10 if i % 2 == 0) yield i
println(result)
//生成10,20,...,90,100
var result = for (i <- 1 to 10) yield i * 10
println(result)
while 循环
// 初始化条件
while (条件表达式) {
// 语句块
// 控制条件
}
do while循环
// 初始化条件
do {
// 语句块
// 控制条件
} while (条件表达式)
break和Continue
- Scala中break和continue需要导包实现
//实现break
// 先导包
import scala.util.control.Breaks._
// 当 i == 5 时结束循环
breakable {
for (i <- 1 to 10) {
if (i == 5) break() else println(i)
}
}
//实现continue
// 先导包
import scala.util.control.Breaks._
// 当 i == 5 时跳过当次循环,继续下一次
for (i <- 1 to 10) {
breakable {
if (i == 5) break() else println(i)
}
}
方法
定义方法
def 方法名(参数名:参数类型, 参数名:参数类型, ...): [返回值类型] = {
// 语句块(方法体)
}
- 举例
// 定义方法
def addNum(a:Int, b:Int): Int = {
a + b
}
- 方法参数列表的参数类型不能省略
- 方法返回值类型可以省略,由 Scala 编译器自动推断,但是定义递归方法,不能省略。
- 方法返回值可以不写 return,默认是 {} 块的最后一个逻辑行。
- 在 Scala 中,方法传参默认是 val 类型,即不可变,这意味着你在方法体内部不能改变传入的参数。
惰性方法
- lazy可以用于常量也可以用于方法
- 并不是在方法前面加上lazy
// 定义方法
def addNum(a:Int, b:Int): Int = {
a + b
}
// 惰性加载
lazy val result = addNum(2, 3)
// 首次使用该值时,方法才会执行
println(result)
def addNum(a:Int = 1, b:Int = 1): Int = {
a + b
}
方法参数
- 默认参数
def addNum(a:Int = 1, b:Int = 1): Int = {
a + b
}
val result = addNum()
println(result)
- 带名参数
def addNum(a:Int = 1, b:Int = 1): Int = {
a + b
}
val result = addNum(b = 5)
println(result)
- 变长参数
- 变长参数只能定义到最后一个
def addNum(a:Int*): Int = {
a.sum
}
val result = addNum(1, 2, 3, 4, 5)
println(result)
方法调用
在Scala中 + - * / 都属于方法
- 后缀调用法
//语法
对象.方法名(参数)
//案例
Math.abs(-1)
- 中缀调用法
//语法
对象名 方法名 参数
//案例
//求两数大小
Math max (1.2)
- 花括号调用法
//语法
Math.abs{
//语句块
}
//绝对值
Math.abs{
println("求绝对值")
-1
}
- 无括号调用法
// 定义一个无参数的方法
def hello(): Unit = {
println("Hello World!")
}
// 调用方法
hello
函数
- 函数可以存储在变量中
- 函数可以作为参数
- 函数可以作为返回值
定义函数
// 因为函数是对象,所以函数有类型:(函数参数类型1, 函数参数类型2,...) => 函数返回值类型
val 函数名: (函数参数类型1, 函数参数类型2,...) => 函数返回值类型 = (参数名:参数类型, 参数名:参数类型, ...) => {
函数体
}
函数的本质就是引用类型,相当于 Java 中 new 出来的实例,所以函数是在堆内存中开辟空间;
函数的定义不需要使用 def 关键字,但定义的函数一定要有输入和返回值,没有返回值相当于返回的是 Unit;
函数不建议写 return 关键字,Scala 会使用函数体的最后一行代码作为返回值;
因为函数是对象,所以函数有类型,但函数类型可以省略,Scala 编译期可以自动推断类型。
-
案例
// 定义一个函数,接收两个参数,将它们相加后的结果返回 val addNum: (Int, Int) => Int = (a: Int, b: Int) => { a + b } // 省略函数返回值 val addNum = (a: Int, b: Int) => { a + b } // 调用函数 val result = addNum(2, 3) println(result)
方法与函数的区别
- 方法是隶属于类或者对象的,在运行时,它会被加载到 JVM 的方法区中
- 函数是一个对象,继承自 FunctionN,函数对象有 apply,curried,toString,tupled 这些方法,方法则没有。
方法转函数
//语法
val|var 变量名 = 方法名 _
//案例
// 定义方法
def addNum(a:Int, b:Int): Int = {
a + b
}
// 将方法转换为函数
var a = addNum _
// 调用函数
a(2, 3)
Option
开发中,在返回一些数据时,难免会遇到空指针异常,遇到一次就处理一次相对来讲比较繁琐。而在 Scala 中,我们返回某些数据
时,可以返回一个 Option 类型的对象来封装具体的数据,从而有效的避免空指针异常
语法
- Some(x)表示实际的值
- None表示没有值
//案例
def division(a: Int, b: Int): Option[Int] = {
// 定义一个两个数相除的方法,使用 Option 类型来封装结果
if (b == 0) { // 当除数为零时,打印异常信息
None
} else { // 当除数不为零时,打印相除结果
Some(a / b)
}
}
val a = 10
val b = 0
val result = division(10, 0)
// 配合 Option 的 isEmpty 方法来检测元素是否为 None
println(result.isEmpty)
// 配合 Option 的 getOrElse 方法返回友好提示
println(result.getOrElse("对不起,除数不能为零"))
