Scala從零起步:變量和標(biāo)識符
導(dǎo)讀
上周,開啟了作為大數(shù)據(jù)分析師學(xué)習(xí)Scala系列第一篇推文,旨在提綱挈領(lǐng)的介紹Scala理念、特性及開發(fā)環(huán)境安裝。今天開始進入Scala從零起步正題:變量和標(biāo)識符。

在學(xué)習(xí)一門編程語言過程中,變量應(yīng)該是繼輸出“hello,world”之后的第一個核心概念:在計算機程序的世界里,變量可看做是連接程序員和二進制字節(jié)碼之間的橋梁。一般而言,程序員所說的變量是通常是指代一個或一組數(shù)據(jù),然而按照Scala的價值觀,函數(shù)其實也可算作變量。甚至廣義的辯證來講,一段程序代碼中絕大多數(shù)部分都是由各種變量組成的。作為入門第一課,本文所指代變量當(dāng)然僅特指狹義的“變量”——即指代一個或一組數(shù)據(jù)的變量。
本文主要分享三個問題:
如何定義一個變量
變量的數(shù)據(jù)類型
變量/標(biāo)識符命名規(guī)范
個人有過C/C++語言學(xué)習(xí)的經(jīng)歷,當(dāng)前主要應(yīng)用的是Python語言,相較于這兩者,Scala其變量定義方式與二者都全然不同。比如C/C++中崇尚先定義后使用,所以一般是先聲明一個變量并明確指定變量類型,諸如:
int i = 1; // C其中int 用于聲明了具體的變量類型,而Python中則要簡潔得多,由于動態(tài)類型的特性,所以在創(chuàng)建變量時無需指定變量類型,直接簡單粗暴地直接聲明變量即可:
i = 1 # Python當(dāng)然,隨著Python的不斷發(fā)展,變量聲明也開始支持明確變量類型,例如:
i:int = 1 # python雖然上述語句中明確了變量i是int類型數(shù)據(jù),但實際上其效果與C/C++中全然不同:Python的變量類型聲明在變量名之后,僅用于提示使用者該變量的預(yù)期類型,且聲明是這個類型后續(xù)也可能改變(畢竟Python的特性之一是動態(tài)類型);而C/C++中的變量類型聲明在變量名之前,是一種真正的明確和指定變量類型,且一旦指定則后續(xù)不可變更。
在簡單對比完了Python和C/C++變量類型定義之后,則可引出本文的主角:Scala變量類型定義。Scala變量類型定義,個人理解來看可以看做是綜合了C與Python變量類型二者的特點:既追求變量定義的簡潔性(無需具體制定變量類型),又帶有一定的類型聲明的味道。所以,Scala變量定義由三部分組成:
必須冠以val/var作為關(guān)鍵字開頭
隨后承接變量名
在變量名之后支持聲明或缺省變量類型
例如:
val i = 1 // scala變量類型方式一var j:Int = 1 // scala變量類型方式二
在上述兩種變量定義方式中,變量i是一個val類型,未顯示指定變量類型,所以交由scala解釋器自動推斷,此處可推斷為Int類型;變量j是一個var類型,顯示指定變量類型為Int。值得注意的是Scala中的類型關(guān)鍵字均為大寫開頭的單詞,例如整型寫作Int。
變量類型推斷是Scala語言的一大特色,在前篇入門介紹文章中也給予解釋,后續(xù)也將多次提及,此處暫且略過。重點解讀val和var的含義:
val:即value,用以表達在程序中無需再次賦值的變量,某種程度上象征著該變量因不可變而更為安全——當(dāng)然,這話不絕對,后續(xù)將隨時發(fā)現(xiàn)明明是val定義的變量卻可以各種“改變”的打臉案例,注意這里的改變是引號下的改變;
var:即variable,真正的變量,可能在程序中多次發(fā)生變化或者說再次賦值,所以用變量。
需要指出的是,scala中變量類型要么是val要么是var,且雖然val是不可變類型,但也仍然屬于變量——這看似矛盾,實則需要辯證的看待此問題:一方面,變量本身就是一個廣義的術(shù)語,在這段代碼中不可變變量a表示的是一個整數(shù),而在另一段代碼中變量a可能表達的卻可能是一個字符串,所以當(dāng)然是變量;另一方面,前面也提及val定義的變量也可能發(fā)生改變,例如val定義的一個數(shù)組b,雖然由于val的限制b是不可變的數(shù)組,數(shù)組就還是這個數(shù)組,但數(shù)組里的每個元素其實可以隨時改變,所以其實仍然是一個變量!
那為什么直覺上val會給我們一種"變量不可變"的感覺呢?其實多半是潛意識里將其與常量發(fā)生了混淆:常量才是真真正正的不會發(fā)生改變,例如圓周率π,它不會因為在這段代碼或者那段代碼中而存在不同(至少目前來看是確切的常數(shù))。
辯證理解了scala中大費周章的搞出了val/var兩類變量聲明的含義,那么自然會存在疑惑:這么做的目的是什么呢?這是一個好問題,甚至個人認(rèn)為某種程度上可以管窺一豹的了解Scala的價值觀:
val聲明的變量相較var類型而言,更利于內(nèi)存回收,所以應(yīng)盡可能使用val類型
val變量跟Scala函數(shù)式編程思想一脈相承,即盡量保證函數(shù)不帶來副作用(包括不改變函數(shù)內(nèi)部的變量本身),而多用輸入輸出來模塊化封裝
這一部分圍繞Scala中的變量定義做以介紹,雖然有些冗長,但個人覺得深入理解Scala的變量定義價值觀還是很有必要的。尤其是理解val/var的哲學(xué)理念將伴隨Scala整個學(xué)習(xí)周期……,
前文提到,在Scala變量定義中,支持顯示聲明或缺省變量類型,當(dāng)缺省時則交由解釋器自動推斷。其實這里暗含了一個細節(jié),即與Python中的動態(tài)語言特性不同,Scala中的變量是有明確數(shù)據(jù)類型的!那么,這就自然引出第二個話題,Scala中支持哪些變量類型呢?對此,個人援引Scala變量類型的一張經(jīng)典圖例:
上圖中,從上到下(實線箭頭的反方向)是父類和子類的關(guān)系,即數(shù)據(jù)類型不斷細化;而左右區(qū)分來看,左半部分是值類型(value),例如數(shù)值型、字符型和布爾型,右半部分是引用類型(reference),例如數(shù)組、列表、集合等。兩半部分相當(dāng)于分布源自于一個父類型:AnyVal和AnyRef,而AnyVal和AnyRef則又均源自于共同的父類Any類型。
除了實箭頭之外,還標(biāo)識了一些虛線箭頭,這是表達了允許隱式轉(zhuǎn)換的數(shù)據(jù)類型,當(dāng)然這里的隱式轉(zhuǎn)換肯定是以不丟失精度為前提——丟失精度的轉(zhuǎn)換肯定是強制轉(zhuǎn)換!例如,Byte是單字節(jié)整數(shù),由8bit組成,自然能表達的數(shù)值區(qū)間要小于Short類型(16bit),依此類推。
還需注意的一個類型是Char類型。如果熟悉Python的話,那么肯定知道Char(單字符)和String(多字符,即字符串)其實都是屬于字符串的一種,而且表達方式也都通用(Python中支持單引號、雙引號、三單引號、三雙引號四種表達Char和String的方式),而在Scala中二者則截然不同:String類型是一個字符串,用雙引號表示,即便雙引號之內(nèi)僅有單個字符時也是一個String類型;而Char類型僅能是單個字符,用單引號表示,例如一個Char變量c='C'除了字面量表示一個字母之外,其實還隱式的對應(yīng)整數(shù)67(大寫字母C的ascii值),所以下面的語句也就不例外:
// Scalascala> val c = 'C'val c: Char = Cscala> c + 1val res1: Int = 68scala> val d = "C"val d: String = Cscala> d + 1 # 這里是將數(shù)值1隱式轉(zhuǎn)換為了字符串"1",而后完成字符串拼接val res2: String = C1
最后,值得補充的是,Scala中所有類型的頂級父類(超類)是Any,而所有類型的子類是Nothing。不針對二者的具體含義展開過多理解,單論二者命名本身還是很具象的:能容納所有數(shù)據(jù)類型的類型不就是Any嗎?而所有類型的公共交集,則自然是Nothing,因為不存在一種數(shù)據(jù)既是字符串、又是數(shù)值,同時還是布爾類型等等。

