標籤:
1.Scala的數實值型別
Scala的數實值型別與Java類似,他們的範圍與Java也是一致的。與Java不同的是,他們都是對象,是相應的數值類的執行個體。Scala通過富封裝(Rich Wrapper)類給這些數實值型別提供了強大的支援。
1.1.數實值型別
Scala的數實值型別和取值範圍,見下表。
- Boolean: true 或者 false
- Byte: 8位, 有符號(2-7 ~ 27 - 1)
- Short: 16位, 有符號 (2-15 ~ 215 - 1)
- Int: 32位, 有符號 (2-31 ~ 231 - 1)
- Long: 64位, 有符號 (2-63 ~ 263 - 1)
- Char: 16位, 無符號 (0 ~ 216 - 1)
- Float: 32位, 單精確度浮點數
- Double: 64位, 雙精確度浮點數
你可以像Java中一樣使用這些類型。不過,實際上,Scala的類型系統與Java或其他語言,如C#或Java,不太一樣,在Scala中,基本類型也是class,比如,Int類型,來自scala.Int,每一個數字,都是scala.Int的一個執行個體。 統一的類型系統,即原始類型與類的統一,是Scala的一大特點,這一特點讓Scala比Java和C#等語言更加物件導向。
請注意
數實值型別都被定義為abstract和final的,這意味著,不能通過new獲得實值型別的執行個體。建立它們的執行個體只能通過文本(Literal)。
1.2.裝箱(Boxing)和拆箱(Unboxing)
熟悉Java或C#等語言的讀者會知道,裝箱是指將原始類型轉換成參考型別(對象),用於需要對象的操作,而拆箱,則是把對象轉換成原始類型,用於需要原始類型的情境。
由於數實值型別本身已經是類對象,因此Scala裡不需要裝箱(boxing)和拆箱(unboxing)操作。當然,Scala代碼最終會運行在JVM上,所以實際上,始終會有裝箱成Scala類對象,和拆箱成Java原始實值型別的操作,但是這些操作是透明的,程式員不需要關心(實際上,這是由定義在Predef中的隱式轉換完成的)。
1.3.富封裝類(Rich Wrapper)
如上所述,你可以像在Java中一樣使用基本類型,不過,Scala實際上提供了非常多的方法給基本類型。這些方法在富封裝(Rich Wrapper)裡,比如,Int對應的是scala.runtime.RichInt。每一個基本類型都有一個相對應的富封裝類。 基本類型,在必要的時候通過隱式轉換轉換為對應的富封裝類,從而可調用富封裝類提供的方法。
隱式轉換是Scala相當重要的技術,後續文章將會介紹。
下面我們寫幾行code看一下RichInt的強大功能。
val n1 = 2 max 3println("2 max 3 = " + n1)val n2 = -1.absprintln("-1.abs = " + n2)val n3 = 1 to 5println("1 to 5 = " + n3)val n4 = 1.isValidCharprintln("1.isValidChar = " + n4)val n5 = -1.isValidCharprintln("-1.isValidChar = " + n5)2.Scala的字串(String)
String是非常常用的類型,本質上其實是一系列的Char,但是由於它太常用,在很多方面都有特殊處理,在大多數程式設計語言裡都會被單獨列出。
Scala裡的String是直接借用了Java的String,也就是說,String只是java.lang.String的一個別名,原始碼類似於:
type String = java.lang.String
不過,由於String實際是一系列Char的不可變的集合,Scala中大部分針對集合的操作,都可以用於String,具體來說,String的這些方法存在於類scala.collection.immutable.StringOps中。 由於String在需要時能隱式轉換為StringOps,因此不需要任何額外的轉換,String就可以使用這些方法。如下所示。
val str = "Hello"val r1 = str.drop(2) //"llo"println("\"Hello\".drop(2) = " + r1)val r2 = str.dropRight(2) //"Hel"println("\"Hello\".dropRight(2) = " + r2)val r3 = str.filter( _ != ‘l‘) //"Heo"println("\"Hello\".filter( _ != ‘l‘) = " + r3)val r4 = str.intersect("world") //"lo"println("\"Hello\".intersect(\"world\") = " + r4)
與Java或C#一樣,String是不可變的,對String進行操作,會得到新的String執行個體。因此在需要頻繁操作String的情況下,請使用StringBuilder。
val builder = new StringBuilderbuilder.append("Hello")builder.append(", world")builder += ‘!‘builder.insert(0,"Me: ")println(builder) //Me: Hello, world!3.Scala的類層級3.1.Scala的統一類型
Scala中,所有的值都是類對象,而所有的類,包括實值型別,都最終繼承自一個統一的根類型Any。統一類型,是Scala的又一大特點。更特別的是,Scala中還定義了幾個底層類(Bottom Class),比如Null和Nothing。類層級結構圖如下所示。
3.1.1.頂層類Any
Any是最頂層的類,這意味著,所有的類都是Any的子類型,都可以調用Any類的方法。頂層類Any提供了如下方法,Scala的任意對象都可以調用這些方法。
final def ==(that: Any): Booleanfinal def !=(that: Any): Booleandef equals(that: Any): Booleandef ##: Intdef hashCode: Intdef toString: String
請注意
以上方法中,== , != 與Java中是有區別的。Scala裡,==永遠跟equals是一樣的,!=則是其否定。==與!=是final的,因此不能被重載。要比較對象的引用,需使用AnyRef的eq方法。
3.1.2.底層類Null和Nothing
Null是所有參考型別的子類型,而Nothing是所有類型的子類型。Null類只有一個執行個體對象,null,類似於Java中的null引用。由於Null是所有參考型別的子類型,這意味著,null可以賦值給任意參考型別,但是不能賦值給實值型別。
也許你要問,那麼是不是可以將Nothing類的執行個體對象賦值給實值型別。答案是不可以,因為Nothing類沒有執行個體,一個也沒有。沒有執行個體的Nothing類有什麼作用呢?
Nothing,可以作為沒有正常傳回值的方法的傳回型別,非常直觀的告訴你這個方法不會正常返回,而且由於Nothing是其他任意類型的子類,他還能跟要求傳回值的方法相容。這句話不是很好理解,我們用個例子來說明。scala.sys有兩個方法error和exit,原始碼如下:
def error(message: String): Nothing = throw new RuntimeException(message)def exit(): Nothing = exit(0)
通過傳回型別Nothing,我們就知道,error方法和exit方法不會正常返回。但是我們在調用他們的時候,可以跟所要求的傳回值相容,比如:
def divide(x: Int, y: Int): Int = { if (y == 0) error("divide by zero") else x / y}
上面的divide方法要求返回一個Int值,但是如果出錯,Nothing也是可以接受的。
另一個用處是在有泛型的地方,由於Scala支援協變(如果不理解協變,沒關係),Nothing在必要的時候可以代替其他的類型。 比如List[Nothing]就可以當做任意類型的List[T]使用。恰好,類型List[Nothing],有一個特別的執行個體,Nil。所以Nil可以當做任意List[T]的執行個體使用。
3.1.3.Unit類型
在類層級結構圖裡能看到,在AnyVal的子類型裡,有一個特別的類型,Unit。Unit類型用來標識過程,也就是沒有明確傳回值的函數。 由此可見,Unit類似於Java裡的void。Unit只有一個執行個體,(),這個執行個體也沒有實質的意義。
參考文獻:
http://meetfp.com/zh/scala-basic/class-hierarchy
[Scala基礎系列 04]Scala基本類型