本文是對Scala語言的基本語法的一個學習總結,共包括如下章節:
- 基本元素
- 結構化語句
- 數據類型
- 小結
參考資料:
1、如果需要對scala的基本概念和開發環境搭建進行了解,可參考文章《Scala學習筆記(1)-快速起步》。
一、基本元素
(一)數據類型
scala是一種強類型語言,所有數據都是有類型的。scala的數據類型可分為兩種:
1、一種是scala預定義的一些數據類型,比如預定義的值類型(如Int,Char等)、預定義的引用類型(如字符串類型String)。
2、另一種是自定義的數據類型,如自定義類。
(二)基礎數據類型
scala中有如下基礎數據類型,與Java中是一致的。如下表所列:
類型名 | 含義 | 類型名 | 含義 |
---|---|---|---|
Byte | 8位有符號整數 | Float | 32位單精度浮點數 |
Short | 16位有符號整數 | Double | 32位雙精度浮點數 |
Int | 32位有符號整數 | Char | 16位無符號數 |
Long | 64位有符號整數 | Boolean | 布爾型,true或false |
需要注意的是,scala并沒有基本類型,scala是一種純面向對象的編程語言。它的每一個基礎類型都是一個類。當把一個scala應用程序編譯成java字節碼的時候,編譯器會自動把scala基礎類型轉變成java基本類型,這樣有助于提高應用程序的性能。
(三)變量定義
scala有兩種類型的變量:可變和不可變的。
可變變量使用關鍵字var聲明,可以在創建之后重新賦值,如:
var x = 10
x =20
不可變變量使用關鍵字val聲明,在初始化之后不可以重新賦值了,如:
val y = 10
y =20 //這個語句會報編譯器錯誤
scala語言的變量定義有下特點:
1、需要用關鍵字var 或 val來定義
2、變量定義時就必須進行初始化賦值
3、變量定義時可以省去類型名,會根據賦值進行類型推斷,如下面兩個語句是等價的:
var x: Int= 10
var x =10
如果要聲明類型,類型名在變量名后面,之間以冒號分隔。顯然,不加類型代碼更簡潔。
(四)字面量
scala中各數據類型的字面量與java中的一致。
(五)表達式
表達式是可計算的語句,每個表達式都有一個返回值。
(六)語句塊
利用{}將多個語句組成一個語句塊,語句塊也是有返回值的,塊中最后一個表達式的結果也是整個塊的結果。如下面是合法的代碼:
object Hello {
def main(args: Array[String]) {
var x=
{
val x = 1 + 1
x + 1
}
println(x)
}
}
(七)類
scala是一種純面向對象的編程語言,類是面向對象編程的基本單元,所有的代碼都包含在類中。scala的類和Java中的類基本相似,但也有一些自己的特點。下面是一個簡單的類的定義:
class Person{
var name:String="tom"
def show()={
println(name)
}
}
上面代碼中定義了一個Person類,類中有一個成員變量name和一個成員方法show,可以看出,和java中的類定義類似。
scala中的類是一種用戶自定義的數據類型。關于scala的類及面向對象的編程的更詳細的信息我們在后面的文章中介紹。
(八)函數和方法
函數和方法這兩個名詞在很多語言中都會提到,通常來說,在類中定義的作為類的成員的函數稱為方法。比如在c語言中,沒有類的概念,所以沒有方法的概念,所以全部稱為函數。而在c++中,函數既可以獨立在類的外部,也可以定義在類的內部作為類的成員,所以在類的外部定義時我們稱為函數,在類的內部作為成員定義時我們稱為方法。
函數還是方法都是完成一個特定功能的代碼集合,無論是函數還是方法,都有幾個共同的特性:
1、輸入參數列表,每個函數或方法都可以有0個或多個參數
2、返回值,函數或方法都會有一個返回值
3、代碼,函數或方法通過其包含的代碼來完成其設定的功能。只有被調用后,代碼才會被執行。
在scala中,我們使用def關鍵字定義方法(如上面Person類中的show方法),scala中的方法的語法和java中的方法類似。關于scala中方法的詳細介紹,參見《Scala學習筆記(3)-面向對象編程上篇》;關于scala函數的含義,以及函數與方法的區別,參見《Scala學習筆記(5)-函數式編程》中的介紹。
二、結構化語句
(一)if條件語句
scala的if條件語句語法與java的完全一致,本文不再介紹。
需要說明一點的是,在scala中,任何語句都是一個表達式,表達式都是有值的。if語句也不例外,可以將if作為一個表達式賦值給變量。如下面的例子:
scala> val re = if(3>1) println("A")
A
re: Unit = ()
scala> val re = if(3>5) println("A")
re: Unit = ()
scala> val re = if(3>5) 10 else 5
re: Int = 5
if語句作為一個表達式,其返回值實際是if語句中最后一個被執行語句的返回值。上面代碼中,第一個if語句最后執行的是 println("A") 語句,因為println方法的返回值是Unit類型,Unit類型具有唯一實例(),后面會詳細介紹,這里理解成java中的void即可。第二個if語句沒有滿足條件的語句被執行,返回值也是Unit類型,值為()。第三個if語句,最后執行的語句就是個整數表達式5,所以返回的類型是Int型,值為5。
(二)循環語句
1、while循環
scala語言提供了和java中一樣用法的while循環和do while循環。下面看一個使用while循環的簡單例子:
scala> var i=1
i: Int = 1
scala> while(i<5){
| print(i+",")
| i=i+1
| }
1,2,3,4,
scala>
2、for循環
scala語言提供了for循環語句,和c/c++/java不同的是,沒有for(初始化變量;條件判斷;更新變量)這樣的循環語法,而是支持對集合的遍歷來實現循環(類似Java中for語句對Java的遍歷)。for循環的語法格式如下:
for(元素變量 <- 集合對象){
.....循環代碼
}
如果循環體代碼只有一條語句,則{}可省略。
我們來看一個簡單例子:
scala> var list = List("hello ","world ","good")
list: List[String] = List("hello ", "world ", good)
scala> for(item <- list){
| print(item)
| }
hello world good
scala>
上面代碼中的list變量是一個List類型的實例,List是scala中的列表集合,和Java中的List接口類似。
3、有過濾條件的for循環,語法格式如:
for(元素變量 <- 集合對象 if條件判斷1; if條件判斷2.....){
//所有條件都滿足才執行循環中的語句
}
下面看一個具體的例子,如下:
scala> var list = List(1,5,2,7,4)
list: List[Int] = List(1, 5, 2, 7, 4)
scala> for(item <- list if item>2;if item<7) print(item)
54
scala>
說明,條件不滿足并不是結束(或跳出)整個for循環,而是不執行本次循環體中的語句。
關于如何跳出循環,下面會進行介紹。
同if語句一樣,循環語句也可以作為一個表達式,也有返回值,可以賦值給變量,只不過scala的循環語句,無論是while還是for,不管循環體中是什么代碼,當作表達式時,返回值的類型總是為Unit。如下面例子:
scala> val re = for(i<-List(2,3)) 5
re: Unit = ()
scala> val re = for(i<-List(2,3)) print(i)
23re: Unit = ()
可以看出,無論循環體中執行的是一個表達式,還是一個返回值為Unit的方法調用,for循環作為一個表達式返回值都是Unit類型。
(三)跳出循環
前面我們介紹了scala中的循環語句,但一直沒有提到如何跳出循環,在java中,提供了break和continue的關鍵字用于跳出循環和提前結束本次循環,但scala中沒有這兩個關鍵字。在scala中,可以通過其它方法來結束循環。
實現contine關鍵字的功能比較簡單,只需通過if語句來實現。我們看一段Java代碼使用contine關鍵字的例子:
int i=10;
while(i>0){
i=i-1;
if(i%2==0){
continue;
}
System.out.println(i);
}
上面代碼不用continue關鍵字,只需改變下if語句,如下面代碼是scala中的實現:
var i=10
while(i>0){
i=i-1
if(i%2!=0){
println(i)
}
}
不過想替代break關鍵字,尤其是對于for循環,就相對麻煩一些了。下面我們來看看怎么處理。
方法一:通過變量來控制
對于while循環,因為天生其條件就是布爾表達式,只需在循環體中根據情況修改變量的值,讓條件表達式為false,就可以結束循環。對于for循環,也可以加上一個條件變量來實現,如下面例子:
var list = List(1,5,-1,7,4)
var flag=true
for(item <- list if flag){
if(item<0)
flag=false
else
print(item)
}
顯然對于for循環,這不是一種好的方式,因為效果上能實現類似跳出循環,但實際是計算flag值為false后,對于集合中的剩余元素,每個元素還是得判斷一下,只是因為flag的值一直為false了,循環體不會被執行。
方法二:使用scala.util.control.Breaks類
Breaks類中的breakable方法可以跟一代碼塊,在代碼塊中調用Breaks類的break方法可以跳出這個代碼塊,如下面例子:
import scala.util.control.Breaks._
println("begin")
breakable{
println("a1")
break
println("a2")
}
println("end")
上面代碼首先通過import語句將Breaks類引入。breakable方法后跟著的是代碼塊,實際這是breakable方法的參數,如果我們查看breakable方法的源代碼,會發現breakable方法的參數就是一個不帶任何參數的函數類型。而一個代碼塊就可以看作是不帶參數的函數,所以可以傳遞給breakable方法。在該代碼塊中,我們調用了break方法。執行上述代碼,我們會發現break語句后面的 println("a2") 語句沒有被執行。
有了Breaks這個類的功能,我們只需將循環代碼放入到breakable方法的參數代碼塊中,然后在循環體中根據條件調用Breaks類的break方法,這樣就可以跳出整個代碼塊,也就跳出了循環。如上面for循環例子可這樣實現:
import scala.util.control.Breaks._
var list = List(1,5,-1,7,4)
breakable{
for(item <- list){
if(item<0) break
print(item)
}
}
顯然,我們也可以很容易的實現嵌套循環中從內循環中跳到最外部,如下面例子:
import scala.util.control.Breaks._
var list = List(1,5,-1,7,4)
breakable{
for(i <- List(1,2,3))
for(item <- list){
if(item<0) break
print(item)
}
}
可以看出,使用Breaks類提供的方法來跳出循環,在簡單場景下和java中使用break關鍵字有點類似,但Breaks類的功能更加強大和通用。
方法三:使用內嵌方法和return語句
我們可以將循環語句放入到一個方法中,然后當條件滿足時,使用return語句,這樣方法都結束了,自然循環就結束了。如下面例子:
def func(){
println("begin")
def test(){
for(item <- List(1,5,-1,7,4) ){
if(item<0)
return
println(item)
}
}
test()
println("end")
}
func()
上面代碼在方法func的內部定義了一個test方法,在test方法的for循環中使用return語句提前結束方法,也就是提前結束了循環。這種方式雖然可以實現跳出循環,但要顯示定義一個方法,顯然降低了代碼的整潔度。
三、數據類型
(一)概述
scala是一種純面向對象的編程語言,所有的數據類型都是類,所有的值都是對象。scala語言中所有的類都繼承自一個共同的超類Any,是scala類層級的根節點,在其下面有兩個子類:AnyVal和AnyRef。
其中AnyVal是Scala中所有值類型的超類,scala中有9種預定義的值類型,這9種預定義的值類型分別是Double,Float,Long,Int,Short,Byte,Unit,Char和Boolean。值類型的實例必須都有值。
AnyRef 代表引用類型,全部的非值類型都被定義為引用類型。在Scala中預定義的一些類型,如字符串類型String,集合類型List,Set等,以及用戶自定義的每個類型都是 AnyRef 的子類型。AnyRef 相當于java中的java.lang.Object類。
下圖顯示了scala的類型層次關系。
在上圖的scala類型層次圖中我們看到最下面還有Null和Nothing兩個類型,后面會專門介紹。
(二)基礎類型的隱式(implict)轉換
上面提到,AnyVal類型的子類中,有9種預定義的值類型分別是Double,Float,Long,Int,Short,Byte,Char,Boolean和Unit。除Boolean和Unit類型外,其它的值型之間可以實現隱式的轉換。它們的轉換關系如下:
也就是按照上圖箭頭的方向,比如Byte類型的數據可以直接賦值給Short及之后的類型的變量。因為越是往后,類型的存儲范圍越大,高范圍的可與容納低范圍的,反之不行。如:
scala> var b:Byte=100
b: Byte = 100
scala> var s:Short=50
s: Short = 50
scala> s=b
s: Short = 100
scala> b=s
<console>:13: error: type mismatch;
found : Short
required: Byte
b=s
從上面例子可以看出,Byte類型的變量賦值給Short類型的沒問題,反之會報錯。Char類型雖然是保存的字符,可以賦值給Int類型及后面的類型,賦值時會自動按照ASCII碼來轉換,但反之不行。不過可以將字面量的整數賦值給Char類型,會自動轉換為字符。如:
scala> var c:Char='a'
c: Char = a
scala> c=98
c: Char = b
scala> var a:Int = c
a: Int = 98
scala> c=a
<console>:13: error: type mismatch;
found : Int
required: Char
c=a
^
(三)Null和Nothing
前面的scala類型層次圖中我們看到最下面還有Null和Nothing兩個類型。
其中Null 是所有引用類型的子類型(也就是說是AnyRef 的任何子類型)。它具有唯一的實例null(是個關鍵字)。null 主要是為了和其它JVM語言的交互而提供的,并且應該永遠不要在Scala代碼中使用它。
Nothing 是所有類型的子類型(包括Null類型),也叫做底部類型。但是Nothing類卻不存在任何實例。Nothing的主要用處體現在兩個地方:
1、在定義空列表時使用,如:
scala> var list = List()
list: List[Nothing] = List()
2、作為方法不正常返回的返回類型使用,如:
def error(msg:String):Nothing={
throw new RuntimeException(msg)
}
def divide(x: Int, y: Int): Int ={
if (y != 0)
x / y
else
error("can not divide by zero")
}
(四)Unit類型
9種值類型中還有一個Unit類型,這個在java中沒有對應的類型,它是scala中一種特殊的類型,有唯一的實例() ,代表沒有值,類似c或Java中的void。通常用作不返回任何結果的方法的結果類型。如:
scala> val re = println("hello")
hello
re: Unit = ()
scala> def test() = {}
test: ()Unit
在上面的第一個例子中,println函數只是輸出信息,不返回任何結果。這時調用時返回的就是()值。第二個例子,定義了一個空函數,顯示函數的返回類型就是Unit類型。
當然我們也可以顯示的定義一個方法的返回值為Unit類型,如:
def test(info:String):Unit={
println(info)
}
(五)Option類型
大多數語言都有一個特殊的關鍵字或者對象來表示一個對象引用的是“無”,在Java中,它是null。在Java 里,null 是一個關鍵字,不是一個對象,所以對它調用任何方法都是非法的。但是這對語言設計者來說是一件令人疑惑的選擇。為什么要在程序員希望返回一個對象的時候返回一個關鍵字呢?
在前面部分已經提到,scala中的Null類型,有唯一的實例null(是個關鍵字),實際上scala提供null主要是為了和其它JVM語言的交互而提供的,并且應該永遠不要在Scala代碼中使用它。
在scala中,我們應該使用Option類型。Scala中的Option(選項)類型用來表示一個值是可選的(有值或無值)。它是一個泛型類型,Option[T] 是一個類型為 T 的可選值的容器: 如果值存在, Option[T] 的值就是Some[T] ,如果不存在, Option[T] 的值就是 None 。
Scala程序使用Option非常頻繁,在Java中使用null來表示空值,代碼中很多地方都要添加null關鍵字檢測,不然很容易出現NullPointException。因此Java程序需要關心那些變量可能是null,而這些變量出現null的可能性很低,但一但出現,很難查出為什么出現NullPointerException。 Scala的Option類型可以避免這種情況,因此Scala應用推薦使用Option類型來代表一些可選值。比如如果一個函數的返回值可能不會引用任何值,則返回類型可以定義為Option類型,這樣調用者一眼就可以看出這種類型的值可能為None。
比如scala提供的數據結構map,其get方法(根據key獲取value)返回的就是一個Option類型。這么設計的原因是因為key有可能不存在。我們看一個例子:
scala> val myMap: Map[String, String] = Map("key1" -> "hello")
myMap: Map[String,String] = Map(key1 -> hello)
scala> val value1: Option[String] = myMap.get("key1")
value1: Option[String] = Some(hello)
scala> val value2: Option[String] = myMap.get("key2")
value2: Option[String] = None
scala> var re = value1.get
re: String = hello
可以看出因為map中存在鍵為key1的數據,所以調用map的get方法返回的是Option類型的實例 Some(hello) ;而鍵key2不存在,返回的是Option類型的另一個實例None。對于Some[T],通過調用它的get方法,可以獲取T值。
顯然在實際使用時,我們并不知道返回的Option對象的值是Some還是None,所以并不能不經過判斷直接調用get,這有很多種解決方法:
1、先判斷值是否是None,如:
scala> val myMap: Map[String, String] = Map("key1" -> "hello")
myMap: Map[String,String] = Map(key1 -> hello)
scala> val value1: Option[String] = myMap.get("key1")
value1: Option[String] = Some(hello)
scala> if (value1!=None){
| print(value1.get)
| }
hello
判斷一個Option對象的值是否為None,最好的方式是調用Option的isEmpty方法,如:
scala> val myMap: Map[String, String] = Map("key1" -> "hello")
myMap: Map[String,String] = Map(key1 -> hello)
scala> val value1: Option[String] = myMap.get("key1")
value1: Option[String] = Some(hello)
scala> if (!value1.isEmpty) print(value1.get)
hello
不過在scala中,還有比使用if語句更好的方式,就是利用scala的match功能。
2、使用match功能,如
scala> val myMap: Map[String, String] = Map("key1" -> "hello")
myMap: Map[String,String] = Map(key1 -> hello)
scala> val value1: Option[String] = myMap.get("key1")
value1: Option[String] = Some(hello)
scala> val re = value1 match{
| case Some(v) => v
| case None => "?"
| }
re: String = hello
可以看出,使用match語句,如果不存在值返回一個默認值。
3、利用Option的getOrElse方法
Option的getOrElse方法可以設置一個默認值,當Option對象有值時,返回具體的值,當沒有值時,返回默認值。如下面例子:
scala> val myMap: Map[String, String] = Map("key1" -> "hello")
myMap: Map[String,String] = Map(key1 -> hello)
scala> val re1 = myMap.get("key1").getOrElse("?")
re1: String = hello
scala> val re2 = myMap.get("key2").getOrElse("?")
re2: String = ?
Option類除了有上面介紹的get方法、isEmpty方法、getOrElse方法,還有很多其它方法,這里不再一一介紹。
4、使用for循環
上面我們提到在Scala里Option[T]實際上是一個容器,就像數組或是List一樣,你可以把他看成是一個可能有零個或一個元素的List。當Option里面有東西的時候,這個List的長度是1(也就是 Some),而當你的Option里沒有東西的時候,它的長度是0(也就是 None)。
如果我們把Option當成一般的List來用,并且用一個for循環來走訪這個Option的時候,如果Option是None,那這個for循環里的程序代碼自然不會執行,于是我們就達到了不用檢查Option是否為None這件事。如下面例子:
scala> val myMap: Map[String, String] = Map("key1" -> "hello")
myMap: Map[String,String] = Map(key1 -> hello)
scala> val value1: Option[String] = myMap.get("key1")
value1: Option[String] = Some(hello)
scala> for(item<-value1) println(item);
hello
scala> val value2: Option[String] = myMap.get("key2")
value2: Option[String] = None
scala> for(item<-value2) println(item);
scala>
從上面例子可以看出,因為value2中沒有值,所以第二個for循環中的代碼就沒有被執行。
(六)元組類型
元組是scala中的一種數據類型,它是不同類型值的聚集,它可以將不同類型的值存放在一個變量中保存。如下面例子:
scala> var data=("hello",12,true)
data: (String, Int, Boolean) = (hello,12,true)
scala> var data: (String, Int, Boolean) =("hello",12,true)
data: (String, Int, Boolean) = (hello,12,true)
上面代碼定義了一個元組類型的變量data,值為("hello",12,true),可以看出元組的類型名并不是統一的一個名稱,而是跟值有關系。上面元組變量data對應的類型名為(String, Int, Boolean)。
元組最大的方便之處就可以將多個值放在一起,后面可以很方便的提取其中的各個值到多個單獨的變量中。如下面例子:
scala> var (a,b,c) =data
a: String = hello
b: Int = 12
c: Boolean = true
可以看出,通過 var (a,b,c) =data 這樣的操作,將data元組中的3個值提取到了a,b,c三個獨立的變量中。這個特性尤其在方法返回多個值時很有用。
(七)符號類型
scala的符號類型(名稱為Symbol),主要其標識作用。可以采用如下的方式定義符號類型:
scala> var s1 = 'tag
s1: Symbol = 'tag
scala> var s2:Symbol = 'tag
s2: Symbol = 'tag
在標識符前面加上’就定義了一個符號類型的值。上面代碼定義了兩個符號類型變量,s1沒有顯示聲明類型名,由scala自動推斷出來;s2顯示的聲明類型為Symbol。
四、小結
本文對scala的基本語法、結構化語句、數據類型等知識進行了總結,在后面的文章中會對scala面向對象、面向函數的編程特性進行介紹。