1. 程式人生 > >快學Scala筆記(一)

快學Scala筆記(一)

一、語法

沒有switch語句,沒有break 和 continue,使用boolean控制變數,或使用巢狀函式,從函式中return

表示式和語句不需要分號結尾,且都有返回值(返回最後一條表示式的值),如:

if(  x > 0 )  "a" else 0        // 返回 Any 型別

if(  x > 0 )  "a"                  //  Unit 型別 ( ) ,相當於void

for迴圈 ( 通常不需要for迴圈,通過對序列中的所有值應用某個函式來處理它們 )

for(  i  <-  1 to n )

for(  i  <-  1 until n )

for(  i  <-  "hello" )

for( i <- 1 to 3; j <- 1 to 3 )        // 多個生成器

for( i <- 1 to 3; j <- 1 to 3 if i != j )    // 帶條件的生成器

for( i <- 1 to 3; from = 4 - i; j <- from to 3 )        // 定義變數

for( i <- 1 to 10 ) yield i % 3        // for 推導式:以yield開始,則會構造一個集合,每次迭代生成集合中的一個值,// 生成 Vector (  1, 2, 0, 1, 2, 0, 1, 2, 0, 1 )

for 推導式生成的集合與它的第一個生成器是型別相容的

for( c <- "Hello"; i <- 0 to 1 ) yield ( c+i ).toChar            // 生成 "HIeflmlmop"

for( i <- 0 to 1; c <- "Hello" ) yield ( c+i ).toChar            // 生成 Vector( 'H', 'e', 'l', 'l', 'o', 'I', 'f', 'm', 'm', 'p' )

二、函式

必須給出所有引數的型別,如果函式不是遞迴的,則不需要指定返回值的型別,Scala可以通過 = 右邊的表示式型別推斷返回型別

def abs( x: Double ) = if ( x >= 0 ) x else -x

def fac( n: Int ){            

      var r = 1

      for ( i <- 1 to n ) r = r*i

      r            // 程式碼塊的最後一個表示式的值就是返回值

}

遞迴函式,則必須指定返回型別

def fac( n: Int ) : Int = if( n <= 0 ) 1 else n * fac( n -1 )

預設引數和帶名引數

def decorate( str: String, left: String = "[", right: String = "]" ) = left + str + right

decorate( left = "<", right = ">", str = "hello"  )       

// 帶名引數順序不需要和宣告一致,只要未命名的引數排在前面即可

變長引數

def sum( args: Int* ) = {        // Seq 型別的引數

      var result = 0

      for( arg <- args ) result += arg

      result

}

var s = sum( 1 to 5 )    // 錯誤,Range是一個引數

var s = sum( 1 to 5 : _* )     // 正確,將1 to 5當做引數序列處理

def recursiveSum( args : Int* ) : Int = {

        if ( args.length == 0 ) 0

        else args.head + recursiveSum( args.tail : _* ) 

        //  head 是首個元素,tail 是其他元素的序列,也是一個Seq

}

當呼叫變長引數,且引數型別為Object的Java方法,如PrintStream.printf 或 MessageFormat.format 時,需要將基本型別轉換為AnyRef

var str = MessageFormat.format( "{0} is {1}", "everything", 42.asInstanceOf[AnyRef])

過程

如果函式體包含在花括號中,但沒有前面的 = 號,那麼返回型別就是Unit,這樣的函式被稱為過程(procedure),過程不返回值

def hello( s: String ) { }

懶值 lazy

在首次使用時才會被賦值,懶值有額外開銷,每次訪問都有一個方法被呼叫,以執行緒安全的方式檢查是否初始化。介於 val 和 def 之間,val: 在被定義時取值,def: 在每一次被使用時取值

lazy val words = scala.io.Source.fromFile( "/usr/share/dict/words").mkString

異常

丟擲的物件必須是 java.lang.Throwable 的子類,不過scala沒有“受檢”異常: 不用宣告函式或方法有可能丟擲某種異常

throw表示式有特殊的型別Nothing,在if/else表示式中很有用,如果一個分支的型別是Nothing,那麼if/else表示式的型別就是另一個分支的型別

if( x >= 0 ) { sqrt(x) } else throw new IllegalArgumentException( "x should not be negtive" )

捕獲異常的語法採用的是模式匹配

var in = new URL( "http://…" ).openStream()

