Scala 数据类型与变量

2,436 阅读5分钟

Scala 强制要求变量在声明时赋值。

var Pi : Double = 3.1415

Scala 的基本数据类型和 Java 一样分为八种:ShortByteIntLongFloatDoubleBooleanChar

Scala 支持类型推断,对于未声明类型的变量,它的实际类型取决于第一次字面量赋值。比如,下面的变量 a 会被编译器翻译成 String 类型。

var a = "Hello Scala"

之后,变量 a 的数据类型将 保持不变 ,这符合 Scala 作为强类型语言的特性。在未声明类型的情况下,无浮点数值默认为 Int 类型,所有的浮点数默认是 Double 类型。可以通过补充后缀来显式地表明数据类型:

var v1 = 3.2f // Float
var v2 = 3.2  // Double
var v3 = 3    // Int
var v4 = 3l   // long

无法推断类型的变量会被设置为 Any 类型。比如:

// Any
val a = if (Random.nextInt() % 2 == 0) {1} else {"hello"}

Scala 使用 var 表示一个 可变变量 ( variable ),使用 val 来表示一个 不变值 ( value ) 。只有 var 变量可以改变引用。不可变的值意味着它是线程安全的,不需要锁机制来保持同步。因为这个优点,在后续的函数式 / 并发编程中,优先使用值拷贝来传递状态,避免修改变量来改变状态

var obj1 = new Object()
obj1 = new Object()   // ok

val obj2 = new Object()
obj2 = new Object()  // error

特别地,对于 val 类型的值,Scala 支持将其延迟加载。举一个例子:

val t1 = System.currentTimeMillis()
val expensive = {Thread.sleep(1000);"hello"}
val duration = System.currentTimeMillis() - t1
println(s"duration: ${duration}ms")

由于 expensive 的赋值块内部包含了一条休眠命令,因此程序对其进行初始化时需要停顿将近 1 秒的时间,即便 expensive 在后面的程序中并没有用到。这种情况下,对 expensive 进行初始化是一种资源的浪费。在实际场合,如果不确定一个变量是否会被使用,且初始化代价又十分高昂时,那么不妨使用 lazy 关键字令它被延迟加载。

// 它现在只会在第一次被调用时加载。
lazy val expensive = {Thread.sleep(1000);"hello"}

不过,为了表述方便,下文 varval 统称为变量。

数据类型

Scala 的数据类型分为 AnyVal ( 值类型 ) 以及 AnyRef ( 引用类型 ) 。一切 AnyVal 都是对象,包括 IntDoubleFloat 等,因此 Scala 的数值天生就携带各种 toXXX 方法用于转换类型。下图为 Scala 数据类型族谱:

image-20200416180544163.png

Any 是所有数据类型的顶类型,Nothing 则是所有类型的底类型。Null 类型有且只有一个值:null。它是所有 AnyRef 类的底类型,这样设计的目的是可以将 null 赋值给任何一个 AnyRef 类型。

Unit 被归为 AnyVal 的子类型。它用于表示函数没有返回值,比如主程序的返回值就是 Unit 类型。它有且只有一个值,为 ()

浮点型数据有多种表示方法:

  1. 十进制表示方式:5.125.12f.512 ( 表示 0.512 )

  2. 科学计数法方式:5.12e2 ( 5.12 × 102 ),5.12e-2 ( 5.12 × 10-2 )

下表给出了不同数据类型的值域:

type描述
Byte8 位 ( 1 字节 ) 有符号补码整数。区间 -128 ~ 127。
Short16 位 ( 2 字节 ) 有符号补码整数。区间 -(2^16) ~ (2^16)-1
Int32 位 ( 4 字节 ) 有符号补码整数。区间 -(2^32) ~ (2^32)-1
Long64 位 ( 8 字节 ) 有符号补码整数。区间 -(2^64) ~ (2^64)-1
Float32 位,IEEE 754 标准的单精度浮点数。
Double64 位,IEEE 754 标准的单精度浮点数。
Char16 位无符号的 Unicode 字符,区间值为 U+0000 ~ U+FFFF。
String字符序列。
Boolean只有两个值:truefalse
Unit只有唯一值 (),常用于表示方法 / 函数无返回值,相当于 Java 的 void
Null只有唯一值 null,表示空的对象。
Nothing是所有 Scala 类型的底类型,常用于抛出异常。
Any是所有 Scala 类型的顶类型。
AnyRef是 Scala 一切引用类型的父类型。

值转换

低精度的值可以赋值给高精度类型的变量或值,该过程是自动转换。反之则需要牺牲精度,该过程是强制转换。

在多种类型的数据混合计算时,系统的计算结果取 精度最大的数据类型。比如,IntFloat 的数值运算是 Float 类型,IntLong 的数值计算是 Long 类型。

val a : Int = 100
val b : Long = 200l
val c = a + b  // c : Long

低精度的变量不能接受高精度的值。

val a : Float = 10.d  // error

CharInt 两个数据类型之间可以自动转换:

  1. 为一个 Char 类型变量 / 值赋予一个 Int 值时,结果是 Unicode 编码中对应该数值编号的字符;
  2. 为一个 Int 类型变量赋予一个 Char 值时,结果是字符在 Unicode 编码中的数值编号。

在计算 'a' + 1 时,程序首先会将 Char 类型的 'a' 转换为 Int 数值,因此计算结果也是 Int 类型。注意,表达式不会执行自动转换。比如下方的代码:

val a: Char = 66	  // ok
val b: Char = 65 + 1  // error 

可以在计算的结果上通过 toChar 方法显式转换。

val char1 : Char = (65 + 1).toChar

ByteShort 这两种数据类型不会自动转换到 Char,需要使用toChar 方法进行强制转换。

val bt : Byte = 123
val ch : Char = bt.toChar

ByteShortChar 之间相互计算的结果是 Int 类型。

val a : Byte = 20   // -128 ~ 127
val b : Char = 'a'  // 97
val c : Short = 200 // -2^16 ~ 2^16-1
val d : Int = a + b + c
println(d)

低精度类型的变量接受高精度值时需要以牺牲精度的代价进行 强制转换。比如:Float 是比 Int 精度更高的类型,因此将 Float 值赋给 Int 变量时必须进行强制转换。

val a : Int = 1.4f.toInt

一切 AnyVal 类型都可以调用 toString 方法转换为 String 类型;String 类型也可以通过 toXXX 方法转换成值类型。如:

val a : Int = "100".toInt         // ok
val b : String = 100.toString     // ok
val c : Double = "hello".toDouble // error