Scala學習筆記(2)


Scala基本語言特性

相比JavaC++等語言,Scala融合了OOPFP等編程范式,同時語法上更靈活

語法基礎(概覽)

  • Scala語言中不強制要求分號,可以依行斷句,只有一行帶有多個語句時才要求分號隔開。
  • 使用varval定義變量常量,類型可以由編譯器推導,也可以顯式指定。定義變量時甚至可以省略varval關鍵字,無關鍵字時定義的變量默認即為val,在定義變量的同時就需要初始化變量,否則報錯(抽象類中除外)。
  • 使用def關鍵字定義方法varval定義函數,需要注意的是使用var定義的函數是可以更改實現的,但def定義的方法一經定義實現就不可改變
  • 沒有自增/自減操作符。
  • 所有類型皆為對象,基礎類型如IntDouble等都是類,函數/方法返回值的空類型為Unit,相當於Java/C++中的void
  • 沒有原生enum類型,應繼承枚舉助手類Enumeration
  • 不提供類似Java/C++中的三目運算符,但if語句表達式帶有返回值,可以實現類似效果。
  • 訪問類成員權限默認為public,因而沒有public關鍵字,但有privateprotected關鍵字,作用與Java大致相同,但支持更細粒度的權限控制。
  • 可以使用操作符作為函數名,達到類似C++/C#中操作符重載的效果。
  • 類的成員變量可以與方法名稱相同

Hello World

創建文件Test.scala,輸入以下代碼:

object Test {
def main(args: Array[String]): Unit //帶有等號的方法可以省略返回值類型由編譯器進行推導
= println("Hello World!")
}

與Java類似,Scala也是從主方法main中開始執行整個程序,不過main方法並不定義在類中,而是定義在單例對象中(使用object關鍵字創建單例對象)。
將主方法寫在class中能夠通過編譯,但生成的字節碼文件在執行時會出錯。
也可以不手動定義main方法而去讓伴生對象繼承App特質,即可直接執行代碼語句,例如:

object Test extends App {
println("Hello World!")
}

單例對象的名稱可以與源碼文件的文件名不同。

方法(Method)

與Java不同,Scala中同時支持函數方法(Java只有方法而沒有真正意義上的”函數”,只有與”函數”類似的”靜態方法”)。
方法由def關鍵字定義,可以被def方法、val函數重寫。一個典型的方法格式如下:

def methodName(args: Type):Type = {
/* function_body */
}

Scala中方法體不需要顯式使用return關鍵字來給出方法返回值,編譯器會將函數體的最后一句代碼推導出類型做為整個函數的返回值。
對於有返回值的方法,必須要在方法定義中加入等號,否則編譯器不會推導返回值。
即使方法的返回值為Unit,只要顯式指定了返回值類型,則必須在方法體中加入等號。
方法和函數的形參不需要不能使用valvar關鍵字聲明,只需寫明類型即可。
在Scala中,方法允許省略參數,空的參數表可以直接省略,如:

def getNum: Int = 100
def getNum(): Int = 100 //以上兩個定義作用相同,但只能存在一個

無參方法與空參方法只能存在一個,但二者在使用方式上略有不同,無參方法在調用時只能直接使用方法名,在方法名后加上括號調用就會出錯;但空參方法既可以使用帶有括號的方法調用方式,也可以省略括號,例如:

scala> def getNum: Int = 100            //定義了方法 getNum: Int
getNum: Int
scala> getNum //正確,返回 100
res0: Int = 100
scala> getNum() //錯誤,提示 error: Int does not take parameters
<console>:12: error: Int does not take parameters
getNum()
^
scala> def getNum(): Int = 200 //定義了方法 getNum(): Int
getNum: ()Int
scala> getNum //正確,返回 200
res1: Int = 200
scala> getNum() //正確,返回 200
res2: Int = 200

同時,無參方法不能與已有字段名稱相同(編譯報錯),而空參方法允許帶有同名的字段。
需要注意的是,在Scala中,賦值表達式的值為Unit,而不是類似Java/C++中的以被賦值的變量類型為表達式的值。例如:

scala> var _num = 0
_num: Int = 0
scala> def testNum(num: Int): Int = _num = num //由編譯器推斷出的返回值類型為Unit
<console>:12: error: type mismatch;
found : Unit
required: Int
def testNum(num: Int): Int = _num = num
^

參數默認值

