Scala
简介
概述
-
Scala是Martin Ordersky(马丁.奥德斯科/基)于2001年开始设计的一门支持面向对象(Object-oriented)和函数式编程(Functional Programming)的多范式语言
-
Scala的官网:www.scala-lang.org
-
Scala目前为止有3个大版本:Scala1.X~3.X。目前Scala1.X已经停止使用,Scala3.x兼容Scala2.x的所有特性,但是Scala3.x从2021年才推出的版本,也因此,目前大数据中的主流框架Spark、Flink等基本上是基于Scala2.x实现的,所以上课阶段使用Scala2.x
-
Scala语言即支持脚本方式来运行,也支持编译之后执行。Scala也是通过JVM来编译的,所以Scala代码编译完成之后依然产生的是
.class
文件,也因此Scala定义的逻辑可以在Java中执行,同样,Java中所有的类都可以被Scala调用 -
安装Scala之前,需要先安装JDK1.8
特点
-
Scala是一门多范式编程语言,支持面向对象和函数式编程两种方式。目前市面上的编程语言可以分为四类:面向对象、面向过程、函数式、泛型式
-
Scala是一门静态类型(强类型)的编程语言,在编译的时候就需要确定数据类型,定义之后类型不能更改
-
Scala是一门基于JVM的编程语言,所以和Java是互通的
-
相对于Java代码而言,Scala会更加的优雅和简洁
基本语法
注意问题
-
在Scala中,认为换行就是新的一行语句,所以省略分号不写
-
主函数必须定义到伴生对象object中
-
Scala的注释方式和Java的注释方式是一模一样的,支持单行注释、多行注释和文档注释
-
在Scala中,一共有39个关键字,其中大部分关键字是Java是重合的,例如
package
,class
,for
等,但是Scala同时提供了自有的关键字,例如object
,def
,yield
,var
,val
,implicit
等
标识符命名规则
-
由字母、数字和下划线组成
-
数字不能开头
-
不能使用关键字
-
严格区分大小写
-
可以使用符号作为标识符,但是如果使用符号作为标识符,那么整个标识符必须全部使用符号
-
可以使用反引号来将任意字符串标记为标识符
变量和常量
-
变量的定义规则
var 变量名:数据类型 = 数据
-
常量的定义规则
val 常量名:数据类型 = 数据
-
在Scala中,推荐使用常量
-
因为Scala是一门强类型语言,所以变量/常量在赋值之后,数据类型就不能发生变化,因此Scala中,可以根据值自动推导数据类型,所以定义变量/常量的时候,数据类型可以省略
-
除非值的类型和声明类型不完全一致,此时才需要显式声明
-
不同于Java的地方在于,不支持变量先声明后赋值
控制台输入输出
类型转换
运算符
流程控制
判断结构
循环结构
while循环
for循环
循环守卫式
循环的嵌套
变量引入
循环的返回值
倒叙遍历
package com.fesco.loop import scala.language.postfixOps object LoopDemo4 { def main(args: Array[String]): Unit = { val arr = Array(5, 1, 4, 7, 8) val maxIndex = arr.length - 1 // 正序遍历 for (i <- 0 to maxIndex) println(arr(i)) println("=" * 50) // 倒序遍历 // 方式一 for (i <- maxIndex to(0, -1)) println(arr(i)) println("=" * 50) // 方式二 for (i <- maxIndex to 0 by -1) println(arr(i)) println("=" * 50) // 方式三 for (i <- 0 to maxIndex reverse) println(arr(i)) } }
循环控制
-
在Scala中,推荐使用
scala.io.StdIn
来从控制台获取数据 -
在Scala中,可以使用
print
或者println
来进行输出。println
是输出并换行 -
如果需要输出多个数据,可以使用
+
来进行字符串的拼接println("name:" + name + ", age:" + age + ", gender:" + gender)
-
Scala中还提供了占位符的方式来输出数据
printf("name:%s, age:%d, gender:%s\n", name, age, gender)
-
Scala中还提供了数据的引用形式
println(s"name:$name, age:$age, gender:$gender")
-
如果需要保留小数位(精确),那么此时需要以f作为开头
println(f"total:${price * weight}%3.2f")
-
字符串中支持转义字符
// 字符串中支持转义字符 println("hello\tworld") // 原样输出字符串 println(raw"hello\tworld")
-
输出多行组成的字符串
val sql = """ |select name, | sum(score) |from students |group by name; """.stripMargin println(sql)
Scala
基础语法
数据类型
概述
-
在Java中,基本类型、static等都不符合面向对象的规则。Scala作为一门完全面向对象的语言,不支持基本类型、static等
-
在Scala中,提供了公共父类:
Any
,即所有的类都是Any
的子类-
Any下分为了两个大的子类:
AnyVal
(任意数值)和AnyRef
(任意引用)。无论AnyVal
还是AnyRef
都是类 -
AnyVal
包含了常用的数据类型-
例如:
Byte
,Short
,Int
,Long
,Float
,Double
,Char
,Boolean
-
Unit
:对应了Java中的void。当一个函数没有返回值的时候,返回值类型就是Unit
。Unit
只包含了一个对象,就是()
-
StringOps
:字符串,是对Java中的String
类做了增强
-
-
除了
AnyVal
以外的其他的类都是AnyRef
的子类-
AnyRef
包含Scala中的类和Java中的类 -
AnyRef
中提供了特殊子类:Null
。Null
是任意AnyRef
类中的子类。Null
只有一个值就是null
-
-
-
不同于Java的地方在于,Scala中还提供了公共子类:
Nothing
,即所有的类都是Nothing
的父类 -
在Scala中,整数默认为
Int
,小数默认为Double
-
和Java中,在Scala中,数据类型转换也分为隐式(自动)和显式(强制)
-
所有的强制类型转换需要通过
toxxx
函数来转换// 显式转换 val a = 5L val c: Int = a.toInt println(c) val x = 5.38 val y = x.toFloat println(y)
-
Scala中提供了五大类运算符:算术、赋值、比较、逻辑、位
-
需要注意的是,Scala中没有提供三元运算符(
?:
) -
Scala中没有自增(
++
)和自减(--
)运算 -
Scala中还不支持连等定义和连等赋值
var x = 5 // 错误写法 x += x -= 3
-
在Scala中,没有基本类型的说法,所有的数据都是对象,因此所有的运算符本质上都是函数
val x = 3 val y = 5 // 所有的运算符本质上都是函数 val r1 = x.+(y) println(r1) // 调用函数的时候,.可以省略不屑 val r2 = x + (y) println(r2) // 当调用函数中只有一个参数的时候,可以省略()不屑 val r3 = x + y println(r3)
-
同Java一样,Scala中提供了
if
,if-else
和if-else if
结构,语法和Java也是一样的 -
案例
package com.fesco.ifx import scala.io.StdIn object IfDemo { def main(args: Array[String]): Unit = { // 输入一个数字,判断是奇数还是偶数 // 获取数字 val n = StdIn.readInt() // 判断是奇数还是偶数 if (n % 2 == 1) println(s"${n}是一个奇数") else println(s"${n}是一个偶数") } }
-
案例
package com.fesco.ifx import scala.io.StdIn object IfElseIfDemo { def main(args: Array[String]): Unit = { // 案例:输入数字表示用电费,来计算电费 // 2820 -> 0.54 // 4800 -> 0.69 // >4800 -> 0.89 // 用电量 val n = StdIn.readInt() // 计算一共需要缴费多少 if (n <= 2820) println(s"${n * 0.54}") else if (n <= 4800) print(s"${2820 * 0.54 + (n - 2820) * 0.69}") else println(s"${2820 * 0.54 + 1980 * 0.69 + (n - 4800) * 0.89}") } }
-
和Java中不太一样的地方在于,Scala中任何结构都应该有计算结果,即任何结构都应该由返回值 - 因此,判断结构也应该有返回值,返回值默认就是代码块的最后一行
package com.fesco.ifx import scala.io.StdIn object IfDemo2 { def main(args: Array[String]): Unit = { // 案例:输入两个数,输出较大的那个数字 val a = StdIn.readInt() val b = StdIn.readInt() val max = if (a > b) a else b println(max) } }
-
Scala中同样提供了while结构
while(条件){ 代码块 }
-
在Scala中任何结构都应该有计算结果,因此while结构也应该有计算结果,结果类型固定是
Unit
-
案例
package com.fesco.loop object WhileDemo { def main(args: Array[String]): Unit = { // 记录和 var sum = 0 // 变量 var i = 1 val r: Unit = while (i <= 100) { sum += i i += 1 } println(sum) println(r) } }
-
Scala中提供了for循环,但是Scala中的for循环和Java中不同,提供了
for-to
,for-until
和循环守卫式/判断式/条件式 -
for-to语法
for(变量 <- 起始值.to(结束值)){ 代码块 } // 或者 for(变量 <- 起始值 to 结束值){ 代码块 } // x <- a to b,表示a≤x≤b
指定步长
for(变量 <- 起始值.to(结束值, 步长)){ 代码块 } // 或者 for(变量 <- 起始值.to(结束值).by(步长)){ 代码块 }
-
案例
package com.fesco.loop object ForDemo1 { def main(args: Array[String]): Unit = { // 打印1-10 for (n <- 1.to(10)) println(n) // .可以省略,如果只有1个参数,()也可以省略 for (n <- 1 to 10) println(n) // 打印10以内的偶数 for (n <- 0.to(10, 2)) println(n) for (n <- 0 to(10, 2)) println(n) for (n <- 0.to(10).by(2)) println(n) for (n <- 0 to 10 by 2) println(n) } }
-
案例
package com.fesco.loop import scala.io.StdIn object ForDemo2 { def main(args: Array[String]): Unit = { // 案例一:输出m行n列的*组成的矩形 val m = StdIn.readInt() val n = StdIn.readInt() for (i <- 1 to m) println("*" * n) println("=" * 50) // 案例二:输出m行*组成的三角形 for (i <- 1 to m) println("*" * i) println("=" * 50) // 案例三:输出m行*组成的三角形 for (i <- 1 to m) { // 打印空格 print(" " * (m - i)) // 打印* println("*" * (2 * i - 1)) } } }
-
for-until结构和for-to结构一样,区别就在于
a to b表示a≤x≤b a until b表示a≤x<b
-
循环守卫式又称之为循环条件式或者循环判断式
-
当在循环中直接嵌套了一个判断结构的时候,那么此时可以将判断条件紧跟循环来执行,当满足判断条件的时候,才能进入循环执行,构成了循环守卫式
package com.fesco.loop object ForDemo3 { def main(args: Array[String]): Unit = { // 打印:100以内是5的倍数,但不是7的倍数的数字 for (i <- 0 until 100 by 5) { if (i % 7 != 0) { println(i) } } // 在循环中直接嵌套了一个if判断 // 实际上需要满足条件,才会继续执行 // 循环守卫式 println("*" * 50) // 将条件紧跟循环之后,只有满足条件才会进入循环 // 如果不满足条件,那么就继续递增直到结束 for (i <- 0 until 100 by 5 if i % 7 != 0) println(i) // 或者 for (i <- 0 until(100, 5) if i % 7 != 0) println(i) } }
-
在Scala中,支持循环的嵌套
package com.fesco.loop object LoopDemo { def main(args: Array[String]): Unit = { // 打印九九乘法表 for (i <- 1 to 9) { // 控制列 for (j <- 1 to i) { print(s"$j*$i=${i * j}\t") } // 换行 println() } } }
-
特殊形式
package com.fesco.loop object LoopDemo2 { def main(args: Array[String]): Unit = { for (i <- 1 to 5) { for (j <- 1 to 3) { print(s"$i, $j\t") } } println() // 方式一:将内外循环合并成1个 for (i <- 1 to 5; j <- 1 to 3) { print(s"$i, $j\t") } println() // 在Scala中,尽量的避免使用分号,推荐换行来表示不同的语句 for { i <- 1 to 5 j <- 1 to 3 } { print(s"$i, $j\t") } } }
-
类似于循环守卫式和循环嵌套,for循环还支持变量的引入
-
案例
package com.fesco.loop import scala.io.StdIn object LoopDemo3 { def main(args: Array[String]): Unit = { // 输入一个数字n,输出这个数字拆分的求和形式 // 例如输入数字8 // 拆分成1+7 2+6 3+5 4+4 // 获取数字 val n = StdIn.readInt() // 拆分 for (i <- 1 to (n / 2)) { val j = n - i println(s"$n=$i+$j") } println("*" * 50) // 在循环中直接嵌套了一个变量j,然后后续操作变量j for (i <- 1 to (n / 2); j = n - i) println(s"$n=$i+$j") println("*" * 50) // 在Scala中,推荐换行来写新语句 for { i <- 1 to (n / 2) j = n - i } { println(s"$n=$i+$j") } } }
-
在Scala中所有的结构都应该有计算结果,因此循环也必须有计算结果。其中while和do-while的结果类型只能是Unit,而for循环默认的结果类型也是Unit,但是for循环可以通过
yield
关键字将计算结果放入一个Vector
中返回 -
案例
package com.fesco.loop import java.util import scala.io.StdIn object YieldDemo { def main(args: Array[String]): Unit = { // 案例:输入一个数字n,获取1~n的平方形式 // 例如输入数字4,获取1,4,9,16 val n = StdIn.readInt() // 方式一 val v = new util.ArrayList[Int]() for (i <- 1 to n) { // v.add(i * i) v add i * i } println(v) println("=" * 50) // 方式二 val square = for (i <- 1 to n) yield i * i println(square) } }
-
在Scala中,没有
break
和continue
关键字 -
对于continue而言,Scala就认为可以通过条件判断来实现相同的效果,因此没有必要添加
continue
关键字来增加学习成本 -
中止循环
-
通过抛出异常的方式来中止循环
package com.fesco.loop import scala.io.StdIn object BreakDemo { def main(args: Array[String]): Unit = { val n = StdIn.readInt(); for (i <- 1 to n) { if (i % 4 == 0) throw new RuntimeException() println(i) } } }
这种方式确实可以中止循环,但是同时也把程序给结束掉了
-
抛出异常,结合try-catch来捕获异常
package com.fesco.loop import scala.io.StdIn object BreakDemo2 { def main(args: Array[String]): Unit = { val n = StdIn.readInt() try { for (i <- 1 to n) { if (i % 5 == 0) throw new RuntimeException() println(i) } } catch { case e: RuntimeException => // 捕获到异常之后不做任何处理 } println("testing") } }
-
抛异常这种方式,确实可以结束循环,但是代码的可读性不高,因此Scala中提供了
Breaks
类,Breaks
类中提供了break
函数来结束循环package com.fesco.loop import scala.io.StdIn import scala.util.control.Breaks object BreakDemo3 { def main(args: Array[String]): Unit = { val n = StdIn.readInt() for (i <- 1 to n) { if (i % 5 == 0) { // 本质上还是通过抛出异常的方式来中止循环 Breaks.break() } println(i) } println("testing") } }
为了让代码结构变得比较优雅,在导包的时候可以导入Breaks的所有函数
package com.fesco.loop import scala.io.StdIn // 在Scala中,导包的时候,_表示通配符 import scala.util.control.Breaks._ object BreakDemo4 { def main(args: Array[String]): Unit = { val n = StdIn.readInt() for (i <- 1 to n) { // if (i % 6 == 0) break() // 调用了break函数,并且函数没有参数,所以()可以省略 if (i % 6 == 0) break println(i) } println("testing") } }
案例:数字炸弹
package com.fesco.loop import scala.io.StdIn object LoopCaseDemo { def main(args: Array[String]): Unit = { // 产生0-100之间的随机数,然后进行猜测 // 提示猜测范围,如果猜测准确,则爆炸! val n = (Math.random() * 101).toInt // 定义变量来记录范围 var min = 0 var max = 100 // 提示用户输入 println(s"请输入 $min~$max 范围内的数字:") var x = StdIn.readInt() // 判断值是否相等 while (x != n) { // 判断输入的x是否在指定范围内 if (x < min || x > max) println("输入的数字不在范围内!,请重新输入") else if (x > n) max = x else min = x // 重新获取数据 println(s"请输入 $min~$max 范围内的数字:") x = StdIn.readInt() } // 只要循环结束,就表示猜中了 println("boom!!!") } }
-
Scala也支持do-while结构,语法和Java一样
do { 代码块 } while (条件)
-