scala是基於java開發的,結合了面向對象編程技術及函數式編程技術。scala的靜態類型避免了複雜程序的大部分BUG。它的JVM和Js運行環境讓你能夠構建高性能系統。html
scala -version
Scala有一個命令行叫作REPL,能夠實現交互式編程。命令行鍵入scala
,進入交互式編程環境。java
$ scala scala> // 默認狀況下每一個鍵入的表達式都將保存爲一個新的類型。 // 注意在Scala中只有對象類型,Int也爲對象。 scala> 2 + 2 res0: Int = 4 // 默認值能夠被重用,注意結果顯示變量的類型。 // res1值類型爲Int,值爲6 scala> res0 + 2 res1: Int = 6 // Scala是一個強類型語言。 // 可使用type指令在不執行表達式狀況下檢查表達式類型。 scala> :type (true, 2.0) (Boolean, Double) // REPL會話能夠保存到本地目錄 scala> :save /sites/repl-test.scala // 可使用load指令加載scala文件 scala> :load /sites/repl-test.scala Loading /sites/repl-test.scala... res2: Int = 4 res3: Int = 6 // 可使用h指令檢查最近的輸入歷史 scala> :h? 1 2 + 2 2 res0 + 2 3 :save /sites/repl-test.scala 4 :load /sites/repl-test.scala 5 :h?
已經學會交互式編程了,如今開始學一點scala。正則表達式
// 這是單行註釋 /* 這是多行註釋 */ // 打印,並換行 println("Hello world!") println(10) // Hello world! // 10 // 打印,不換行 print("Hello world") print(10) // Hello world10 // 變量聲明使用var或者val. // val聲明變量不可變的,var聲明變量可變。 val x = 10 // x是10 x = 20 // 報錯 var y = 10 y = 20 // y如今是20
Scala是一個靜態類型語言,注意到在上述聲明中咱們並無指定類型。這是由於Scala具備類型推斷的功能。大部分狀況下Scala編譯器能夠推斷變量的類型,因此能夠不用每次都顯示聲明變量。如下是顯示聲明變量:編程
val z: Int = 10 val a: Double = 1.0 // 注意表達式自動從Int轉換爲Double, 結果是10.0,而不是10 val b: Double = 10 // Boolean值 true false // Boolean操做 !true // false !false // true true == false // false 10 > 5 // true // 常規操做 1 + 1 // 2 2 - 1 // 1 5 * 3 // 15 6 / 2 // 3 6 / 4 // 1 6.0 / 4 // 1.5 6 / 4.0 // 1.5 "Scala字符串經過雙引號括起來"//String 'a' // a 是一個Scala 字符 // '單引號字符串並不存在 // Strings對象有經常使用的Java字符串對象的方法。 "hello world".length "hello world".substring(2, 6) "hello world".replace("C", "3") // Strings對象有額外的Scala方法. "hello world".take(5) "hello world".drop(5) // 支持字符串插值,注意前綴"s" val n = 45 s"We have $n apples" // => "We have 45 apples" // 插值字符串支持表達式 val a = Array(11, 9, 6) s"My second daughter is ${a(0) - a(2)} years old." // => "My second daughter is 5 years old." s"We have double the amount of ${n / 2.0} in apples." // => "We have double the amount of 22.5 in apples." s"Power of 2: ${math.pow(2, 2)}" // => "Power of 2: 4" // 使用f前綴實現格式化版本插值字符串 f"Power of 5: ${math.pow(5, 2)}%1.0f" // "Power of 5: 25" f"Square root of 122: ${math.sqrt(122)}%1.4f" // "Square root of 122: 11.0454" // 原生字符串,忽略特殊字符,使用raw前綴 raw"New line feed: \n. Carriage return: \r." // => "New line feed: \n. Carriage return: \r." // 有些字符如"須要使用\轉義 "They stood outside the \"Rose and Crown\"" // => "They stood outside the "Rose and Crown"" // 三個雙引號實現字符串多行編寫 val html = """<form id="daform"> <p>Press belo', Joe</p> <input type="submit"> </form>"""
Functions定義方法:數組
def functionName(args...): ReturnType = { body... }
Scala中經常使用定義沒有return關鍵字.函數體最後一個表達式就是返回值。
def sumOfSquares(x: Int, y: Int): Int = { val x2 = x * x val y2 = y * y x2 + y2 }
若是函數體只有一個表達式,{ }能夠省略.數據結構
def sumOfSquaresShort(x: Int, y: Int): Int = x * x + y * y // 調用函數很簡單 sumOfSquares(3, 4) // => 25
你可使用變量名來指定不一樣傳參順序app
def subtract(x: Int, y: Int): Int = x - y subtract(10, 3) // => 7 subtract(y=10, x=3) // => -7
大部分狀況下能夠略寫函數返回類型,編譯器能夠從最後一個表達式值推斷出來less
def sq(x: Int) = x * x // 編譯器推斷返回值類型爲Int
函數能夠有默認值。dom
def addWithDefault(x: Int, y: Int = 5) = x + y addWithDefault(1, 2) // => 3 addWithDefault(1) // => 6
匿名函數ide
(x: Int) => x * x
不像def聲明, 若是context很清晰,匿名函數的輸入也能夠省略。注意"Int => Int"表明一個函數輸入類型爲Int,返回類型也爲Int。
//sq是一個函數類型變量 val sq: Int => Int = x => x * x // 調用匿名函數 sq(10) // => 100
若是匿名函數的變量僅使用一次,Scala提供一個極簡寫法,使用_表明入參。
val addOne: Int => Int = _ + 1 val weirdSum: (Int, Int) => Int = (_ * 2 + _ * 3) addOne(5) // => 6 weirdSum(2, 4) // => 16
Scala能夠包含return關鍵字,僅在最裏層使用,外層使用def關鍵字。
WARNING: 在Scala中使用return容易出錯,盡力避免這種寫法。 這種寫法對匿名函數一點做用都沒有,例如裏指望foo(7)返回17,實際返回7
def foo(x: Int): Int = { val anonFunc: Int => Int = { z => if (z > 5) return z // 該行讓foo返回z else z + 2 // 該行是anonFunc返回值 } anonFunc(x) + 10 // 該行是foo函數返回值 } foo(7) // => 7
1 to 5 val r = 1 to 5 //foreach循環 r.foreach(println) //合法表達式 r foreach println //合法表達式 (5 to 1 by -1) foreach (println)
一個while循環
var i = 0 while (i < 10) { println("i " + i); i += 1 }
一個do-while循環
i = 0 do { println("i is still less than 10") i += 1 } while (i < 10)
在Scala中定義回調函數是重複一個動做的慣用方法。回調函數須要顯示聲明返回類型,編譯器沒法推斷。
//這裏時Unit, 至關於Java中的void def showNumbersInRange(a: Int, b: Int): Unit = { print(a) if (a < b) showNumbersInRange(a + 1, b) } showNumbersInRange(1, 14)
val x = 10 if (x == 1) println("yeah") if (x == 10) println("yeah") if (x == 11) println("yeah") if (x == 11) println("yeah") else println("nay") println(if (x == 10) "yeah" else "nope") val text = if (x == 10) "yeah" else "nope"
支持的數據類型又數組,字典,集合,鏈表,元組
//數組 val a = Array(1, 2, 3, 5, 8, 13) a(0) // Int = 1 a(3) // Int = 5 a(21) // Throws an exception //字典 val m = Map("fork" -> "tenedor", "spoon" -> "cuchara", "knife" -> "cuchillo") m("fork") // java.lang.String = tenedor m("spoon") // java.lang.String = cuchara m("bottle") // Throws an exception //設定字典默認value值 val safeM = m.withDefaultValue("no lo se") // java.lang.String = no lo se safeM("bottle") //集合 val s = Set(1, 3, 7) // Boolean = false s(0) // Boolean = true s(1) //元組 (1, 2) (4, 3, 2) (1, 2, "three") (a, 2, "three") val divideInts = (x: Int, y: Int) => (x / y, x % y) // (Int, Int) = (3,1) val d = divideInts(10, 3) // 使用 _._n訪問元組元素,n值是元素的索引值,基數爲1。 d._1 // Int = 3 d._2 // Int = 1 // 一樣的你可使用多元賦值,這樣代碼更可讀。 val (div, mod) = divideInts(10, 3) div // Int = 3 mod // Int = 1
到目前爲止本教程展現的都是簡單表達式,這些表達式能夠在REPL中快速測試,但這些表達式不能獨立存在Scala文件中 ,Scala中容許存在的高級式爲:
classe聲明與其餘語言相似,構造器參數在類名後面聲明,初始化代碼在類中。
class Dog(br: String) { // 構造器代碼 //定義一個字段 var breed: String = br // 定義了一個方法,返回字符串 def bark = "Woof, woof!" // 字段和方法默認是public。"protected"、"private"關鍵字是容許的。 //定義私有方法 private def sleep(hours: Int) = println(s"I'm sleeping for $hours hours") // 抽象方法無需聲明方法體. Dog類也須要聲明爲抽象類 // abstract class Dog(...) { ... } // def chaseAfter(what: String): String } val mydog = new Dog("greyhound") println(mydog.breed) // => "greyhound" println(mydog.bark) // => "Woof, woof!"
"object"關鍵字建立了一個類型和一個類型單例。能夠直接使用Dog對象,無需手動實例化。這種區別相似於其餘語言類方法和靜態方法。注意object和class命名能夠相同。
object Dog { def allKnownBreeds = List("pitbull", "shepherd", "retriever") def createDog(breed: String) = new Dog(breed) }
case classes是一些具備額外功能的類。初學者很容易混淆class和case class的使用。一般狀況下classes用於實現封裝,多態,及行爲。類中字段是私有的,方法則是對外暴露。case classes主要目的是保存不可變數據,他們不多有方法。相似Java中的Enum。
case class Person(name: String, phoneNumber: String) // 建立一個新的實例. 注意cases classes不須要使用"new" val george = Person("George", "1234") val kate = Person("Kate", "4567") //直接訪問 george.phoneNumber // => "1234" // 全部字段都相同便可爲true,無需重寫equals方法 Person("George", "1234") == Person("Kate", "1236") // => false // 易於拷貝 // otherGeorge == Person("George", "9876") val otherGeorge = george.copy(phoneNumber = "9876")
相似Java接口, traits定義對象類型和方法簽名。scala容許實現部分方法。不容許有構造方法。Traits能繼承其餘traits或者無構造參數類。
trait Dog { def breed: String def color: String def bark: Boolean = true def bite: Boolean } class SaintBernard extends Dog { val breed = "Saint Bernard" val color = "brown" def bite = false } scala> b res0: SaintBernard = SaintBernard@3e57cd70 scala> b.breed res1: String = Saint Bernard scala> b.bark res2: Boolean = true scala> b.bite res3: Boolean = false
能夠繼承多個trait, class "extends" 第一個trait,關鍵字"with"能夠添加額外的traits.
trait Bark { def bark: String = "Woof" } trait Dog { def breed: String def color: String } class SaintBernard extends Dog with Bark { val breed = "Saint Bernard" val color = "brown" } scala> val b = new SaintBernard b: SaintBernard = SaintBernard@7b69c6ba scala> b.bark res0: String = Woof
模式匹配是Scala中常用的功能。這裏展現模式如何匹配case class。
//Scala中cases不須要break語句。 def matchPerson(person: Person): String = person match { // Then you specify the patterns: case Person("George", number) => "We found George! His number is " + number case Person("Kate", number) => "We found Kate! Her number is " + number case Person(name, number) => "We matched someone : " + name + ", phone : " + number }
在字符串後聲明r方法來建立正則表達式。
val email = "(.*)@(.*)".r
模式匹配相似與C語言中的switch聲明,但更加有用。在Scala中,你能夠匹配更多。
def matchEverything(obj: Any): String = obj match { // 能夠匹配值: case "Hello world" => "Got the string Hello world" // 能夠按類型匹配: case x: Double => "Got a Double: " + x // 能夠指定條件: case x: Int if x > 10000 => "Got a pretty big number!" // 匹配case classes case Person(name, number) => s"Got contact info for $name!" // 匹配正則表達式 case email(name, domain) => s"Got email address $name@$domain" // 匹配元組 case (a: Int, b: Double, c: String) => s"Got a tuple: $a, $b, $c" // 匹配數據結構 case List(1, b, c) => s"Got a list with three elements and starts with 1: 1, $b, $c" // 嵌套匹配 case List(List((1, 2, "YAY"))) => "Got a list of list of tuple" // 若是以前的都沒匹配上,執行這個case case _ => "Got unknown object" }
事實上,你可使用"unapply"方法匹配任意的對象。這個功能容許你將整個函數定義爲模式。
val patternFunc: Person => String = { case Person("George", number) => s"George's number: $number" case Person(name, number) => s"Random person's number: $number" }
Scala容許函數做爲入參或者返回值類型。
//定義一個入參爲Int,返回值爲Int的函數 val add10: Int => Int = _ + 10 // List(11, 12, 13) add10函數做用在每一個元素上。 List(1, 2, 3) map add10 // 能夠在列表上使用匿名函數 List(1, 2, 3) map (x => x + 10) // 若是僅有一個入參且入參僅使用一次,可使用_符號表明入參 List(1, 2, 3) map (_ + 10) // 若是匿名函數中僅使用傳參,你甚至能夠省略_ List("Dom", "Bob", "Natalia") foreach println
// 定義集合 // val s = Set(1, 3, 7) def sq: Int => Int = _*10 //執行map操做 s.map(sq) val sSquared = s.map(sq) //使用過濾函數 sSquared.filter(_ < 10) //執行reduce操做 sSquared.reduce (_+_) // 過濾函數傳入函數類型入參 List(1, 2, 3) filter (_ > 2) // List(3),保留元素3 case class Person(name: String, age: Int) List( Person(name = "Dom", age = 23), Person(name = "Bob", age = 30) ).filter(_.age > 25) // List(Person("Bob", 30))
Scala中幾乎全部容器都有 foreach
方法, 傳入一個參數,返回值爲Unit。
val aListOfNumbers = List(1, 2, 3, 4, 10, 20, 100) aListOfNumbers foreach (x => println(x)) aListOfNumbers foreach println // For comprehensions for { n <- s } yield sq(n) val nSquared2 = for { n <- s } yield sq(n) for { n <- nSquared2 if n < 10 } yield n for { n <- s; nSquared = n * n if nSquared < 10} yield nSquared
重大提醒: Implicits是Scala的一個強大功能,因此很容易被誤用.
Scala初學者應當耐住誘惑不使用這些功能,直到不只可否理解如何工做,同時可以理解用法的最佳實踐。在這個教程講述這部分緣由是這個功能使用太廣泛了,咱們沒法避免使用一個沒隱式功能的庫,因此理解隱式功能對你頗有義。
任何值(vals, functions, objects等)均可以使用「implicit」關鍵字聲明爲隱式。
// 注意本節將使用第五節定義的Dog類。 implicit val myImplicitInt = 100 implicit def myImplicitFunction(breed: String) = new Dog("Golden " + breed)
implicit關鍵字不改變值自己行爲。
// => 102 myImplicitInt + 2 // => "Golden Pitbull" myImplicitFunction("Pitbull").breed
區別在於當一段代碼須要一個implicit值,全部定義爲implicit的值均可行了。
// 函數入參定義一個隱式howMany字段 def sendGreetings(toWhom: String)(implicit howMany: Int) = s"Hello $toWhom, $howMany blessings to you and yours!" // 若是爲howMany提供一個值,函數正常調用。 sendGreetings("John")(1000) // => "Hello John, 1000 blessings to you and yours!" // 但若是忽略這個值,須要使用另外一個隱式值, 這裏是"myImplicitInt" sendGreetings("Jane") // => "Hello Jane, 100 blessings to you and yours!"
隱式函數參數讓咱們能夠模擬其餘函數語言的模板類,而且有本身的縮寫方法,如下兩行表達含義相同。
// def foo[T](implicit c: C[T]) = ... // def foo[T : C] = ...
另外一種編譯器尋找隱式表達式的另外一種狀況是若是你調用obj.method(...),實際這個obj沒有這個"method"方法,在這種狀況下,若是有一個隱式轉換表達式從類型A => B,A是obj的類型,B有一個方法叫"method",轉換會自動進行。
// 因爲有myImplicitFunction這個函數,下面語句是合法的。 "Retriever".breed // => "Golden Retriever" "Sheperd".bark // => "Woof, woof!"
以上兩個字符串首先使用以前定義的隱式函數轉換成Dog類實例,而後調用合適的方法。這個是極其有用的方法,可是也很差用,容易形成BUG。事實若是使用implicit函數,編譯器會給你一個warning。因此除非你知道本身在幹啥,不然永遠不要使用隱式功能。
// 導入類 import scala.collection.immutable.List // 導入全部子包 import scala.collection.immutable._ // 導入多個類 import scala.collection.immutable.{List, Map} // 使用'=>'重命名導入 import scala.collection.immutable.{List => ImmutableList} // 導入全部類,除了Map和Set類 import scala.collection.immutable.{Map => _, Set => _, _} // 一樣能夠導入Java類。 import java.swing.{JFrame, JWindow} // 在一個scala程序中,程序入口定義在object中,該object僅包含一個方法。 // 單一方法, main: object Application { def main(args: Array[String]): Unit = { // stuff goes here. } }
一個文件可包含多多個class及object. 使用scalac編譯
// 按行讀取文件 import scala.io.Source for(line <- Source.fromFile("myfile.txt").getLines()) println(line) // 使用Java's PrintWriter寫入文件 val writer = new PrintWriter("myfile.txt") writer.write("Writing line for line" + util.Properties.lineSeparator) writer.write("Another line here" + util.Properties.lineSeparator) writer.close()