Scala詳解

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  

 

1       快速入門

1.1               分號

分號表示語句的結束;

若是一行只有一條語句時,能夠省略,多條時,須要分隔

通常一行結束時,表示表達式結束,除非推斷該表達式未結束:

// 末尾的等號代表下一行還有未結束的代碼.

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)

 

多個表達式在同一行時,須要使用分號分隔

 

1.2              常變量聲明

1.2.1      val常量

定義的引用不可變,不能再指向別的對象,至關於Java中的final

 

Scala中一切皆對象,因此,定義一切都是引用(包括定義的基本類型變量,實質上是對象)

val定義的引用不可變,指不能再指向其餘變量,但指向的內容是能夠變的:

val定義的常量必需要初始化

 

val的特性能併發或分佈式編程頗有好處

1.2.2      var變量

定義的引用能夠再次改變(內容就更能夠修改了),但定義時也須要初始化

 

在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關鍵字只標識引用自己是否能夠指向另外一個不一樣的對象,它們並未代表其所引用的對象內容是否可變

1.2.3      類型推導

定義時能夠省略類型,會根據值來推導出類型

scala> var str = "hello"

str: String = hello

scala> var int = 1

int: Int = 1

定義時也可明確指定類型:

scala> var str2:String = "2"

str2: String = 2

1.2.4      函數編程風格

之前傳統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)

  }

 

1.3  Range

數據範圍、序列

支持Range的類型包括Int、Long、Float、Double、Char、BigInt和BigDecimal

 

Range能夠包含區間上限,也能夠不包含區間上限;步長默認爲1,也能夠指定一個非1的步長:

 

1.4               定義函數

函數是一種具備返回值(包括空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

 

1.5               while、if

打印入口程序的外部傳入的參數:

 

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 的形式

1.6               foreach、for

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)

1.7               讀取文件

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()

}

 

1.8               類、字段和方法

類的方法以 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返回值,函數的最後一行的值就做爲函數的返回值

1.9               Singleton單例對象

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應用的入口程序

 

1.10         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方法不能經過繼承獲得,必須本身行編寫;

 

1.11         基本類型

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

1.12         字面量

字面量就是直接寫在代碼裏的常量值

1.12.1              整數字面量

十六進制以 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

1.12.2              浮點數字面量

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

1.12.3              字符字面量

使用單引號引發的單個字符

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 = \

 

1.12.4              字符串字面量

使用雙引號引發來的0個或多個字符

scala> val hello = "hello"

hello: java.lang.String = hello

 

特殊字符也需轉義:

scala> val escapes = "\\\"\'"

escapes: java.lang.String = \"'

 

若是字符串中須要轉義的字符不少時,可使用三個引號(""")開頭和結尾,這樣之間的字符都將看做是最原始的字符,不會被轉義(固然三個連續的引號除外):

 

發現第二行前面的空格也會原樣輸出來,因此第二行前面看起來縮進了,若是要去掉每行前面的空白字符(ASCII編碼小於等於32的都會去掉),則把管道符號(|)放在每行前面,而後對字符串調用stripMargin:

 

1.12.5              Symbol符號字面量

以單引號打頭,後面跟一個或多個數字、字母或下劃線,但第一個字符不能是數字,這種字面量會轉換成預約義類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方法

1.12.6              布爾字面量

scala> val bool = true

bool: Boolean = true

scala> val fool = false

fool: Boolean = false

1.13         操做符和方法

操做符如+加號,實質上是類型中有名爲 + 的方法:

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!

1.14         數學運算

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

1.15         關係和邏輯操做

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.16         位操做符

按位與運算(&):都爲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

1.17         對象相等性

基本類型比較:

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是不同的

1.18         操做符優先級

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

1.19         基本類型富包裝類型

基本類型除了一些常見的算術操做外,還有一些更爲豐富的操做,這些操做能夠直接使用,更多須要參考API中對應的富包裝類:

上述相應的富操做都是由下面相應的富包裝類提供的,使用前會先自動進行隱式轉換:

 

相關文章
相關標籤/搜索