1 快速入門... 4java
1.1 分號... 4es6
1.2 常變量聲明... 4編程
1.2.1 val常量... 4併發
1.2.2 var變量... 4分佈式
1.2.3 類型推導... 5函數式編程
1.2.4 函數編程風格... 5函數
1.3 Range. 5工具
1.4 定義函數... 6post
1.5 while、if6性能
1.6 foreach、for. 7
1.7 讀取文件... 7
1.8 類、字段和方法... 7
1.9 Singleton單例對象... 9
1.10 Scala入口程序... 10
1.11 基本類型... 11
1.12 字面量... 12
1.12.1 整數字面量... 12
1.12.2 浮點數字面量... 12
1.12.3 字符字面量... 13
1.12.4 字符串字面量... 13
1.12.5 Symbol符號字面量... 14
1.12.6 布爾字面量... 15
1.13 操做符和方法... 15
1.14 數學運算... 16
1.15 關係和邏輯操做... 17
1.16 位操做符... 17
1.17 對象相等性... 18
1.18 操做符優先級... 19
1.19 基本類型富包裝類型... 19
分號表示語句的結束;
若是一行只有一條語句時,能夠省略,多條時,須要分隔
通常一行結束時,表示表達式結束,除非推斷該表達式未結束:
// 末尾的等號代表下一行還有未結束的代碼.
def equalsign(s: String) =
println("equalsign: " + s)
// 末尾的花括號代表下一行還有未結束的代碼.
def equalsign2(s: String) = {
println("equalsign2: " + s)
}
//末尾的逗號、句號和操做符均可以代表,下一行還有未結束的代碼.
def commas(s1: String,
s2: String) = Console.
println("comma: " + s1 +
", " + s2)
多個表達式在同一行時,須要使用分號分隔
定義的引用不可變,不能再指向別的對象,至關於Java中的final
Scala中一切皆對象,因此,定義一切都是引用(包括定義的基本類型變量,實質上是對象)
val定義的引用不可變,指不能再指向其餘變量,但指向的內容是能夠變的:
val定義的常量必需要初始化
val的特性能併發或分佈式編程頗有好處
定義的引用能夠再次改變(內容就更能夠修改了),但定義時也須要初始化
在Java中有原生類型(基礎類型),即char、byte、short、int、long、float、double和boolean,這些都有相應的Scala類型(沒有基本類型,但比如Java中相應的包裝類型),Scala編譯成字節碼時將這些類型儘量地轉爲Java中的原生類型,使你能夠獲得原生類型的運行效率
用val和var聲明變量時必須初始化,但這兩個關鍵字都可以用在構造函數的參數中,這時變量是該類的一個屬性,所以顯然沒必要在聲明時進行初始化。此時若是用val聲明,該屬性是不可變的;若是用var聲明,則該屬性是可變的:
class Person(val name: String, var age: Int)
即姓名不可變,但年齡是變化的
val p = new Person("Dean Wampler", 29)
var和val關鍵字只標識引用自己是否能夠指向另外一個不一樣的對象,它們並未代表其所引用的對象內容是否可變
定義時能夠省略類型,會根據值來推導出類型
scala> var str = "hello"
str: String = hello
scala> var int = 1
int: Int = 1
定義時也可明確指定類型:
scala> var str2:String = "2"
str2: String = 2
之前傳統Java都是指令式編程風格,若是代碼根本就沒有var,即僅含有val,那它或許是函數式編程風格,所以向函數式風格轉變的方式之一,多使用val,嘗試不用任何var編程
指令式編程風格:
def printArgs(args: Array[String]): Unit = {
var i = 0
while (i < args.length) {
println(args(i))
i += 1
}
}
函數式編程風格:
def printArgs(args: Array[String]): Unit = {
for (arg <- args)
println(arg)
}
或者:
def printArgs(args: Array[String]): Unit = {
//若是函數字面量只有一行語句而且只帶一個參數,
//則麼甚至連指代參數都不須要
args.foreach(println)
}
數據範圍、序列
支持Range的類型包括Int、Long、Float、Double、Char、BigInt和BigDecimal
Range能夠包含區間上限,也能夠不包含區間上限;步長默認爲1,也能夠指定一個非1的步長:
函數是一種具備返回值(包括空Unit類型)的方法
函數體中最後一條語句即爲返回值。若是函數會根據不一樣狀況返回不一樣類型值時,函數的返回類型將是不一樣值的通用(父)類型,或者是能夠相互轉換的類型(如Char->Int)
若是函數體只一條語句,能夠省略花括號:
def max2(x:Int,y:Int)=if(x>y)x else y
scala> max2(3,5)
res2: Int = 5
Unit:返回類型爲空,即Java中的void類型。若是函數返回爲空,則能夠省略
scala> def greet()=println("Hello")
greet: ()Unit
打印入口程序的外部傳入的參數:
object Test { def main(args: Array[String]): Unit = { var i = 0 while (i < args.length) { if (i != 0) print(" ") print(args(i)) i += 1 } } }
注:Java有++i及i++,但Scala中沒有。
與Java同樣,while或if後面的布爾表達式必須放在括號裏,不能寫成諸如 if i < 10 的形式
object Test { def main(args: Array[String]): Unit = { var i = 0; args.foreach(arg => { if (i != 0) print(" "); print(arg); i += 1 }) } }
foreach方法參數要求傳的是函數字面量(匿名函數),arg爲函數字面量的參數,而且值爲遍歷出來的集合中的每一個元素,類型爲String,已省略,如不省略,則應爲:
args.foreach((arg: String) => { if (i != 0) print(" "); print(arg); i += 1 })
若是函數字面量只有一行語句而且只帶一個參數,則麼甚至連指代參數都不須要:
args.foreach(println)
也可使用for循環來代替:
for (arg <- args) println(arg)
object Test {
def main(args: Array[String]): Unit = {
import scala.io.Source
//將文件中全部行讀取到List列表中
val lines = Source.fromFile(args(0)).getLines().toList
//找到最長的行:相似冒泡排序,每次拿兩個元素進行比較
val longestLine = lines.reduceLeft((a, b) => if (a.length() > b.length()) a else b)
//最長行的長度自己的寬度
val maxWidth = widthOfLength(longestLine)
for (line <- lines) {
val numSpaces = maxWidth - widthOfLength(line)
//讓輸出的每行寬度右對齊
val padding = " " * numSpaces
println(padding + line.length() + " | " + line)
}
}
def widthOfLength(s: String) = s.length().toString().length()
}
類的方法以 def 定義開始,要注意的 Scala 的方法的參數都是 val 類型,而不是 var 類型,所以在函數體內不能夠修改參數的值,好比若是你修改 add 方法以下:
使用class定義類:
class ChecksumAccumulator {}
而後就可使用 new 來實例化:
val cal = new ChecksumAccumulator
類裏面能夠放置字段和方法,這都稱爲成員(member)。
字段,不論是使用val仍是var,都是指向對象的變量(即Java中的引用)
方法,使用def進行定義
class ChecksumAccumulator {
var sum = 0
}
object Test {
def main(args: Array[String]): Unit = {
val acc = new ChecksumAccumulator
val csa = new ChecksumAccumulator
}
}
剛實例化時內存的狀態以下:
注:在Scala中,因爲數字類型(整型與小數)都是final類型的,即不可變,因此在內存中若是是同一數據,則是共享的
因爲上面是使用var進行定義的字段,而不是val,因此能夠從新賦值:
acc.sum = 3
如今內存狀態以下:
因爲修改了acc中sum的內容,因此acc.sum指向了3所在的內存。
對象的穩定型就是要保證對象狀態的穩定型,即對象中字段值在對象整個生命週期中持續有效。這須要將字段設爲private以阻止外界直接對它進行訪問與修改,由於私有字段只能被同一類裏的方法訪問,因此更新字段的代碼將被鎖定在類裏:
class ChecksumAccumulator {
private var sum = 0
def add(b: Byte): Unit = {
sum += b
}
}
與Java 不一樣的,Scala 的缺省修飾符爲 public,也就是若是不帶有訪問範圍的修飾符 public,protected,private,Scala 缺省定義爲 public
類的方法以 def 定義開始,要注意的 Scala 的方法的參數都是 val 類型,而不是 var 類型,所以在函數體內不能夠修改參數的值,好比若是你修改 add 方法以下:
def add(b: Byte): Unit = {
b = 1 // 編譯出錯,由於b是val :error: reassignment to val
sum += b
}
若是某個方法的方法體只有一條語句,則能夠去掉花括號:
def add(b: Byte): Unit = sum += b
類的方法分兩種,一種是有返回值的,一種是不含返回值
若是方法沒有返回值(或爲Unit),則定義方法時能夠去掉結果類型和等號 =,並把方法體放在花括號裏:
def add(b: Byte) { sum += b }
定義方法時,若是去掉方法體前面的等號 =,則方法的結果類型就必定是Unit,這無論方法體最後語句是啥,由於編譯器能夠把任何類型轉換爲Unit,如結果是String,但返回結果類型聲明爲Unit,那麼String將被轉換爲Unit並丟棄原值。下面是明肯定義返回類型爲Unit:
scala> def f(): Unit = "this String gets lost"
f: ()Unit
去掉等號的方法返回值類型也必定是Unit:
scala> def g() { "this String gets lost too" }
g: ()Unit
加上等號時,若是沒有肯定定義返回類型,則會根據方法體最後語句來推導:
scala> def h() = { "this String gets returned!" }
h: ()java.lang.String
scala> h
res0: java.lang.String = this String gets returned!
Scala 代碼無需使用「;」結尾,也不須要使用 return返回值,函數的最後一行的值就做爲函數的返回值
Scala中不能定義靜態成員,而是以定義成單例對象(singleton object)來代替,即定義類時,使用的object關鍵字,而非class關鍵字,但看上去就像定義class同樣:
class ChecksumAccumulator {
private var sum = 0
def add(b: Byte): Unit = {
sum += b
}
def checksum(): Int = {
return ~(sum & 0xFF) + 1
}
}
import scala.collection.mutable.Map
object ChecksumAccumulator {
private val cache = Map[String, Int]()
def calculate(s: String): Int =
if (cache.contains(s))
cache(s)
else {
val acc = new ChecksumAccumulator
for (c <- s)
acc.add(c.toByte)
val cs = acc.checksum()
cache += (s -> cs)
cs
}
}
當單例對象(object)與某個類(class)的名稱相同時(上面都爲ChecksumAccumulator),它就被稱爲是這個類的伴生對象。類和它的伴生對象必須定義在一個源文件裏。類被稱爲這個單例對象的伴生類。類和它的伴生對象能夠互相訪問其私有成員
能夠將單例對象看成Java中的靜態方法工具類來使用,能夠直接經過單例對象的名稱來調用:
ChecksumAccumulator.calculate("Every value is an object.")
其實,單例對象就是一個對象,不須要實例化就能夠直接經過單例對象名來訪問其成員,即單例對象名就至關於變量名,已指向了某個類的實例,只不過該類不是由你來實例化,而是在訪問它時由Scala實例化出來的,且在JVM只有一個這個的實例。在編譯伴生對象時,會生成一個相應名爲ChecksumAccumulator$(在單例對象名後加上美圓符號)的類:
類和單例對象的差異:單例對象定義時不帶參數(Object關鍵字後面),而類能夠(Class關鍵字後面能夠帶括號將類參數包圍起來),由於單例對象不是使用new關鍵字實例化出來的(這也是 Singleton 名字的由來)
單例對象在第一次被訪問的時候纔會被始化
沒有伴生類的單例對象被稱爲獨立對象,通常做爲相關功能方法的工具類,或者用做Scala應用的入口程序
在Java中,只要類中有以下簽名的main方法,便可做爲程序入口程序:
class T {
public static void main(String[] args) {}
}
在Scala中,入口程序不是定義在類class中的,而是定義在單例對象中的,
object T {
def main(args: Array[String]): Unit = {}
}
與Java 相似,Scala 中任何 Singleton對象(使用Object關鍵字定義),若是包含 main 方法,均可以做爲應用的入口
Scala的每一個源文件都會自動引入包java.lang和scala包中的成員,和scala包中名爲Predef的單例對象的成員,該單例對象中包含了許多有用的方法,例如,當在Scala源文件中寫pringln的時候,實際調用了Predef.println,另外當你寫assert,實質上是調用Predef.assert
Java的源文件擴展名爲.java,而Scala的源文件擴展名爲.scala
在Java中,若是源文件中有public的class,則該public類的類名必須與Java源文件名一致,但在Scala中沒有這種限制,但通常會將源文件名與類名設爲一致
與Java同樣,也有對應的編譯與運行命令,它們分別是scalac(編譯)與scala(運行),ava中的爲javac、java,不論是Java仍是Scala程序,都會編譯成.class的字節碼文件
Scala 爲 Singleton 對象的 main 定義了一個 App trait 類型
Scala的入口程序還能夠繼承scala.App特質(Trait,Scala中的Trait像Java中的Interface,但不一樣的是能夠有方法的實現),這樣就不用寫main方法(由於scala.App特質裏實現了main方法),而直接將代碼寫在花括號裏,花括號裏的代碼會被收集進單例對象的主構造器中,並在類被初始化時執行:
object T extends scala.App {
println("T")
}
缺點:命令參數行args不能再被訪問;某些JVM線程會要求main方法不能經過繼承獲得,必須本身行編寫;
Java 支持的基本數據類型,Scala 都有對應的支持,不過 Scala 的數據類型都是對象,並且這些基本類型均可以經過隱式自動轉換的形式支持比 Java 基本數據類型更多的方法:好比調用 (-1).abs() ,Scala 發現基本類型 Int 沒有提供 abs()方法,但能夠發現系統提供了從 Int 類型轉換爲 RichInt 的隱式自動轉換,而 RichInt 具備 abs 方法,那麼 Scala 就自動將 1 轉換爲 RichInt 類型,而後調用 RichInt 的 abs 方法。
Scala 的基本數據類型有: Byte,Short,Int,Long 和 Char (這些成爲整數類型)。整數類型加上 Float 和 Double 成爲數值類型。此外還有 String 類型,除 String 類型在 java.lang 包中定義,其它的類型都定義在包 scala 中。好比 Int 的全名爲 scala.Int。實際上 Scala 運行環境自動會載入包 scala 和 java.lang 中定義的數據類型,你可使用直接使用 Int,Short,String 而無需再引入包或是使用全稱(如scala.xx與java.lang.xx)。
Scala的基本類型與Java對應類型範圍徹底同樣,這樣可讓Scala編譯器直接把這些類型編譯成Java中的原始類型
scala> var hex=0xa //十六進制,整數默認就是Int類型
hex: Int = 10
scala> var hex:Short=0x00ff //若要Short類型,則要明確指定變量類型
hex: Short = 255
scala> var hex=0xaL //賦值時明確指定數據爲Long型,不然默認爲Int類型
hex: Long = 10
scala> val prog=2147483648L //若超過了Int範圍,則後面必定要加上 L ,置換爲Long類型
prog: Long = 2147483648
scala> val bt:Byte= 38 //若要Byte類型,則要在定義時明確指定變量的類型爲Byte類型
bt: Byte = 38
scala> val big=1.23232 //小數默認就是Double類型
big: Double = 1.23232
scala> val big=1.23232f //若是要定義成Float,則可直接在小數後面加上F
big: Float = 1.23232
scala> val big=1.23232D //雖然默認就是Double,但也可在小數後面加上D
big: Double = 1.23232
scala> val a='A' //類型推導成Char類型
a: Char = A
scala> val f ='\u0041' //也可使用Unicode編碼表示,以 \u 開頭,u必定要小寫,且\u後面接4位十六進制
f: Char = A
scala> val hello="hello" //類型推導成String類型
hello: String = hello
scala> val longString=""" Welcome to Ultamix 3000. Type "Help" for help.""" //以使用三個引號(""")開頭和結尾,這樣之間的字符都將看做是最原始的字符,不會被轉義
longString: String = " Welcome to Ultamix 3000. Type "Help" for help." //注:開頭與結尾的雙引號不屬於上面字符串的一部分,而是表示控制檯上輸出的是String
scala> val bool=true
bool: Boolean = true
字面量就是直接寫在代碼裏的常量值
十六進制以 0x或0X開頭:
scala> val hex = 0x5
hex: Int = 5
scala> val hex2 = 0x00FF
hex2: Int = 255
scala> val magic = 0xcafebabe
magic: Int = -889275714
注:不區分大小寫
八進制以0開頭
scala> val oct = 035 // (八進制35是十進制29)
oct: Int = 29
scala> val nov = 0777
nov: Int = 511
scala> val dec = 0321
dec: Int = 209
若是是非0開頭,即十進制:
scala> val dec2 = 255
dec2: Int = 255
注:無論字面量是幾進制,輸出時都會轉換爲十進制
若是整數以L或l結尾,就是Long類型,不然默認就是Int類型:
scala> val prog = 0XCAFEBABEL
prog: Long = 3405691582
scala> val tower = 35L
tower: Long = 35
scala> val of = 31l
of: Long = 31
從上面能夠看出,定義Int型時能夠省去類型便可,若是是Long類型,定義時也可省略Long類型,此時在數字後面加上L或l便可,但也能夠直接定義成Long也可:
scala> var lg:Long = 2
lg: Long = 2
若是要獲得Byte或Short類型的變量時,需在定義時指定變量的相應類型:
scala> val little: Short = 367
little: Short = 367
scala> val littler: Byte = 38
littler: Byte = 38
scala> val big = 1.2345
big: Double = 1.2345
scala> val bigger = 1.2345e1
bigger: Double = 12.345
scala> val biggerStill = 123E45
biggerStill: Double = 1.23E47
小數默認就是Double類型,若是要是Float,則要以F結尾:
scala> val little = 1.2345F
little: Float = 1.2345
scala> val littleBigger = 3e5f
littleBigger: Float = 300000.0
固然Double類型也能夠D結尾,不過是可選的
scala> val anotherDouble = 3e5
anotherDouble: Double = 300000.0
scala> val yetAnother = 3e5D
yetAnother: Double = 300000.0
固然,也要以在定義變量時明確指定類型也可:
scala> var f2 = 1.0
f2: Double = 1.0
scala> var f2:Float = 1
f2: Float = 1.0
使用單引號引發的單個字符
scala> val a = 'A'
a: Char = A
單引號之間除了直接是字符外,也能夠是對應編碼,編碼是八進制或十六進制來表示
若是以八進制表示,則以 \ 開頭,且爲 '\0 到 '\377' (0377=255):
scala> val c = '\101'
c: Char = A
注:若是以八進制表示,則只能表示一個字節大小的字符,即0~255之間的ASCII碼單字節字符,若是要表示大於255的Unicode字符,則只能使用十六進制來表示:
scala> val d = '\u0041'
d: Char = A scala>
val f = '\u0044'
f: Char = D
scala> val c = '\u6c5f'
c: Char = 江
注:以十六進制表示時,需以 \u(小寫)開頭,即後面跟4位十六進制的編碼(兩個字節)
實際上,十六進制能夠出如今Scala程序的任何地方,如能夠用在變量名裏:
scala> val B\u0041\u0044 = 1
BAD: Int = 1
轉義字符:
scala> val backslash = '\\'
backslash: Char = \
使用雙引號引發來的0個或多個字符
scala> val hello = "hello"
hello: java.lang.String = hello
特殊字符也需轉義:
scala> val escapes = "\\\"\'"
escapes: java.lang.String = \"'
若是字符串中須要轉義的字符不少時,可使用三個引號(""")開頭和結尾,這樣之間的字符都將看做是最原始的字符,不會被轉義(固然三個連續的引號除外):
發現第二行前面的空格也會原樣輸出來,因此第二行前面看起來縮進了,若是要去掉每行前面的空白字符(ASCII編碼小於等於32的都會去掉),則把管道符號(|)放在每行前面,而後對字符串調用stripMargin:
以單引號打頭,後面跟一個或多個數字、字母或下劃線,但第一個字符不能是數字,這種字面量會轉換成預約義類scala.Symbol的實例,如 'cymbal編譯器將會調用工廠方法Symbol("cymbal")轉化成Symbol實例。
scala> val s = 'aSymbol
s: Symbol = 'aSymbol
scala> s.name
res20: String = aSymbol
符號字面量 'x 是表達式 scala.Symbol("x") 的簡寫
Java中String的intern()方法:String類內部維護一個字符串池(strings pool),當調用String的intern()方法時,若是字符串池中已經存在該字符串,則直接返回池中字符串引用,若是不存在,則將該字符串添加到池中,並返回該字符串對象的引用。執行過intern()方法的字符串,咱們就說這個字符串被拘禁了(interned),即放入了池子。默認狀況下,代碼中的字符串字面量和字符串常量值都是被拘禁的,例如:
String s1 = "abc";
String s2 = new String("abc");
System.out.println(s1 == s2);//false
System.out.println(s1 == s2.intern());//true
同值字符串的intern()方法返回的引用都相同,例如:
String s2 = new String("abc");
String s3 = new String("abc");
System.out.println(s2 == s3);// false
System.out.println(s2.intern() == s3.intern());// true
String str1 = "abc";
String str2 = "abc";
System.out.println(str1 == str2);//true
String str3 = new String("abc");
System.out.println(str1 == str3);//false
Sysmbol實質上也是一種字符串,其好好處:
1. 節省內存
在Scala中,Symbol類型的對象是被拘禁的(interned,即會被放入池中),任意的同名symbols都指向同一個Symbol對象,避免了因冗餘而形成的內存開銷:
val s1 = "aSymbol"
val s2 = "aSymbol"
println(s1.eq(s2)) //true :代表s1與s2指向同一對象
val s3 = new String("aSymbol") //因爲在編譯時就肯定,因此仍是會放入常量池
println(s1.eq(s3)) //false : 代表s1與s3不是同一對象
println(s1 == s3) //true:雖然不是同一對象,可是它們的內容相同
val s = 'aSymbol
println(s.eq('aSymbol)) //true
println(s.eq(Symbol("aSymbol"))) //true:只要是同名的Symbol,則都是指向同一對象
//即便s與s3的內容相同,但eq比較的是對象地址,因此不等
println(s.name.eq(s3)) //false
println(s.name == s3) //true:但內容相同
println(s.name.eq(s1)) //true : s與s1的的內容都會放入池中,因此指向的是同一對象
注:在Scala中,若是要基於引用地址進行比較,則要使用eq方法,而不是==,這與Java是不同的
2. 快速比較
因爲Symbol類型的對象會自動拘禁的(interned),任意的同名symbols(準確的說是值)都指向同一個Symbol對象,而相同值的字符串並不必定是同一個instance,因此symbols對象之間的比較操做符==速度會很快:由於它只基於地址進行比較,若是發現不是同一Symbols對象,則就認爲不相同,不會在對內容進行比較(由於不一樣名的Symbols的值確定也不相同)
Symbol類型的應用
Symbol類型通常用於快速比較,例如用於Map類型:Map<Symbol, Data>,根據一個Symbol對象,能夠快速查詢相應的Data, 而Map<String, Data>的查詢效率則低不少。
雖然說利用String的intern方法也能夠實現Map<String, Data>的鍵值快速比較,可是因爲須要顯式地調用intern()方法,在編碼時會形成不少的麻煩,並且若是忘了調用intern()方法,還會形成難以尋找的bug。從這個角度看,Scala的Symbol類型會自動進行intern操做(加入到池中),因此簡化了編碼的複雜度;而Java中除了字符串常量,是不會自動進行intern的,須要對相應對象手動調用interned方法
scala> val bool = true
bool: Boolean = true
scala> val fool = false
fool: Boolean = false
操做符如+加號,實質上是類型中有名爲 + 的方法:
scala> val sum = 1 + 2 // Scala調用了(1).+(2)
sum: Int = 3
scala> val sumMore = (1).+(2)
sumMore: Int = 3
實際上Int類型包含了名爲 + 的各類不一樣參數的重載方法,如Int+Long:
scala> val longSum = 1 + 2L // Scala調用了(1).+(2L)
longSum: Long = 3
既然+是名爲加號的方法,能夠以 1+2 這種操做模式來調用,那麼其餘方法也是能夠以下方式來調用:
scala> val s = "Hello, world!"
s: java.lang.String = Hello, world!
scala> s indexOf 'o' // 調用了s.indexOf(’o’)
res0: Int = 4
若是有多個參數,則要使用括號:
scala> s indexOf ('o', 5) // Scala調用了s.indexOf(’o’, 5)
res1: Int = 8
在 Scala 中任何方法均可以是操做符:Scala裏的操做符不是特殊的語法,任何方法均可以是操做符,究竟是方法仍是操做符取決於你如何使用它。若是寫成s.indexOf('o'),indexOf就不是操做符。不過若是寫成,s indexOf 'o',那麼indexOf就是操做符了
上面看到的是中綴操做符,仍是前綴操做符,如 -7裏的「-」,後綴操做符如 7 toLong裏的「toLong」( 實爲 7.toLong )。
前綴操做符與後綴操做都只有一個操做數,是一元(unary)操做符,如-2.0、!found、~0xFF,這些操做符對應的方法是在操做符前加上前綴「unary_」:
scala> -2.0 // Scala調用了(2.0).unary_-
res2: Double = -2.0
scala> (2.0).unary_-
res3: Double = -2.0
能夠看成前綴操做符用的標識符只有+、-、!和~。所以,若是你定義了名爲unary_!的方法,就能夠對值或變量用 !P 這樣的前綴操做符方式調用方法。可是若是你定義了名爲unary_*的方法,就沒辦法將其用成前綴操做符了,由於*不是四種能夠看成前綴操做符用的標識符之一。
後綴操做符是不用點與括號調用的不帶任何參數的方法:
scala> val s = "Hello, world!"
s: java.lang.String = Hello, world!
scala> s.toLowerCase()
res4: java.lang.String = hello, world!
因爲不帶參數,則可能省略括號
scala> s.toLowerCase
res5: java.lang.String = hello, world!
還能夠省去點:
scala> import scala.language.postfixOps//須要導一下這個來激活後綴操做符使用方式,不然會警告
import scala.language.postfixOps
scala> s toLowerCase
res6: java.lang.String = hello, world!
scala> 1.2 + 2.3
res6: Double = 3.5
scala> 3 - 1
res7: Int = 2
scala> 'b' - 'a'
res8: Int = 1
scala> 2L * 3L
res9: Long = 6
scala> 11 / 4
res10: Int = 2
scala> 11 % 4
res11: Int = 3
scala> 11.0f / 4.0f
res12: Float = 2.75
scala> 11.0 % 4.0
res13: Double = 3.0
數字類型還提供了一元的前綴 + 和 - 操做符(方法 unary_+ 和 unary_-)
scala> val neg = 1 + -3
neg: Int = -2
scala> val y = +3
y: Int = 3
scala> -neg
res15: Int = 2
scala> 1 > 2
res16: Boolean = false
scala> 1 < 2
res17: Boolean = true
scala> 1.0 <= 1.0
res18: Boolean = true
scala> 3.5f >= 3.6f
res19: Boolean = false
scala> 'a' >= 'A'
res20: Boolean = true
可使用一元操做符!(unary_!方法)改變Boolean值:
scala> val thisIsBoring = !true
thisIsBoring: Boolean = false
scala> !thisIsBoring
res21: Boolean = true
邏輯與(&&)和邏輯或(||):
scala> val toBe = true
toBe: Boolean = true
scala> val question = toBe || !toBe
question: Boolean = true
scala> val paradox = toBe && !toBe
paradox: Boolean = false
與Java裏同樣,邏輯與和邏輯或有短路:
scala> def salt() = { println("salt"); false }
salt: ()Boolean
scala> def pepper() = { println("pepper"); true }
pepper: ()Boolean
scala> pepper()&& salt()
pepper
salt
res22: Boolean = false
scala> salt()&& pepper()
salt
res23: Boolean = false
scala> pepper() || salt()
pepper
res24: Boolean = true
scala> salt() || pepper()
salt
pepper
res25: Boolean = true
按位與運算(&):都爲1時才爲1,不然爲0
按位或運算(|):只要有一個爲1 就爲1,不然爲0
按位異或運算(^):相同位產生0,不一樣產生1,所以0011 ^ 0101產生0110。
scala> 1 & 2 // 0001 & 0010 = 0000 = 0
res24: Int = 0
scala> 1 | 2 // 0001 | 0010 = 0011 = 3
res25: Int = 3
scala> 1 ˆ 3 // 0001 ^ 0011 = 0010 = 2
res26: Int = 2
scala> ~1 // ~0001 = 1110(負數原碼:從最末一位向前除符號位各位取反便可) = 1010 = -2
res27: Int = -2
負數補碼:反碼+1
左移(<<),右移(>>)和無符號右移(>>>)。左移和無符號右移在移動的時候填入零。右移則在移動時填入左側整數的最高位(符號位)。
scala> -1 >> 31 //1111 1111 1111 1111 1111 1111 1111 1111 向右移動後,仍是 1111 1111 1111 1111 1111 1111 1111 1111,左側用符號位1填充
res38: Int = -1
scala> -1 >>> 31 //1111 1111 1111 1111 1111 1111 1111 1111 向右無符號移動後,爲0000 0000 0000 0000 0000 0000 0000 0001,左側用0填充
es39: Int = 1
scala> 1 << 2 //0000 0000 0000 0000 0000 0000 0000 0001 向左位移後,爲0000 0000 0000 0000 0000 0000 0000 0100,右側用0填充
res40: Int = 4
基本類型比較:
scala> 1 == 2
res31: Boolean = false
scala> 1 != 2
res32: Boolean = true
scala> 2 == 2
res33: Boolean = true
這些操做對全部對象都起做用,而不只僅是基本類型,如列表的比較:
scala> List(1, 2, 3) == List(1, 2, 3)
res34: Boolean = true
scala> List(1, 2, 3) == List(1, 3, 2)
res35: Boolean = false
還能夠對不一樣類型進行比較,如:
scala> 1 == 1.0
res36: Boolean = true
scala> List(1, 2, 3) == "hello"
res37: Boolean = false
甚至能夠與null進行比較:
scala> List(1, 2, 3) == null
res38: Boolean = false
== 操做符在比較以前,會先判斷左側的操做符是否爲null,不爲null時再調用左操做數的equals方法進行比較,比較的結果主要是看這個左操做數的equals方法是怎麼實現的。只要比較的二者內容相同且而且equals方法是基於內容編寫的,不一樣對象之間比較也可能爲true。x == that判斷表達式判斷的過程實質以下:
if (x.eq(null))
that eq null
else
x.equals(that)
equals方法是檢查值是否相等,而eq方法檢查的是引用是否相等。因此若是使用 == 操做符比較兩個對象時,若是左操做數是null,那麼調用eq判斷右操做數也是否爲null;若是左操做數不是null的狀況則調用equals基於對象內容進行比較
!= 就是 == 計算結果取反
Java中的==便可以比較原始類型,也能夠比較引用類型。對於原始類型,Java的==比較值的相等性,與Scala一致,而對於引用類型,Java的==是比較這兩個引用是否都指向了同一個對象,不過Scala比較對象地址不是 == 而是使用eq方法,因此Scala中的 == 操做符做用於對象時,會轉換調用equals方法來比較對象的內容(在左操做數非null狀況下)
AnyRef的equals方法默認調用eq方法實現,也就是說,默認狀況下,判斷兩個變量相等,要求必須指向同一個對象實例
注:在Scala中,若是要基於引用地址進行比較,則要使用eq方法,而不是==,這與Java是不同的
Scala中沒有操做符,操做符只是方法的一種表達方式,其優先級是根據做用符的第一個字符來判斷的(也有例外,請看後面以等號 = 字符結束的一些操做符),如規定第一個字符*就比+的優先級高。以操做符的第一個字符爲依據,優先級以下:
(全部其餘的特殊字符)
* / %
+ -
:
= !
< >
&
^
|
(全部字母)
(全部賦值操做)
上面同一行的字符具備一樣的優先級
scala> 2 << 2 + 2 // 2<< (2 + 2)
res41: Int = 32
<<操做符第一個字符爲<,根據上表 << 要比 + 優先級低
若是操做符以等號字符( =)結束 , 且操做符並不是比較操做符<=,> =, ==,或=,那麼這個操做符的優先級與賦值符( =)相同。也就是說,它比任何其餘操做符的優先級都低。例如:
x *= y + 1
與下面的相同:
x *= (y + 1)
操做符 *= 以 = 結束,被看成賦值操做符,它的優先級低於+,儘管操做符的第一個字符是*看起來高於+。
任何以「:」字符結尾的方法由它的右操做數調用,並傳入左操做數;其餘結尾的方法與之相反,它們被左操做數調用,並傳入右操做:a * b 變成 a.*(b), a:::b 變成 b.:::(a)。
多個同優先級操做符出現時,若是方法以:結尾,它們就被從右往左進行分組;反之,就從左往右進行分組,如:a ::: b ::: c 會被看成 a :::
(b ::: c),而 a * b * c 被看成(a * b) * c
基本類型除了一些常見的算術操做外,還有一些更爲豐富的操做,這些操做能夠直接使用,更多須要參考API中對應的富包裝類:
上述相應的富操做都是由下面相應的富包裝類提供的,使用前會先自動進行隱式轉換: