快學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._