基本数据类型及其操作
in Scala with 0 comment

基本数据类型及其操作

in Scala with 0 comment

介绍

内容

在本节实验中,将会讲解 Scala 支持的基本数据类型。

实验知识点

环境

适合人群

本课程难度为一般,属于初级级别课程,适合零基础或具有 Java 编程基础的用户。

开发准备

为了使用交互式 Scala 解释器,你可以在打开的终端中输入命令:

su -l hadoop #密码为 hadoop
scala

当出现 scala> 开始的命令行提示符时,就说明你已经成功进入解释器了。如下图所示。

image-1655705257729

实验步骤

基本数据类型简介

如果你是个 Java 程序员,你会发现 Java 支持的基本数据类型,Scala 都有对应的支持,不过 Scala 的数据类型都是对象(比如整数),这些基本类型都可以通过隐式自动转换的形式支持比 Java 基本数据类型更多的方法。

隐式自动转换的概念将在后面介绍,简单的说就是可以为基本类型提供扩展,比如调用 (-1).abs(),Scala 发现基本类型 Int 没有提供 abs() 方法,但可以发现系统提供了从 Int 类型转换为 RichInt 的隐式自动转换,而 RichInt 具有 abs 方法,那么 Scala 就自动将 1 转换为 RichInt 类型,然后调用 RichIntabs 方法。

Scala 的基本数据类型有:ByteShortIntLongChar,这些称为整数类型。整数类型加上 FloatDouble 称为数值类型。此外还有 String 类型,除 String 类型在 java.lang 包中定义,其它的类型都定义在包 scala 中。比如 Int 的全名为 scala.Int。实际上 Scala 运行环境自动会载入包 scalajava.lang 中定义的数据类型,你可以直接使用 IntShortString 而无需再引入包或是使用全称。

下面的例子给出了这些基本数据类型的字面量用法,由于 Scala 支持数据类型推断,你在定义变量时多数可以不指明数据类型,而是由 Scala 运行环境自动给出变量的数据类型:

Welcome to Scala 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_171).
Type in expressions for evaluation. Or try :help.

scala> var hex = 0x5
hex: Int = 5

scala> var hex2 = 0x00ff
hex2: Int = 255

scala> val prog = 0xcafebabel
prog: Long = 3405691582

scala> val littler:Byte = 38
littler: Byte = 38

scala> val big = 1.23232
big: Double = 1.23232

scala> val a = 'A'
a: Char = A

scala> val f ='\u0041'
f: Char = A

scala> val hello = "hello"
hello: String = hello

scala> val longString = """ Welcome to Ultamix 3000. Type "Help" for help."""
longString: String = " Welcome to Ultamix 3000. Type "Help" for help."

scala> val bool = true
bool: Boolean = true

scala>

Scala 的基本数据类型的字面量也支持方法。这点和 Java 不同,Scala 中所有的数值字面量也是对象,比如:

scala> (-2.7).abs
res3: Double = 2.7

scala> -2.7 abs
warning: there were 1 feature warning(s); re-run with -feature for details
res4: Double = 2.7

scala> 0 max 5
res5: Int = 5

scala> 4 to 6
res6: scala.collection.immutable.Range.Inclusive = Range(4, 5, 6)

这些方法其实是对于数据类型的 Rich 类型的方法,Rich 类型将在后面再做详细介绍。

操作基本数据类型

Scala 提供了丰富的运算符用来操作前面介绍的基本数据类型。前面说过,这些运算符(操作符)实际为普通类方法的简化(或者称为美化)表示。比如 1+2,实际为 (1).+(2),也就是调用 Int 类型的 + 方法。

例如:

scala> val sumMore = (1).+(2)
sumMore: Int = 3

实际上类 Int 定义了多个 + 方法的重载方法(以支持不同的数据类型),比如和 Long 类型相加。

+ 符号是一个运算符,并且是一个中缀运算符。在 Scala 中你可以定义任何方法为一操作符。比如 StringIndexOf 方法也可以使用操作符的语法来书写。例如:

scala> val s = "Hello, World"
s: String = Hello, World

scala> s indexOf 'o'
res0: Int = 4

由此可以看出,运算符在 Scala 中并不是什么特殊的语法,任何 Scala 方法都可以作为操作符来使用。是否是操作符取决于你如何使用这个方法,当你使用 s.indexOf('o') 时,indexOf 不是一个运算符。而你写成 s indexOf 'o'indexOf 就是一个操作符,因为你使用了操作符的语法。

除了类似 + 的中缀运算符(操作符在两个操作符之间),还可以有前缀运算符和后缀运算符。顾名思义,前缀运算符的操作符在操作数前面,比如 -7 前面的 -。后缀运算符的运算符在操作数的后面,比如 7 toLong 中的 toLong。前缀和后缀操作符都使用一个操作数,而中缀运算符使用前后两个操作数。Scala 在实现前缀和后缀操作符的方法,其方法名都以 unary_- 开头。

比如,表达式 -2.0 实际上调用 (2.0).unary_-方法。

scala> -2.0
res1: Double = -2.0

scala> (2.0).unary_-
res2: Double = -2.0

如果你需要定义前缀方法,你只能使用 +-!~ 四个符号作为前缀操作符。

后缀操作符在不使用 . 和括号调用时不带任何参数。在 Scala 中,你可以省略掉没有参数的方法调用的空括号。按照惯例,如果你调用方法是为了利用方法的“副作用”,此时写上空括号,如果方法没有任何副作用(没有修改其它程序状态),你可以省略掉括号。

比如:

scala> val s = "Hello, World"
s: String = Hello, World

scala> s toLowerCase
warning: there was one feature warning; re-run with -feature for details
res0: String = hello, world

具体 Scala 的基本数据类型支持的操作符,可以参考 Scala API 文档。下面以示例介绍一些常用的操作符:

算术运算符(包括 +×/

scala> 1.2 + 2.3
res0: Double = 3.5

scala> 'b' -'a'
res1: Int = 1

scala> 11 % 4   #取余
res2: Int = 3

scala> 11.0f / 4.0f
res3: Float = 2.75

scala> 2L * 3L
res4: Long = 6

关系和逻辑运算符(包括 ><>=! 等)

scala> 1 >2
res5: Boolean = false

scala> 1.0 <= 1.0
res6: Boolean = true

scala> val thisIsBoring = !true
thisIsBoring: Boolean = false

scala> !thisIsBoring
res7: Boolean = true

scala> val toBe = true
toBe: Boolean = true

scala> val question = toBe || ! toBe  #表示逻辑或
question: Boolean = true

要注意的是,逻辑运算支持“短路运算”,比如 op1 || op2,当 op1 = trueop2 无需再计算,就可以知道结果为 true。这时 op2 表示式不会执行。例如:

scala> def salt()= { println("salt");false}
salt: ()Boolean

scala> def pepper() = {println("pepper");true}
pepper: ()Boolean

scala> pepper() && salt()   #表示逻辑与
pepper
salt
res0: Boolean = false

scala> salt() && pepper()
salt
res1: Boolean = false

位操作符(包括 &|^~

scala> 1 & 2
res2: Int = 0

scala> 1 | 2
res3: Int = 3

scala> 1 ^ 2
res4: Int = 3

scala> ~1
res5: Int = -2

对象恒等比较

如果需要比较两个对象是否相等,可以使用 ==!= 操作符。

scala> 1 == 2
res6: Boolean = false

scala> 1 !=2
res7: Boolean = true

scala> List(1,2,3) == List (1,2,3)
res8: Boolean = true

scala> ("he"+"llo") == "hello"
res9: Boolean = true

Scala 的 == 和 Java 不同,scala 的 == 只用于比较两个对象的是否相同。而对于引用类型的比较使用另外的操作符 eqne

操作符的优先级和左右结合性

Scala 的操作符的优先级和 Java 基本相同,如果有困惑时,可以使用 () 改变操作符的优先级。操作符一般为左结合,Scala 规定了操作符的结合性由操作符的最后一个字符定义。对于以 结尾的操作符都是右结合,其它的操作符多是左结合。

例如:a*ba.*(b),而 a:::bb.:::(a),而 a:::b:::c 等价于 a::: (b ::: c)a*b*c 等价于 (a*b)*c

进阶:基本数据类型的实现方法

本小节内容可能需要部分后面章节的知识作为基础。若理解有困难,可以尝试学习后续课程后再返回查看。

Scala 的基本数据类型是如何实现的呢?实际上,Scala 以和 Java 同样的方式存储整数:把它当作 32 位的字类型。这对于有效使用 JVM 平台和与 Java 库的互操作性方面来说都很重要。

标准的操作,如加法或乘法,都被实现为数据类型基本运算操作。然而,当整数需要被当作(Java)对象看待的时候,Scala 使用了“备份”类 java.lang.Integer。如在整数上调用 toString 方法或者把整数赋值给 Any 类型的变量时,就会这么做。当需要的时候,Int 类型的整数能自动转换为 java.lang.Integer 类型的“装箱整数(boxed integer)”。

这些听上去和 Java 的 box 操作很像,实际上它们也很像,但这里有一个重要的差异,Scala 使用 box 操作比在 Java 中要少的多:

// Java 代码
boolean isEqual(int x,int y) {
  return x == y;
}
System.out.println(isEqual(421,421));

你当然会得到 true。现在,把 isEqual 的参数类型变为 java.lang.Integer (或 Object,结果都一样):

// Java 代码
boolean isEqual(Integer x, Integer y) {
  return x == y;
}
System.out.println(isEqual(421,421));

你会发现你得到了 false 的原因是,数 421 使用 box 操作了两次,因此参数 xy 是两个不同的对象。因为在引用类型上,== 表示引用相等,而 Integer 是引用类型,所以结果是 false。这是展示了 Java 不是纯面向对象语言的一个方面。我们能清楚观察到基本数据值类型和引用类型之间的差别。

现在在 Scala 里尝试同样的实验:

scala> def isEqual(x:Int,y:Int) = x == y
isEqual: (x: Int, y: Int)Boolean

scala> isEqual(421,421)
res0: Boolean = true

scala> def isEqual(x:Any,y:Any) = x == y
isEqual: (x: Any, y: Any)Boolean

scala> isEqual(421,421)
res1: Boolean = true

Scala 的 == 设计出自动适应变量类型的操作,对值类型来说,就是自然的(数学或布尔)相等。对于引用类型,== 被视为继承自 Objectequals 方法的别名。比如对于字符串比较:

scala> val x = "abcd".substring(2)
x: String = cd

scala> val y = "abcd".substring(2)
y: String = cd

scala> x == y
res0: Boolean = true

而在 Java 里,xy 的比较结果将是 false。程序员在这种情况应该用 equals,不过它容易被忘记。

然而,有些情况下,你可能需要使用引用相等代替用户定义的相等。例如,某些时候效率是首要因素,你想要把某些类进行哈希合并(hash cons),然后通过引用相等比较它们的实例。为了满足这种情况,类 AnyRef 定义了附加的 eq 方法,它不能被重载并且实现为引用相等(也就是说,它表现得就像 Java 里对于引用类型的 == 那样)。同样也有一个 eq 的反义词,被称为 ne。例如:

scala> val x = new String("abc")
x: String = abc

scala> val y = new String("abc")
y: String = abc

scala> x == y
res0: Boolean = true

scala> x eq y
res1: Boolean = false

scala> x ne y
res2: Boolean = true