Scala簡明教程

閱讀說明

  1. 本教程面向有必定編程經驗的開發者。
  2. 本教程預計花費30min

語言說明

scala是基於java開發的,結合了面向對象編程技術及函數式編程技術。scala的靜態類型避免了複雜程序的大部分BUG。它的JVM和Js運行環境讓你能夠構建高性能系統。html

開始

0. 基礎

0.1 設定Scala

  1. 下載Scala - http://www.scala-lang.org/dow...
  2. 把包解壓到你喜歡的目錄,並把目錄加入環境變量
  3. 命令行查看版本
scala -version

0.2 嘗試REPL

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。正則表達式

1. 基本語法

// 這是單行註釋

/*
  這是多行註釋
*/

// 打印,並換行
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>"""

2. 函數

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

3. 流程控制

3.1 循環流程

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)
3.2 條件語句
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"

4. 數據結構

支持的數據類型又數組,字典,集合,鏈表,元組

//數組
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

5. 面向對象編程

到目前爲止本教程展現的都是簡單表達式,這些表達式能夠在REPL中快速測試,但這些表達式不能獨立存在Scala文件中 ,Scala中容許存在的高級式爲:

  • objects
  • classes
  • case classes
  • traits

5.1 classe

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!"

5.2 object

"object"關鍵字建立了一個類型和一個類型單例。能夠直接使用Dog對象,無需手動實例化。這種區別相似於其餘語言類方法和靜態方法。注意object和class命名能夠相同。

object Dog {
  def allKnownBreeds = List("pitbull", "shepherd", "retriever")
  def createDog(breed: String) = new Dog(breed)
}

5.3 case classes

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

5.4 traits

相似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

6. 模式匹配

模式匹配是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
}

6.1 內置正則表達式

在字符串後聲明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"
}

7. 函數式編程

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

7.1 Combinators

// 定義集合
// 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

8. 隱式

重大提醒: 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。因此除非你知道本身在幹啥,不然永遠不要使用隱式功能。

9. 混合使用

// 導入類
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編譯

10. 輸入輸出

// 按行讀取文件
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()

引用

  1. https://learnxinyminutes.com/...
相關文章
相關標籤/搜索