在Scala中,方法中參數允許帶有默認值

scala> var num = 100
num: Int = 100
scala> def setNum(p: Int = 0) { num = p } //方法的參數不能由默認值進行類型推導,即使給參數寫明了默認值,也依然需要顯式指明類型
setNum: (p: Int)Unit
scala> setNum() //對於有參數的方法,即使參數帶有默認值使得參數表可以為空但在調用時依然不能省略括號,否則報錯
scala> println(num)
0 //輸出0

如果一個方法中包含多個同類型並帶有默認值的參數,調用時默認匹配第一個參數:

scala> def func(num1: Int = 100, num2: Int = 200) = println(s"$num1 $num2")
func: (num1: Int, num2: Int)Unit
scala> func(300)
300 200

具名參數

在Scala中,調用方法時可以在參數表中寫明參數的名稱,該特性被稱為”具名參數”。
對於方法中包含多個同類型並帶有默認值參數的情況下,使用該特性可以顯式指定要傳入的是哪一個參數:

scala> func(num2 = 300)
100 300

與C++不同,Scala中,方法參數的默認值不需要連續,參數的默認值可以交錯出現,甚至是顛倒參數順序:

scala> def func(int: Int, str: String = "String", char: Char, double: Double = 123.0) = println(s"$int $str $char $double")
func: (int: Int, str: String, char: Char, double: Double)Unit
scala> func(100, 'c')
<console>:12: error: not enough arguments for method func: (int: Int, str: String, char: Char, double: Double)Unit.
Unspecified value parameter char.
func(100, 'c')
^
scala> func(int = 100, char = 'c') //對於默認參數不連續的方法,需要使用"具名參數"
100 String c 123.0

默認參數與方法重載