相較于Python中的變量蛇形命名,個人更喜歡駝峰命名的緊湊
除了變量命名的書寫規(guī)范,變量命名的組成也與其他語言存在一定不同,例如其他編程語言一般是要求字母、數(shù)字和下劃線組成(不能由數(shù)字開頭),而Scala則要相對更加開放,主要遵循以下幾條原則:
首字符為字母,后續(xù)字符任意字母和數(shù)字,美元符號,可后接下劃線_
數(shù)字不可以開頭。
首字符為操作符(比如+ - * / ),后續(xù)字符也需跟操作符 ,至少一個
操作符(比如+-*/)不能在標(biāo)識符中間和最后.
用反引號`....`包括的任意字符串,即使是關(guān)鍵字(39個)也可以 [true]
簡單畫下重點:scala中除了字母、數(shù)字和下劃線之外,操作符和美元符也可利用;系統(tǒng)關(guān)鍵字也可以通過加反引號``來用作變量標(biāo)識符。
例如:
scala> val * = 3 // 操作符當(dāng)做變量名val *: Int = 3scala> * * * // 試著接收一下,這幾個*都是什么意思val res3: Int = 9scala>?val?`Int`?=?2??//?系統(tǒng)關(guān)鍵字加反引號后當(dāng)做變量名val?Int:?Int?=?2scala> `Int`val res5: Int = 2
不過,一個好的變量命名對于自己和他人閱讀代碼都是至關(guān)重要的,既然英文單詞那么廣泛(漢語拼音也提供了無限可能……),誰會無聊到用可能會產(chǎn)生解釋報錯和理解歧義的美元符、操作符乃至系統(tǒng)關(guān)鍵字來命名變量呢?

相關(guān)閱讀:
