1. 程式人生 > >Scala學習筆記 --《Scala學習筆記》梳理(二)

Scala學習筆記 --《Scala學習筆記》梳理(二)

第1章  概述

Scala是一個編譯型的靜態型別語言

Scala REPL:Read(讀)、Evaluate(執行)、Print(列印)、Loop(迴圈)

第2章  處理資料:字面量、值、變數和型別

scala編譯器將從賦值判斷這個值的型別,這個過程稱為型別推導

命名:Scala中的名字可以使用字母、數字和一些特殊的操作符字元

Scala不准許從高等級型別轉換成低等級型別,但支援低等級型別轉換成高等級,可以選擇toType手動轉換。

要對正則表示式完成更高階的操作,需要呼叫它的r操作符將字串轉換為正則表示式型別,返回Regex例項。

val input ="Enjoying this apple 3.141596 times today"

val pattern = """.* apple ([\d.]+) times.*""".r

val pattern(result)=input

//   模式 (輸出的結果) = 輸入

val res=result.toDouble

Scala型別層次體系:

  • Any、AnyVal、AnyRef型別是scala類層次體系的根
  • AnyVal是值型別,表示資料的核心值,在堆中分配記憶體,或者可以作為JVM基本型別在棧中分配記憶體
  • AnyRef是所有其他型別的根,只能作為物件在堆中分配記憶體
  • Nothing是所有型別的子型別,它的存在提供一個相容的返回型別。
  • NULL是所有AnyRef型別的子型別,為關鍵字null提供一個型別。
  • Char是一個文字單位,使用時用單引號(區分String)
  • Unit型別相當於void,通常用來定義函式/表示式

val data=()

元組

元組是一個包含兩個或多個值的有序容器,所有這些值可以有不同的型別,元組的作用只是作為多個值的容器。

建立元組:

val info = (5,"zhang",true)//建立1

val red = "red"->"like"//建立2

訪問元組:

val name = info._2

val reversed =red._2->red._1

第3章  表示式和條件式

表示式:是一個返回一個值的程式碼單元

"hello"

表示式塊:

val amount = {val x=5*20;x+10}

表示式巢狀:

{val a = 1;{val b = a*2; {val c = b+4; c}}}//返回值是c

語句:不返回值的表示式,返回型別為Unit

if else語句塊

if表示式

val result = if(false)"what does this return?"

result:Any=()

結果值沒有指定,編譯器只能推測確定最合適的型別,可能返回一個String或Unit,所以選擇了根類Any(String和Unit的共同根類)

在Scala中不需要三元式,因為if else可以緊湊寫在一行上

匹配表示式

val max = x > y match {

   case true => x

   case false => y

}

match 匹配表示式可以存在其他操作

val status = 500

val message = status match {

   case 200 => "ok"

   case 400 => { println("ERROR-1")

   "error"}

   case 500 => { println("ERROR-2")

   "error"}

}

模式替換式

val kind = day match {

  case "MON"|"TUE"|"WED"|"THU"|"FRI" => "weekday"

  case "SAT"|"SUN" =>"weekend"

}

通配模式匹配:

(1) 值繫結模式:case other =>

(2) 萬用字元換式:case_ =>

(3)用模式哨兵匹配:

case pattern if <Boolean expression> => <one or more expression>

case s if s!=null => println("hello world")

(4)指定模式變數:對傳入的值的型別進行判斷

case 定義:型別 => expression

for迴圈

for(x <- 1 to 7){println(s"Day $x:")}

yield返回一個表示式,表示式中的集合可以用其他for迴圈中作為迭代器:

for(x<- 1 to 7) yield {s"Day $x:"}

res

for(day <- res) print(day+",")

迭代器哨兵

迭代器哨位也稱為過濾器,在for中使用if

for{

t<-quate.split(",")

if t!=null

if t.size>0

}{println(t)}

巢狀迭代器

for(x<-1 to 2 y<-1 to 3){println(s"($x,$y)")}

值繫結

val power = for(i<- 0 to 8; pow = 1<<i) yield pow

while 和 Do/While迴圈

第4章  函式

在Scala中函式是可重用的命名錶達方式,函式可以引數化,可以返回一個值。

純函式:

  1. 有一個或多個輸入引數
  2. 只使用輸入引數完成計算
  3. 返回一個值
  4. 對於相同的輸入總返回相同的值
  5. 不適用或影響函式之外的任何資料
  6. 不受函式之外的任何資料的影響

def multiplier(x:Int,y:Int):Int = { x*y }

過程

過程是沒有返回值的函式,Scala翻譯器就會匯出這個函式的返回值型別為Unit

無輸入函式

如果函式有副作用,定義時九陰當加括號

def hi():String ="hi"

表示式塊

def formatEuro(amt:Double) = f"$amt%.2f"//呼叫函式

formatEuro(3.4645)

尾遞迴優化

為了避免遞迴過程中不適用額外的棧空間,Scala編譯器可以使用尾遞迴優化一些遞迴函式。利用尾遞迴優化函式,遞迴呼叫不會建立新的棧空間,而是使用當前函式的棧空間。

需要在函式定義前或前一行增加文字@annotation.tailrec標誌尾遞迴優化。

@annotation.tailrec

def power(x:Int,n:Int,t:Int =1):Int = {

   if(n<1) t

   else power(x,n-1,x*t)

}

power(2,8)

Int = 256

巢狀函式

def max(a:Int, b:Int, c:Int) = {

   def max(x:Int,y:Int) = if(x>y) x else y //巢狀函式

   max(a,max(b,c)) //max(42,181,19) --> 181

}

用命名引數呼叫函式:不按順序指定引數

呼叫引數:greet(name="Brown",prefix="Mr")

函式引數預設值:

def greet(name:String, prefix:String = "")=s"$prefix$name"

Vararg引數:

Scala支援vararg引數,可以定義輸入引數個數可變的函式

要標誌一個引數匹配一個或多個輸入實參,在函式定義中需要該引數型別後面增加一個星號*

def sum(items:Int*):Int = {

  var total = 0

  for(i<-items) total+=i

  total

}

sum(10,20,30)

引數組:每個引數組分別用小括號分割

def max(x:Int)(y:Int) = if (x>y) x else y

val larger=max(20)(39)

型別引數:相當於泛型

def identity[A](a:A):A = a

val s:String = identity[String]("Hello")

val d:Double = identity[Double](2.717)

第5章  首類函式

高階函式:map() 、 reduce()、filter()

高階函式優點:具體處理細節,留給高階函式框架萬恆,呼叫者可以指定做什麼,讓高階函式處理具體的邏輯流。

  1. 宣告式程式設計
  2. 指令式程式設計

函式型別

用萬用字元為函式賦值

def double(x:Int):Int = x*2

val mDouble = double_  //相當於函式指標

val amount=mDouble(20)

def max(a:Int,b:Int) = if (a>b) a else b

val maximize:(Int,Int) => Int = max

//            輸入     => 返回 = 呼叫函式

maximize(50,30)

編寫函式字面量

//引數為函式

def safeStringOp(s:String, f:String => String) ={

  if(s!=null) f(s) else s

}

//傳入函式

safeStringOp(null,(s:String) => s.reverse)

佔位符語法:_

函式柯里化:

如果保留一些引數,可以部分應用這個函式,使用萬用字元替代其中的一個函式

def factorOf(x:Int)(y:Int) = y%x ==0

val isEven = factorOf(2)_

val z = isEven(32)

傳名函式

偏函式:呼叫一個偏函式時,如果所使用的資料不能滿足其中至少一個case模式,就導致Scala錯誤

用函式字面量塊呼叫高階函式

def safeStringOp(s:String,f:String => String) = {

   if(s!=null) f(s) else s

}

val timedUUID = safeStringOp(uuid, s => {

    val timed = s.take(24)

    timed.toUpperCase

})

函式可以採用這種方式將單獨的程式碼塊包圍工具函式中:

  1. 管理資料庫事務
  2. 重新嘗試處理可能的錯誤
  3. 資料區域性,全部或外部值

第6章  常用集合

List

val colors = List("red","green","blue")

colors.head

colors.tail

colors(1)

colors(2)