在Scala中,若一個帶有默認的參數的方法省略默認參數時簽名與一個已經存在的方法相同,編譯器並不會報錯(C++編譯器則會報錯),而是在調用方法時優先使用無默認值的版本(處理邏輯類似於C#):

object Main extends App {
def func() = println("No Args")
def func(num: Int = 100) = println(num)
func()
}

輸出結果:
No Args

函數(Function)

在Scala中函數使用var``val關鍵字定義,即函數是一個存儲了函數對象的字段。
一個典型的函數定義如下:

var functionName: FuncType = 符合簽名的方法/函數/Lambda

Scala中的函數類型為Function,根據參數數目的不同,Scala中提供了Function0[+R](無參數)到Function22[-T1, ..., -T22, +R]共23種函數類型,即Scala中的函數,最多可以擁有22個參數。
與方法不同,函數不能夠帶有默認值。
需要注意的是,函數不允許省略參數,因為函數名做為表達式時的語義為函數名所代表的函數內容而非函數調用。空參函數的括號不可省略,直接使用函數名並不代表調用空參函數,比如:

scala> var show100: () => Int = () => 100
show100: () => Int = <function0>
scala> show100 //直接使用函數名得到的是函數對象而非調用函數
res0: () => Int = <function0>
scala> show100()
res1: Int = 100

在Scala中,可以直接使用Lambda創建匿名函數后立即使用:

scala> ((str: String) => println(str))("Hello World!")
Hello World!

C++中的Lambda用法類似:

#include <iostream>

using namespace std;

int main(void)
{
[](const string& str) { cout << str << endl; } ("Hello World!");
return 0;
}

然而在C#中,Lambda需要創建對象或顯式指明類型才能使用,同樣的語句需要寫成:

using System;

class Test
{
static void Main(string[] args)
=>
new Action<string>(str => Console.WriteLine(str))("Hello World!");
//或者寫成 ((Action<string>)(str => Console.WriteLine(str)))("Hello World!");
}

函數組合

在Scala中,函數允許進行組合。
函數組合有兩種方式,a compose b實際調用次序為a(b())a andThen b實際調用次序為b(a())
需要注意的是,方法不能直接進行組合,需要將其轉化為函數(方法名之后加_符號)。

object Main extends App {
def add(num: Int) = num + 100
def double(num: Int) = num * 2

//只有函數能進行組合,方法需要加"_"符號轉化成函數
var compose = add _ compose double
var andThen = add _ andThen double

println(compose(100) == add(double(100)))
println(andThen(100) == double(add(100)))
}

輸出結果:
true
true

傳名參數(By-name Parameter)

當一個方法接收的參數時,該參數即為傳名參數(By-name Parameter),如下所示:

def func(arg: => T) ...

可以使用傳名參數可以接收任意數量的代碼,如下所示:

object Main extends App {

def show(args: => Unit) = args

//單行語句可直接作為參數
show(println("123"))

//多行語句可放在大括號中
show {
println("456")
println("789")
}
}

運行結果:
123
456
789

函數作為參數

Scala為函數式編程語言,在Scala中函數對象可以直接作為參數傳遞。
當函數作為參數存在時,傳名參數與普通的空參函數參數定義不能同時存在,如下定義只能存在一個:

def func(arg: () => T) = arg
def func(arg: => T) = arg
var func: (() => T) => T = (arg: () => T) => arg

在接收參數時,空參函數參數只能接收同樣空參的函數,即() =>不能被省略,而傳名參數則無此限制。

類型系統

在Scala中,所有的類型皆為對象,所有類型都從根類Any繼承,AnyAnyValAnyRef兩個子類。
在Scala中,基礎類型如IntFloatDoubleUnit等全部從AnyVal類中派生,因而可以直接在泛型中直接使用這些類作為類型參數。
同時,Scala中提供了隱式轉換(ImplicitConversion)來保證Int``Float``Double等類型之間可以自動進行轉換
基礎類型與字符串(String)等類型之間的轉換也由類提供的成員函數進行,如將數值與字符串相互轉換可以使用如下代碼:

var str = 100.toString
var num = str.toInt

在Scala中,所有的基礎類型之外的引用類型派生自類AnyRef

底類型(Bottom)

與Java不同,Scala中存在底類型(bottom)。底類型包括NothingNull

  • Nothing是所有類型Any的子類型,定義為final trait Nothing extends Any
  • Nothing特質沒有實例。
  • Null是所有引用類型AnyRef的子類型,定義為final trait Null extends AnyRef
  • Null特質擁有唯一實例null(類似於Java中null的作用)。

可空類型

在Scala中,使用Option[T]表示可空類型,Option[T]包含兩個子類,Some[T]None,分別代表值存在/值不存在。
Option[T]類型使用getOrElse()方法來獲取存在的值或是當值不存在時使用指定的值,如下所示:

scala> var str1: Option[String] = "test"
<console>:10: error: type mismatch; //賦值失敗,Option[T]只能接收Option及其子類
found : String("test")
required: Option[String]
var str1: Option[String] = "test"
^
scala> var str1: Option[String] = Option("test")
str1: Option[String] = Some(test)
scala> var str2: Option[String] = None
str2: Option[String] = None
scala> println(str1 getOrElse "Get Value Failed!")
test
scala> println(str2 getOrElse "Get Value Failed!")
Get Value Failed! //輸出getOrElse()方法中設定的值

可空類型也可以用於模式匹配中,如下代碼所示:

var str = 100.toString
var num = str.toInt

在Scala中,所有的基礎類型之外的引用類型派生自類AnyRef

底類型(Bottom)

與Java不同,Scala中存在底類型(bottom)。底類型包括NothingNull

  • Nothing是所有類型Any的子類型,定義為final trait Nothing extends Any
  • Nothing特質沒有實例。
  • Null是所有引用類型AnyRef的子類型,定義為final trait Null extends AnyRef
  • Null特質擁有唯一實例null(類似於Java中null的作用)。

可空類型

在Scala中,使用Option[T]表示可空類型,Option[T]包含兩個子類,Some[T]None,分別代表值存在/值不存在。
Option[T]類型使用getOrElse()方法來獲取存在的值或是當值不存在時使用指定的值,如下所示:

scala> var str1: Option[String] = "test"
<console>:10: error: type mismatch; //賦值失敗,Option[T]只能接收Option及其子類
found : String("test")
required: Option[String]
var str1: Option[String] = "test"
^
scala> var str1: Option[String] = Option("test")
str1: Option[String] = Some(test)
scala> var str2: Option[String] = None
str2: Option[String] = None
scala> println(str1 getOrElse "Get Value Failed!")
test
scala> println(str2 getOrElse "Get Value Failed!")
Get Value Failed! //輸出getOrElse()方法中設定的值

可空類型也可以用於模式匹配中,如下代碼所示:

object TestOption extends App {
var s = List(Some(123), None)
for (num <- s)
num match {
case Some(x) => println(x)
case None => println("No Value")
}
}

運行結果:
123
No Value


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2021 ITdaan.com