如果你熟悉Java,你会很开心地发现Java基本类型和操作符在Scala里有同样的意思。然而即使你是一位资深Java开发者,这里也仍然有一些有趣的差别使得本章值得一读。因为本章提到的一些Scala的方面实质上与Java相同,我们插入了一些注释,Java开发者可以安全跳过,以加快你的进程。本章里,你会获得Scala基本类型的概观,包括String和值类型Int,Long,Short,Byte,Float,Double,Char还有Boolean。你会学到可以在这些类型上执行的操作,包括Scala表达式里的操作符优先级是如何工作的。你还会学到隐式转换是如何“丰富”这些基本类型的变体,并带给你那些由Java提供支持之外的附加操作。
5.1 一些基本类型
表格5.1显示了Scala的许多基本的类型和其实例值域范围。总体来说,类型Byte,Short,Int,Long和Char被称为整数类型:integral type。整数类型加上Float和Double被称为数类型:numeric type。
除了String归于java.lang包之外,其余所有的基本类型都是包scala的成员。1
目前实际上你可以使用与Java的原始类型相一致的Scala值类型的小写化名。比如,Scala程序里你可以用int替代Int。但请记住它们都是一回事:scala.Int。Scala社区实践提出的推荐风格是一直使用大写形式。
敏锐的Java开发者会注意到Scala的基本类型与Java的对应类型范围完全一样。这让Scala编译器能直接把Scala的值类型:value type实例,如Int或Double,在它产生的字节码里转译成Java原始类型。
5.2 文本
所有在表5.1里列出的基本类型都可以写成文本:literal。文本是直接在代码里写常量值的一种方式。
整数文本
类型Int,Long,Short和Byte的整数文本有三种格式:十进制,十六进制和八进制。整数文本的开头方式说明了数字的基。如果数开始于0x或0X,那它是十六进制(基于16),并且可能包含从0到9,及大写或小写的从A到F的数字。举例如下:
scala> val hex = 0x5 hex: Int = 5 scala> val hex2 = 0x00FF hex2: Int = 255 scala> val magic = 0xcafebabe magic: Int = -889275714
请注意,不论你用什么形式的整数文本初始化,Scala的shell始终打印输出基于10的整数值。因此解释器会把你用文本0x00FF初始化的hex2变量的值显示为十进制的255。如果数开始于零,就是八进制(基于8)的,并且只可以包含数字0到7。下面是一些例子:
scala> val oct = 035 // (八进制35是十进制29) oct: Int = 29 scala> val nov = 0777 nov: Int = 511 scala> val dec = 0321 dec: Int = 209
如果数开始于非零数字,并且没有被修饰过,就是十进制(基于10)的。例如:
1 scala> val dec1 = 31 2 dec1: Int = 31 3 scala> val dec2 = 255 4 dec2: Int = 255 5 scala> val dec3 = 20 6 dec3: Int = 20
如果整数文本结束于L或者l,就是Long类型,否则就是Int类型。一些Long类型的整数文本有:
scala> val prog = 0XCAFEBABEL prog: Long = 3405691582 scala> val tower = 35L tower: Long = 35 scala> val of = 31l of: Long = 31
如果Int类型的文本被赋值给Short或者Byte类型的变量,文本就会被看作是能让文本值在那个类型有效范围内那么长的Short或者Byte类型。如:
1 scala> val little: Short = 367 2 little: Short = 367 3 scala> val littler: Byte = 38 4 littler: Byte = 38
浮点数文本
浮点数文本是由十进制数字,可选的小数点和可选的E或e及指数部分组成的。下面是一些浮点数文本的例子:
1 scala> val big = 1.2345 2 big: Double = 1.2345 3 scala> val bigger = 1.2345e1 4 bigger: Double = 12.345 5 scala> val biggerStill = 123E45 6 biggerStill: Double = 1.23E47
请注意指数部分表示的是乘上以10为底的幂次数。因此,1.2345e1就是1.2345乘以101,等于12.345。如果浮点数文本以F或f结束,就是Float类型的,否则就是Double类型的。可选的,Double浮点数文本也可以D或d结尾。Float文本举例如下:
1 scala> val little = 1.2345F 2 little: Float = 1.2345 3 scala> val littleBigger = 3e5f 4 littleBigger: Float = 300000.0
最后一个值可以用以下(或其他)格式表示为Double类型:
1 scala> val anotherDouble = 3e5 2 anotherDouble: Double = 300000.0 3 scala> val yetAnother = 3e5D 4 yetAnother: Double = 300000.0
字符文本
字符文本可以是在单引号之间的任何Unicode字符,如:
1 scala> val a = ‘A‘ 2 a: Char = A
除了在单引号之间显式地提供字符之外,你还可以提供一个表示字符代码点的前缀反斜杠的八进制或者十六进制数字。八进制数必须在‘\0‘和‘\377‘之间。例如字母A的Unicode字符代码点是八进制101。因此:
scala> val c = ‘\101‘ c: Char = A
字符文本同样可以以前缀\u的四位十六进制数字的通用Unicode字符方式给出,如:
1 scala> val d = ‘\u0041‘ 2 d: Char = A 3 scala> val f = ‘\u0044‘ 4 f: Char = D
实际上,这种unicode字符可以出现在Scala程序的任何地方。例如你可以这样写一个标识符:
scala> val B\u0041\u0044 = 1 BAD: Int = 1
这个标识符被当作BAD,上面代码里的两个unicode字符扩展之后的结果。通常,这样命名标识符是个坏主意,因为它太难读。然而,这种语法能够允许含非ASCII的Unicode字符的Scala源文件用ASCII来代表。
最终,还有一些字符文本被表示成特殊的转义序列,参见表格5.2。例如:
1 scala> val backslash = ‘\\‘ 2 backslash: Char = \
字串文本
字串文本由双引号(")环绕的字符组成:
1 scala> val hello = "hello" 2 hello: java.lang.String = hello
引号内的字符语法与字符文本相同,如:
1 scala> val escapes = "\\\"\‘" 2 escapes: java.lang.String = \"‘
由于这种语法对于包含大量转义序列或跨越若干行的字串很笨拙。因此Scala为原始字串:raw String引入了一种特殊的语法。以同一行里的三个引号(""")开始和结束一条原始字串。内部的原始字串可以包含无论何种任意字符,包括新行,引号和特殊字符,当然同一行的三个引号除外。举例来说,下面的程序使用了原始字串打印输出一条消息:
1 println("""Welcome to Ultamix 3000. Type "HELP" for help.""")
运行这段代码不会产生完全符合所需的东西,而是:
1 Welcome to Ultamix 3000. Type "HELP" for help.
原因是第二行前导的空格被包含在了字串里。为了解决这个常见情况,字串类引入了stripMargin方法。使用的方式是,把管道符号(|)放在每行前面,然后在整个字串上调用stripMargin:
1 println("""|Welcome to Ultamix 3000. 2 |Type "HELP" for help.""".stripMargin)
这样,输出结果就令人满意了:
1 Welcome to Ultamix 3000. 2 Type "HELP" for help.
符号文本
符号文本被写成‘<标识符>,这里<标识符>可以是任何字母或数字的标识符。这种文本被映射成预定义类scala.Symbol的实例。特别是,文本‘cymbal将被编译器扩展为工厂方法调用:Symbol("cymbal")。符号文本典型的应用场景是你在动态类型语言中使用一个标识符。比方说,或许想要定义个更新数据库记录的方法:
1 scala> def updateRecordByName(r: Symbol, value: Any) { 2 // code goes here 3 } 4 updateRecordByName: (Symbol,Any)Unit
方法带了一个符号参数指明记录的字段名和一个字段应该更新进记录的值。在动态类型语言中,你可以通过传入一个未声明的字段标识符给方法调用这个操作,但Scala里这样会编译不过:
scala> updateRecordByName(favoriteAlbum, "OK Computer") <console>:6: error: not found: value favoriteAlbum updateRecordByName(favoriteAlbum, "OK Computer")
基本同样简洁的替代方案是,你可以传递一个符号文本:
1 scala> updateRecordByName(‘favoriteAlbum, "OK Computer")
除了发现它的名字之外,没有太多能对符号做的事情:
scala> val s = ‘aSymbol s: Symbol = ‘aSymbol scala> s.name res20: String = aSymbol
另一件值得注意的事情是符号是被拘禁:interned的。如果你把同一个符号文本写两次,那么两个表达式将指向同一个Symbol对象。
布尔型文本
布尔类型有两个文本,true和false:
1 scala> val bool = true 2 bool: Boolean = true 3 scala> val fool = false 4 fool: Boolean = false
就这些东西了。现在你简直(literally),可以称为Scala的专家了。
5.3 操作符和方法
Scala为它的基本类型提供了丰富的操作符集。如前几章里描述的,这些操作符实际只是作用在普通方法调用上华丽的语法。例如,1 + 2与(1).+(2)其实是一回事。换句话说,就是Int类包含了叫做+的方法,它带一个Int参数并返回一个Int结果。这个+方法在两个Int相加时被调用:
1 scala> val sum = 1 + 2 // Scala调用了(1).+(2) 2 sum: Int = 3
想要证实这点,可以把表达式显式地写成方法调用:
1 scala> val sumMore = (1).+(2) 2 sumMore: Int = 3
而真正的事实是,Int包含了许多带不同的参数类型的重载:overload的+方法例如,Int还有另一个也叫+的方法参数和返回类型为Long。如果你把Long加到Int上,这个替换的+方法就将被调用:
scala> val longSum = 1 + 2L // Scala调用了(1).+(2L) longSum: Long = 3
符号+是操作符——更明确地说,是中缀操作符。操作符标注不仅限于像+这种其他语言里看上去像操作符一样的东西。你可以把任何方法都当作操作符来标注。例如,类String有一个方法indexOf带一个Char参数。indexOf方法搜索String里第一次出现的指定字符,并返回它的索引或-1如果没有找到。你可以把indexOf当作中缀操作符使用,就像这样:
1 scala> val s = "Hello, world!" 2 s: java.lang.String = Hello, world! 3 scala> s indexOf ‘o‘ // Scala调用了s.indexOf(’o’) 4 res0: Int = 4
另外,String提供一个重载的indexOf方法,带两个参数,分别是要搜索的字符和从哪个索引开始搜索。(前一个indexOf方法开始于索引零,也就是String开始的地方。)尽管这个indexOf方法带两个参数,你仍然可以用操作符标注的方式使用它。不过当你用操作符标注方式调用带多个参数的方法时,这些参数必须放在括号内。例如,以下是如何把另一种形式的indexOf当作操作符使用的例子(接前例):
1 scala> s indexOf (‘o‘, 5) // Scala调用了s.indexOf(’o’, 5) 2 res1: Int = 8
目前为止,你已经看到了中缀:infix操作符标注的例子,也就是说调用的方法位于对象和传递给方法的参数或若干参数之间,如“7 + 2”。Scala还有另外两种操作符标注:前缀和后缀。前缀标注中,方法名被放在调用的对象之前,如,-7里的‘-’。后缀标注中,方法放在对象之后,如,“7 toLong”里的“toLong”
与中缀操作符——操作符带后两个操作数,一个在左一个在右——相反,前缀和后缀操作符都是一元:unary的:它们仅带一个操作数。前缀方式中,操作数在操作符的右边。前缀操作符的例子有-2.0,!found和~0xFF。与中缀操作符一致,这些前缀操作符是在值类型对象上调用方法的简写方式。然而这种情况下,方法名在操作符字符上前缀了“unary_”。例如,Scala会把表达式-2.0转换成方法调用“(2.0).unary_-”。你可以输入通过操作符和显式方法名两种方式对方法的调用来演示这一点:
1 scala> -2.0 // Scala调用了(2.0).unary_- 2 res2: Double = -2.0 3 scala> (2.0).unary_- 4 res3: Double = -2.0