colors.foreach((c:String) => println(c))

val sizes = colors.map((c:String) => c.size)

val total = numbers.reduce((a:Int,b:Int) => a+b )

List[List[Int]] = List(List(1,3,5),List(2,4,6))

Nil:所有列表都有一個Nil例項作為終結點,迭代器可以通過比較當前元素Nil檢查是否達到列表末尾

Nil實際上是List[Nothing]的一個單例例項。

建立一個新的空列表時,實際上會返回Nil而不是一個新例項

while(i!=Nil){print(i.head+",");i=i.tail}

val l:List[Int] = List()

l==Nil

m.tail == Nil

Cons操作符:Cons操作符::繫結元素,可以構建一個列表

val numbers = 1::2::3::4::Nil

val first = Nil.::(1)//插第一個元素

val second = 2::first//追加效果,在第一個元素前新增一個元素2

列表的算數運算:

p94~95

val f = List(23,8,14,21) filter (_ > 18)

val p = List(1,2,3,4,5) partition (_ < 3)

val s = List("apple","to") sortBy (_.size)

List是一個連結串列,由於在列表末尾增加項會改變這個列表,需要複製整個列表,並返回得到這個列表。

表對映操作:

  1. collect
  2. flatMap
  3. map

List(0,1,0) collect {case 1 => "ok"}//偏函式

List("milk,tea") flatMap(_.split(','))//使用一個給定的函式轉換各個元素

List("milk","tea") map(_.toUpperCase)//轉換各個元素

規約操作:

  1. max
  2. min
  3. product //列表中的數相乘
  4. sum

List(41,59,26).max

List(41,59,26).min

List(1,2,3).product

List(1,4,5).sum

val validations = List(true,false,true)

validations forall (_ == true)

p101

轉換集合

  1. mkString
  2. toBuffer
  3. toList
  4. toMap
  5. toSet
  6. toString

p102

======================================

Set

val unique = Set(10,20,30,20,20,10)

val sum = unique.reduce((a:Int,b:Int) => a+b)

Map

val colorMap = Map("red" ->123,"green"->456)

val hashWhite = colorMap.contains("white")

for(pairs <- colorMap){println(pairs)}

======================================

Java和Scala集合相容性

完成Java和Scala集合之間的轉換

import collection.JavaConverters._

asJava     List(12,29).asJava

asScala    new Java.util.ArrayList(5).asScala

第7章  更多集合

不可變型別

可變型別

collection.immutable.List

collection.mutable.Buffer

collection.immutable.Set

collection.mutable.Set

collection.immutable.Map

collection.mutable.Map

val m = Map("APPL"->597,"MSFT"->40)

val n = m - "APPL" + ("GOOC"->521)//刪除一個元素,並增加

構建可變集合的方法:

  1. 建立新的可變集合
  2. 從不可變集合建立可變集合

不可變集合List、Map和Set都可以用toBuffer方法轉換為collection.mutable.Buffer型別,最後可以通過toList,和toMap變為不可變函式

  1. 使用集合構建器

newBuilder指定集合元素的型別,呼叫構建器的result轉換為集合

val b = Set.newBuilder[Char]

b+='h'

b++= List('e','l','l','o')

val helloSet = b.result

陣列

是一個大小不可變,但是內容可變的集合

Array型別實際上只是Java陣列型別的一個包裝器,另外提供了一個高階特性:隱含類

val colors = Array("red","green","blue")

colors(0)

colors

val files= new java.io.File(".").listFiles

val scala = files map (_.getName) filter(_ endsWith "scala")

Seq和序列

Stream:懶列表,訪問元素時才增加相應的元素

Vector型別要以一個Array提供後備儲存

valhi = "Hello," ++ "worldly" take 12 replaceAll ("w","W")

Stream

Stream型別是一個懶集合,由一個或多個初始元素和一個遞迴函式生成,第一次訪問元素時才會把這個元素增加到集合中

流生成的元素會快取,以備以後獲取,確保每個元素只生成一次

使用遞迴生成新的流,可以用來無限生成新的元素。

def inc(i:Int):Stream[Int] = Stream.cons(i,inc(i+1))//1.使用cons會生成流

