Scala(2) 基础语法
2023-08-09 14:53:19 # Big Data # Scala

2. 变量和数据类型

2.1 注释

基本语法

  • 单行注释://

  • 多行注释:/* */

  • 文档注释:/**

    *

    */

注意事项

Scala注释使用和Java完全一样

2.2 变量和常量

基本语法

  • var 变量名 [: 变量类型] = 初始值
  • val 常量名 [: 常量类型] = 初始值

  • 能用常量的地方不用变量

注意事项

  • 声明变量时,类型可以省略,编译器自动推导,即类型推导
  • 类型确定后不可修改
  • 变量声明时,必须要有初始值
  • 在声明/定义变量时,可以使用var或者val来修饰,var修饰的变量可变,val修饰的变量不可变

  • var修饰的对象引用可以改变,val修饰的对象则不可改变,但对象的状态(字段)是可以改变的

    • 类构造方法中的属性加上var/val才能被访问到,能否改变也取决于此
    • 类属性也可以写在字段中,构造方法形如def this(..) {}
    1
    2
    3
    4
    5
    6
    7
    8
    class Student(name: String, var age: Int) { }

    var alice = new Student("alice", 20)
    alice = new Student("Alice", 21)
    alice = null

    val bob = new Student("bob", 23)
    bob.age = 24

2.3 标识符的命名规范

命名规则