try {

      process( in )

} catch {

       // 不需要使用異常變數,用 _ 省略

       case _: MalformedURLException => println( "Bad URL: " + url )        

       case ex: IOException:  => ex.printStackTrace()

} finally {

       in.close()

}

三、陣列

定長陣列

val nums = new Array[Int]( 10 )

val a = new Array[String]( 10 )

var s = Array( "hello", "world" )

s(0) = "Good"

變長陣列

import scala.collection.mutable.ArrayBuffer

val b = ArrayBuffer[Int]()

b += 1                        // 追加一個元素

b += ( 2,3 )                // 追加多個元素

b ++= Array( 8,13 )    // 追加集合

b.trimEnd( 5 )        //  移除最後5個元素

b.insert(2, 6)    // 在下標2處插入6

b.insert(2, 7, 8, 9)    // 新增3個元素

b.remove(2)          // 移除下標為2的元素

b.remove(2, 3)        // 移除3個元素

b.toArray            // 轉換

a.toBuffer

遍歷陣列

for( i <- 0 until a.length ) println( a(i) )

for( i <- 0 until ( a.length, 2 ) println( a(i) )

for( i <- ( 0 until a.length ).reverse ) println( a(i) )

for( elem <- a ) println( elem )

陣列轉換

val a = Array(2,3,4,5,6)

val result = for( elem <- a ) yield 2*elem

for ( elem <- a if elem % 2== 0 ) yield 2* elem       

// 相當於  a.filter( _ %2 == 0 ).map( _ * 2 )    或  a.filter{ _ %2 == 0 }.map{ _ * 2 }

常用陣列操作

a.sum        a.max     a.sorted     b = a.sorted(  _ > _ )

var a = Array( 1, 7, 2, 9 )

a.trimEnd( 2 )

scala.util.Sorting.quickSort( a )        // 可以直接對一個數組Array排序,但不能對陣列緩衝ArrayBuffer排序

a.mkString( " and " )        // 使用分隔符合並陣列元素

a.mkString( "<", ",", ">" )        // 字首、分隔符、字尾

Array的toString沒有意義,ArrayBuffer的toString顯示內容

多維陣列

val matrix = Array.ofDim[Double](3,4)

matrix(row)(colum)

val triangle = new Array[ Array[Int] ]( 10 )     // 不規則陣列

for( i <- 0 until triangle.length )

triangle(i) = new Array[Int] ( i + 1 )

與Java的互操作

// Scala到Java的轉換,Scala緩衝被包裝成實現了java.util.List介面的Java類物件

import scala.collection.JavaConversions.bufferAsJavaList

import scala.collection.mutable.ArrayBuffer

val command = ArrayBuffer( "ls", "-al", "/home/cay" )

val pb = new ProcessBuilder( command )

// Java到Scala的轉換,不能返回ArrayBuffer,包裝起來的物件僅能保證是個Buffer

import scala.collection.JavaConversions.asScalaBuffer

import scala.collection.mutable.Buffer

val cmd: Buffer[String] = pb.command()    

四、對映和元組

val scores = Map( "Alice" -> 10, "Bob" ->3 ) // 對映是對偶的集合

val scores = scala.collection.mutable.Map(  "Alice" -> 10, "Bob" ->3 )    // 平衡樹

var scores = new  scala.collection.mutable.HashMap[String,Int]        // hash表   

var bobscore = scores( "Bob" )

var bobscore = scores.getOrElse( "Bob", 0 )

scores("Bob") = 1

scores += ("Bob"->7, "Fred"->10)

scores -= "Alice"

// 不能更新不可變的對映,但可以獲取一個新對映

val newScores = scores + ("Bob"->7, "Fred"->10)

var scores = …   // scores為可變數

scores = scoers + ("Bob"->10)

迭代對映

for( (k,v) <- scores ) println( k, v )

scores.keySet

scores.values

for( (k,v) <- scores ) yield ( v,k )

排序的對映

val scores = scala.collection.immutable.SortedMap( "Alice" -> 10, "Bob" -> 3 );    // 不可變的樹形對映

Scala沒有可變的樹形對映,最接近的選擇是使用Java的TreeMap

如果要按順序訪問所有鍵,使用LinkedHashMap

val months = scala.collection.mutable.LinkedHashMap("Januay"->1, … )

與Java的互操作

import scala.collection.JavaConversions.mapAsScalaMap

val scores: scala.collection.mutable.Map[String, Int] = new java.util.TreeMap[String, Int]

// 還可以從java.util.Properties得到Map[String,String]的轉換

import scala.collection.JavaConversions.propertiesAsScalaMap

val props : scala.collection.Map[String, Int] = System.getProperties()

// 把Scala對映傳遞給預期Java對映的方法,提供相反的隱式轉換即可。

import scala.collection.JavaConversions.mapAsJavaMap

import java.awt.font.TextAttribute._

val attrs = Map( FAMILY -> "Serif", SIZE -> 12 )

val font = new java.awt.font( attrs )

元組  

// 不同型別的值的聚集

val t = ( 1, 3.14, "Fred" )

t._1    t._2    t._3    // 元組的元素從 _1 開始

val ( first, second, _ ) = t    // 獲取元組元素

"New York".partition( _.isUpper )    // 輸出:( "NY", "ew ork" )

拉鍊操作

val symbols = Array( "<" , "-", ">" )

val counts = Array( 2, 10, 2 )

val pairs = symbols.zip( counts )

// 輸出:Array( ("<",2), ("-",10), (">", 2) )

pairs.toMap    // 轉換成對映

五、類

簡單類和無參方法

類不必宣告為public,scala原始檔可以包含多個類,所有這些類都是公共可見的

class Counter{

      private var value = 0    // 必須初始化欄位

      def increment() { value += 1 }    // 方法預設是public

      def current() = value    // 或 def current = value

}

var myCounter = new Counter        // 或new Counter()

myCounter.increment()           // 無參方法可以不帶括號,改值器建議帶括號

println( myCounter.current )    // 無參方法可以不帶括號,取值器

帶getter和setter屬性

Scala對每個欄位都提供getter和setter方法

class Person {

    var age = 0    // 生成面向JVM的類,私有age,getter方法 age,setter方法 age_=

}

// 如果欄位是私有的,則getter和setter也是私有的

// 如果欄位是val,則只有getter生成

// 如果不需要任何getter或setter,可以將欄位宣告為private[this]

Scala中,方法可以訪問該類的所有物件的私有欄位

class Counter {

       def isLess( other: Counter ) = value  < other.value

}

private[this] var value = 0    // 不允許該類的其他物件訪問

標註為@BeanProperty時,會自動生成 getFoo setFoo 規範的方法

import scala.reflect.BeanProperty

class Person {

@BeanProperty var name: String = _

// 將生成四個方法: name: String    name_=(newValue: String) : Unit    getName() : String     setName( newValue: String ) : Unit

}


scala getter setter 生成規則

構造方法

輔助構造器:必須以先前定義的構造器或主構造器的呼叫開始

class Person {

private var name = ""

private var age = 0

def this( name: String )    {        // 一個輔助構造器

      this()                // 呼叫主構造器

      this.name = name

}

def this( name: String, age: Int )    {        // 另一個輔助構造器

      this( name )

      this.age = age

}

}

val p1 = new Person    // 自動生成無引數的主構造器

主構造器:直接放在類名之後

class Person( val name: String, val age: Int ) {    // ( … ) 中的內容就是主構造器的引數

          println( "constructed" )    // 主構造器會執行類定義中的所有語句

}

class MyProg {

       private val props = new Properties

       props.load( new FileReader("myprog.properties") )

}

class Person ( val name: String = "", val age: Int = 0  ) {    // 預設引數   }

class Person( val name: String, private var age: Int ) { }

class Person( name: String, age: Int ) {     // 不帶限定符的引數取決於在類中如何使用

        // 至少被一個方法所使用,將升格為欄位,宣告並初始化了不可變欄位name和age,是物件私有的,等同於private[this] val

        def description = name + " is " + age

        // 否則,該引數不被儲存為欄位

}


scala 構造器 引數生成的欄位和方法

私有主構造器:  class Person private( val id: Int ) { … }

巢狀類:Scala中,幾乎可以在任何語法結構中巢狀任何語法結構

class Network {

         class Memeber ( val name : String ) { val contacts = new ArrayBuffer[Member] }

}

var chatter = new Network

var myFace = new Network

fred = chatter.join( "Fred" )

bob = myFace.join( "Bob" )

fred.contacts += bob        // !!錯誤!!myFace.Member  和 chatter.Member 不同型別

解決方式一:伴生物件

object Network {

        class Member( val name:String  ){

                val contacts = new ArrayBuffer[Member]

        }

}

解決方式二:型別投影 

class Network {

        class Member( val name:String  ){

              val contacts = new ArrayBuffer[Network#Member]  

              // 任何Network的Member

        }

}

在內部類中,通過OuterName.this訪問外部物件,也可以

class Network { outer =>        // outer  指向 Network.this

    class Member( val name:String  ){

         // println( outer.name )

    }

}

單例物件

Scala 沒有靜態方法或靜態欄位,可以用object構造語法達到同樣的目的

object Accounts {

      private var lastNumber = 0

      def newUniqNumber () = { lastNumber +=1 ; lastNumber }

}

//  呼叫:Accounts.newUniqNumber()    物件的構造器在該物件第一次被使用時呼叫

物件本質上可以擁有類的所有特性,甚至可以擴充套件其他類或特質,只有一個例外:不能提供構造引數

伴生物件

實現既有例項方法又有靜態方法的類,類和它的伴生物件可以互相訪問私有屬性,它們必須存在於同一個原始檔中

class Account {

    val id = Account.newUniqNumber()

    private var balance = 0.0

    def deposite( amount: Double ) { balance += amount }

}

object Accounts {       // 伴生物件

    private var lastNumber = 0

    def newUniqNumber () = { lastNumber +=1 ; lastNumber }

}

擴充套件類或特質的物件

abstract class UndoableAction( val description: String ){

def undo() : Unit

def redo(): Unit

}

object DoNothingAction extends UndoableAction( "Do nothing" ) {

override def undo() {}

override def redo() {}

}

val actions = Map( "open" -> DoNothingAction, "save" -> DoNothingAction, … )

apply方法

我們通常會定義和使用物件的apply方法,當遇到如下形式的表示式時,apply方法就會被呼叫

Object( 引數1, …, 引數N )        // 返回伴生類的物件

Array( "Mary", "had", "a", "little", "lamb" )

定義apply方法:

class Account private ( val id: Int, initialBalance: Double ) {

private val balance = initialBalance

}

object Account {    // 伴生物件

def apply ( initialBalance: Double ) = new Account( newUniqNumber(), initialBalance )

}

val account = Account( 1000.0 )

應用程式物件

每個scala應用程式都必須從main方法開始,這個方法的型別為 Array[String] => Unit

object Hello {

    def main ( args : Array[String] ){

        println( "hello" )

    }

}

也可以擴充套件App特質,將程式程式碼放入構造器方法內

object Hello extends App {

       if( args.length > 0 ) println( args(0) )    // 通過args獲得引數

       println( "hello" )

}

列舉

// scala並沒有列舉型別,不過可以通過標準類庫提供的Enumeration助手類,產生列舉

object TrafficLightColor extends Enumeration {

    val Red, Yellow, Green = Value

}

Value可以有ID和名稱 Value( 10, "red" )

訪問:TrafficLightColor.Red

列舉的型別是: TrafficLightColor.Value    而不是  TrafficLightColor

for ( c <- TrafficLightColor.values ) println( c.id + ": " + c )

TrafficLightColor(0)

TrafficLightColor.withName("Red")

六、包和引入

package com {

    package horstman {

         package impatient {

              class Employee

              ...

         }

    }

}

所有父包中的內容都在作用域內

絕對包名: _root_.scala.collection.mutable.ArrayBuffer[Employee]

串聯式包語句:

package com.horstman.impatient {

    // com 和 com.horstman 的成員在這裡不可見

    package people {

         class Person

         ...

    }

}

檔案頂部標記法 ( 與上面的方式等同)

package com.horstman.impatient

package people

class Person

包物件

包可以包含類、物件和特質,但不能包含函式或變數的定義。包物件用來解決這個侷限。每個包都可以有一個包物件,需要在父包中定義,名字與子包相同。

package com.horstman.impatient

package object people {

   val defaultName = "John"        // 可以用 com.horstman.impatient.people.defaultName 訪問

}

package people {

   class Person {

        var name = defaultName    // 從包物件拿到的常量

   }

}

包可見性

package com.horstman.impatient.people

class Person {

private[people] def description = "A person with name" + name    // 在自己的包中可見

private[impatient] def description = "A person with name" + name    // 可見度延伸至上層

}

引入

import java.awt.Color

import java.awt.Color._

在任何地方都可以宣告引入

選取器:import java.awt.{ Color, Font }

重新命名:import java.util.{ HashMap => JavaHashMap }

隱藏:import java.util.{ HashMap => _, _ }    // 引入除HashMap以外的其他成員

隱式引入: 每個scala程式都隱式地引入:

import java.lang._

import scala._

import Predef._