val s = inc(1)

def inc(head:Int):Stream[Int] = head #:: inc(head+1)//2.使用#::也會生成流

def to(head:Char,end:Char):Stream[Char] = (head > end) match {

    case true => Stream.empty

    case false => head #::to((head+1).toChar,end)

}

val hexChars = to('A','F').take(20).toList

一元集合

A)Option集合

Option型別表示一個值的存在或是不存在

      Option可以安全替代null值,Option型別本身沒有實現,而是依賴兩個子型別提供的具體實現:

                                                     Some、None

var x:String ="Indeed"

var a = Option(x)//Some

x=null

var b = Option(x)//None

a.isDefined

b.isEmpty

//檢測分母是否為0

def divide(amt:Double,divisor:Double):Option[Double] = {

   if(divisor == 0) None

   else Option(amt / divisor)//提供一種安全方法檢測函式結果

}

headOption 檢測空列表

安全的Option抽取操作:

  1. fold
  2. getOrElse
  3. orElse

B)Try集合

util.Try集合將錯誤處理轉變為集合管理,它提供了一種機制來捕獲給定函式中的錯誤

使用Try的錯誤方法:

flatMap、foreach、getOrElse、orElse、toOption、map

val t = util.Try(...)//使用方法

val input="123"

val result = Util.Try(input.toInt) orElse util.Try(input.trim.toInt)

result foreach {r =>println(s"Parsed '$input' to $r!")}

val x = result match {

  case util.Success(x) => Some(x)

  case util.Failure(ex) =>{

   println(s"Couldn't parse input '$input'")

   None

}

}

C)Future集合

Future後臺任務

import concurrent.ExecutionContext.Implicits.global

val f = concurrent.Future {println("hi")}

非同步處理future

def nextFtr(i:Int = 0) = Future{

  def rand(x:Int) = util.Random.nextInt(x)

  Thread.sleep(rand(5000))

  if(rand(3) > 0) (i+1) else throw new Exception

}

同步處理future

concurrent.Await.result()

第8章  類

繼承、多型、封裝

class User

val u = new User

val isAnyRef = u.isInstanceOf[AnyRef]

類引數

類引數可以用來初始化欄位

class User(n:String){

  val name:String = n

}

val u = new User("zhangsan")

巢狀類

類定義中巢狀類,巢狀類除了可以訪問自己的欄位和方法,還可以訪問其父類的欄位和方法

型別引數的類

class Singular[A](element:A) extends Traversable[A]{

   def foreach[B](f:A=>B) = f(element)

}

val p = new Singular("Planes")

抽象類

抽象類生命但不定義欄位和方法。如果一個類擴充套件了抽象類,但這個類沒有標誌抽象類,就必須提供這些欄位和方法的實現。

abstract class Car{

  val year:Int

  val automatic:Boolean = true

  def color:String

}

class RedMini(val year:Int) extends Car{

   def color="Red"

}

val m:Car = new RedMini(2005)

匿名類

apply方法

apply方法有時是指它作為一個預設方法或一個注入方法,可以直接呼叫而不需要方法名

class Multiplier(factor:Int){

   def apply(input:Int)=input * factor

}

val tripleMe = new Multiplier(3)

val tripled = treipledMe.apply(10)

懶值

lazy,只是第一次例項化這些值時才建立。

如果要確保時間或效能敏感操作在類的生命期中只執行一次,懶值是一個很好的方法:

  1. 儲存基於檔案的屬性
  2. 開啟的資料庫連線
  3. 不可變資料等資訊

包裝類

可以在任何位置使用import

批量匯入:

import collection.mutable._

import colection.mutable.{Queue,ArrayBuffer}//批量匯入

別名

為了防止發生衝突,可以使用一個匯入的別名,在區域性名稱空間中對某個型別重新命名

import collection.mutable.{Map => MuMap}

包裝語法

私密性控制:protected、private

最終類:final

第9章  物件、Case類和Trait

物件

是一個類型別,只能有不超過1個例項

物件會在首次訪問時在當前執行的JVM中自動例項化(在訪問之前,不會例項化)

純函式