Scala中的标识符声明,基本和Java一致,但是细节上会有所变化,有以下三点

  • 以字母或者下划线开头,后接字母、数字、下划线
  • 以操作符开头,且只包含操作符(+-*/#!等)
  • 用反引号``包括的任意字符串,即使是Scala关键字也可以

2.4 字符串输出

基本语法

  • 字符串间用 + 号连接,* 表示复制
  • printf用法:通过 % 传值
  • 字符串模板(插值字符串):通过 $ 获取变量值

案例实操

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
val name: String = "tony"
val age: Int = 18

// 字符串用 + 连接, *可用来复制
println(name * 3 + " " + age)

// printf
printf("name = %s age = %d\n", name, age)

// 字符串, 通过$引用
println(s"name = ${name} age = ${age + 2}")

/* 使用多行字符串时, 用三个双引号包围即可
输入的内容如果因为带有空格、\t导致每一行的开始位置不能对齐
可以应用scala的stripMargin方法, 在scala中的stripMargin默认使用"|"作为连接符
在多行换行的行头前加一个"|"即可
*/
val str =
"""hello
| world
|""".stripMargin
println(str)

image-20220918171337002

2.5 控制台输入

基本语法

  • StdIn.readLine()
  • StdIn.readInt()
  • StdIn.readDouble()

案例实操

1
2
3
4
5
6
7
8
9
10
11
12
println("input name:")
val name = StdIn.readLine()
println("input age:")
val age = StdIn.readInt()
println("input sal:")
val sal = StdIn.readDouble()

println(
s"""name = ${name}
|age = ${age}
|sal = ${sal}
|""".stripMargin)

image-20220918172711571

2.6 读写文件

基本语法

  • Source.fromFile("...")
  • new PrintWriter(new File("..."))

案例实操

1
2
3
4
5
6
7
8
9
// 从文件读取
val source = Source.fromFile("src/main/resources/test.txt")
source.foreach(print)
source.close()

// 将数据写入文件
val writer = new PrintWriter(new File("src/main/resources/output.txt"))
writer.write("hello scala from java writer")
writer.close()

2.7 数据类型

  • Scala 中一切数据都是对象,都是 Any 的子类
  • Scala 中数据类型分为两大类:数值类型(AnyVal),引用类型(AnyRef),不管是值类型还是引用类型都是对象
  • Scala 数据类型仍然遵守,低精度的值类型向高精度的值类型,自动转换(隐式转换),图中虚线
  • Scala 中的 StringOps 是对 Java 的 String 的增强
  • Unit:对应 Java 中的 void,用于方法返回值的位置,表示方法没有返回值。Unit 是一个数据类型,只有一个对象就是()。Void 不是数据类型,只是一个关键词
  • Null 是一个类型,只有一个对象就是 null。它是所有引用类型(AnyRef)的子类
  • Nothing,是所有数据类型的子类,主要用在一个函数没有明确返回值时使用,因为这样我们可以把抛出的返回值,返回给任何的变量或者函数
  • Long 需要在数字后加 L,同理,Float 类型需要加 F

20191220112456704

2.8 Unit、Null、Nothing

基本说明

数据类型 描述
Unit 表示无值,和其他语言中void等同。用作不返回任何结果的方法的结果类型。Unit只有一个实例值,写成()。
Null null, Null类型只有一个实例值null
Nothing Nothing类型在Scala的类层级最低端;它是任何其他类型的子类型。当一个函数,我们确定没有正常的返回值,可以用Nothing来指定返回类型,这样有一个好处,就是我们可以把返回的值(异常)赋给其它的函数或者变量(兼容性)

案例实操

  1. Unit 类型用来标识过程,也就是没有明确返回值的函数。

    Unit 类似于 Java 里的 void。Unit只有一个实例————(),这个实例也没有实质意义

    1
    2
    3
    4
    5
    6
    def main(args: Array[String]): Unit = {
    def sayOk : Unit = {}
    println(sayOk)
    }

    //输出()
  2. Null 类只有一个实例对象,Null 类似于 Java 中的 null 引用。Null 可以赋值给任意引用类型(AnyRef),但是不能赋值给值类型(AnyVal)

    1
    2
    3
    4
    5
    var cat = new Cat()
    cat = null // 正确

    var n1: Int = null // 错误
    println("n1: " + n1)

    image-20220918201415881

  3. Nothing,可以作为没有正常返回值的方法的返回类型,非常直观的告诉你这个方法不会正常返回,而且由于 Nothing 是其他任意类型的子类,他还能跟要求返回值的方法兼容。

    1
    2
    3
    4
    5
    6
    def main(args: Array[String]): Unit = {
    def test(): Nothing = {
    throw new Exception()
    }
    test()
    }

2.9 类型转换

2.9.1 数值类型自动转换

image-20220918202357330

基本说明

  • 自动提升原则:有多种类型的数据混合运算时,系统首先自动将所有数据转换成精度大的那种数据类型,然后再进行计算

  • 把精度大的数值类型赋值给精度小的数值类型时,就会报错,反之就会进行自动类型转换。

  • Byte,Short和Char不会相互自动转化

  • Byte,Short和Char三者可以计算,计算时首先转换为Int类型

    image-20220918202902035

    扩展

    1
    2
    3
    4
    5
    6
    7
    def main(args: Array[String]): Unit = {
    // 00000000 00000000 00000000 10000010
    var n: Int = 130
    // 10000010
    var b: Byte = n.toByte
    println(b) // -126
    }

2.9.2 强制类型转换

基本说明

自动类型转换的逆过程,将精度大的数值类型转换为精度小的数值类型。使用时要加上强制转函数,但可能造成精度降低或溢出,格外要注意。

var num : Int = 2.7.toInt

注意事项

  • 将数据由高精度转换为低精度,就需要使用到强制转换
  • 强转符号只针对于最近的操作数有效,往往会使用小括号提升优先级

2.9.3 数值类型和String类型间转换

基本语法

  • 基本类型转String类型: 将基本类型的值+""即可
  • String类型转基本数值类型: s.toInt, s.toFloat, s.toDouble

3. 运算符

大部分与Java相同,仅列举不同之处

3.1 关系(比较)运算符

  • 在 Scala 中,== 是类中定义的方法,其底层调用时,调用的是此类的 equals 方法
  • String 的 equals 方法比较的是两个对象的具体的值。

  • eq 方法比较的是两个对象的地址,其底层调用的是 Java 中的 == 比较运算符。

1
2
3
4
5
6
7
8
def main(args: Array[String]): Unit = {
val s1 = "abc"
val s2 = new String("abc")

println(s1 == s2) // true
println(s1.equals(s2)) // true
println(s1.eq(s2)) // false
}

3.2 赋值运算符

Scala 中没有 ++, — 操作符,可以通过 +=, -= 实现

3.3 三元运算符

Scala 中没有三元运算符 ?:,可以用if-else实现 val res = if (...) x else y

3.4 Scala运算符本质

Scala中其实是没有运算符的,所有的运算符都是方法,任何具有单个参数的方法都可以用作中缀运算符

  • 当调用对象的方法时,点.可以省略
  • 如果函数参数只有一个,或者没有参数,()可以省略
1
2
3
4
5
6
7
8
def main(args: Array[String]): Unit = {
// 标准的加法运算
val i: Int = 1.+(1)
// 当调用对象的方法时, 点.可以省略
val j: Int = 1 + (1)
// 如果函数参数只有一个, 或者没有参数, ()可以省略
val k: Int = 1 + 1
}

4. 流程控制

4.1 if-else

大部分与Java相同

不同点

Scala中if-else表达式其实是有返回值的,具体返回值取决于满足条件的代码体的最后一行

1
2
3
4
5
6
7
8
9
10
11
12
13
def main(args: Array[String]): Unit = {
println("input age:")
val age = StdIn.readInt()
// 如果返回值类型不一致,取他们共同的祖先类型
val res: Any = if (age < 18) {
"童年"
} else if (age >= 18 && age < 30) {
"中年"
} else {
100
}
println(res)
}

4.2 switch

Scala中没有Switch,而是使用模式匹配处理

4.3 for

4.3.1 范围数据循环(to)

基本语法

1
2
3
for (i <- 1 to 3) {
println(i)
}
  • i 表示循环的变量,<- 表示为 i 赋值
  • i 从 1-3 循环,前后闭合

4.3.2 范围数据循环(until)

基本语法

1
2
3
for (i <- 1 until 3) {
println(i)
}
  • i 从 1-2 循环,前闭后开

4.3.3 循环守卫

基本语法

1
2
3
for (i <- 1 to 3 if i != 2) {
println(i)
}
  • 循环守卫,即循环保护式(条件判断式、守卫)。保护式为true才会进入循环体内部

4.3.4 循环步长(by)

基本语法

1
2
3
4
for (i <- 1 to 10 by 3) {
println(i)
}
// 1 4 7 10

4.3.5 嵌套循环

基本语法

1
2
3
4
5
6
7
8
9
10
for (i <- 1 to 3; j <- 1 to 3) {
println("i = " + i + " j = " + j)
}

// 等价于
for (i <- 1 to 3) {
for (j <- 1 to 3) {
println("i = " + i + " j = " + j)
}
}

4.3.6 引入变量

基本语法

1
2
3
4
5
6
7
8
9
for (i <- 1 to 3; j = 4 - i) {
println("i = " + i + " j = " + j)
}

// 等价于
for (i <- 1 to 3) {
var j = 4 - i
println("i = " + i + " j = " + j)
}
  • for推导式一行中由多个表达式时,要加 ; 隔断逻辑

  • for推导式有一个约定,当仅包含单一表达式时使用圆括号,否则一行一个表达式,并用花括号替代

    1
    2
    3
    4
    5
    6
    for {
    i <- 1 to 3
    j = 4 - i
    } {
    println("i = " + i + " j = " + j)
    }

4.3.7 循环返回值

基本语法

1
2
3
4
5
val res = for (i <- 1 to 10) yield i
println(res)

// 打印
// Vector(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
  • 将遍历过程中处理的结果返回到一个新的Vector集合,使用yield关键字
  • 开发中很少使用

案例实操

1
2
3
4
5
6
val res = for (i <- 1 to 10) yield {
i * 2
}
println(res)

// Vector(2, 4, 6, 8, 10, 12, 14, 16, 18, 20)

4.3.8 倒叙打印

基本语法

1
2
3
for (i <- 1 to 10 reverse) {
println(i)
}

4.4 while、do..while

和Java相同

  • 与for不同,while没有返回值,即while语句的结果是Unit类型()

4.5 循环中断

基本说明

Scala内置控制结构去掉了break和continue,是为了更好地适应函数式编程,推荐使用函数式风格解决break和continue的问题。

Scala中使用breakable控制结构来实现break和continue功能

案例实操

  • 异常方式退出循环

    1
    2
    3
    4
    5
    6
    7
    8
    try {
    for (elem <- 1 to 10) {
    println(elem)
    if (elem == 5) throw new RuntimeException
    }
    } catch {
    case e: RuntimeException => e.printStackTrace()
    }
  • 采用Scala自带函数退出循环

    1
    2
    3
    4
    5
    6
    Breaks.breakable {
    for (elem <- 1 to 10) {
    println(elem)
    if (elem == 5) Breaks.break()
    }
    }
  • 对break进行省略

    1
    2
    3
    4
    5
    6
    breakable {
    for (elem <- 1 to 10) {
    println(elem)
    if (elem == 5) break
    }
    }