會返回完全由其輸入計算得到的結果,而沒有任何副作用,而且在引用方面是透明的

apply方法伴生物件

伴生物件是與類同名的一個物件,與類在同一個檔案中定義。伴生物件和類可以認為是一個單個單元,所以它們可以互相訪問私有和保護欄位及方法。

:paste

class Multiplier(val x:Int){def product(y:Int) = x*y}

object Multiplier{def apply(x:Int) = new Multiplier(x)}//伴生物件模式,預設構造方法

:paste

object DBConnection {

   private val  db_url ="jdbc://localhost"

   private val  db_user="franken"

   private val  val_db_pass="berry"

   def apply()=new DBConnection

}

class DBConnection {

    private val props = Map(

       "url" -> DBConnection.db_url,

       "user" -> DBConnection.db_user,

       "pass" -> DBConnection.db_pass

)

   println(s"Created new connection for" + props("url"))

}

REPL的貼上模式還有一個好處:物件和類會同時編譯。除了對私有欄位的特殊伴生訪問

編譯

Date.scala

object Date{

   def main(args:Array[String]){

     println(new java.util.Date)

  }

}

scalac Date.scala //編譯

scala Date //使用

Case類

Case類是不可例項化的類,包含多個自動生成的方法。它包括一個自動生成的伴生物件,這個物件也有其自己的自動生成的方法。Case類對資料傳輸物件很適用,這些類主要用於儲存資料。

case類有一些自己的方法:

apply、copy、equals、hashCode、toString、unapply

case類優點:利用case類不需要編寫太多樣例程式碼

Trait

trait是一種支援多繼承的類,可以同時擴充套件多個trait

擴充套件第一個trait使用extends,擴充套件第二個trait使用with

class Page(val s:String) extends SageStringUtils with HtmlUtils{...}

多trait實現原理

編譯器會建立多個trait的副本,形成一個“很高”的單列層次體系

編譯到.class二進位制檔案時,實際上會擴充套件一個類,這個類又擴充套件另一個類,後者進一步擴充套件下一個類

例如:

class D extends A with B with C

==>

class D extends C extends B extends A

自型別

是trait註解,向一個類增加這個trait時,要求這個類必須有一個特定的型別或子型別

class A{def hi="hi"}

trait B{ self:A => override def toString = "B:"+ hi}//增強方法

class C extends A with B

new C()

第10章  高階型別

高層元組和函式字面量

val t1:(Int,Char) = (1,'a')

val t2:(Int,Char) = Tuple2[Int,Char](1,'a')

val f1:Int => Int = _+2

val f2:Int => Int = new Function1[Int,Int]{def apply(x:Int) = x*2 }

隱含類

提供一種型別安全的方法:"monkey-pathc", 動態修改現有的程式碼,為現有類增加新方法和欄位

//隱含類

object ImplicitClasses{

   implicit class Hello(s:String){ def hello = s"Hello,$s"}//隱含類

   def test = {

      println("world".hello)

   }

}

//隱含值,隱含引數

object ImplicitParams{

   def greet(name:String)(implicit greeting:String) = s"$greeting, $name"

   implicit val hi="Hello"

   def test = {

      println(greet("Developers"))

  }

}

ImplicitParams.test

類型別名

為現有的型別建立一個別名,類型別名只能在物件、類或trait中定義

object TypeFun{

   //Int 類型別名

   type Whole = Int

   val x:Whole =5

   //Tuple類型別名

   type UserInfo = Tuple[Int,String]

   val u:UserInfo = new UserInfo(123,"zhangsan")

}

抽象型別:型別泛型

trait Factory[A]{def create:A}

trait UserFactory extends Factory[User]{def create = new User("")}

定界型別

def check[A<:BaseUser](u:A){ if(u.name.isEmpty) println("Fail")}//不能高於BaseUser

          A>:Customer  //不能低於

型別變化:指定一個型別引數如何調整以滿足一個基類或子型別

  1. 協變型別:必要時自動轉換成基類
  2. 逆變型別:必要時自動轉換為子型別

case class Item[+A](a:A){ def get:A = a }

case class Check[-A] { def check(a:A) = { } }