2.2 跳出循環(huán)的三種方法
1. 使用boolean控制變量
var flag = true
var res = 0
var n = 0
while(flag){
res += n
n += 1
if (n==5) flag = false
}
/****************/
for(i <- 0 until 10 if flag){
res += 1
if (i==4) flag = false
}
2. 在嵌套函數(shù)里面使用return
def outer() = {
var res = 0
def inner(){
for (i<-0 until 10){
if (i==5) return
res += 1
}
}
inner()
res
}
3. 使用Breaks對(duì)象的break方法
import scala.util.control.Breaks._
var res = 0
breakable{
for(i<-0 until 10){
if (i==5) break;
res += i
}
}
2.3 多維數(shù)組、Java數(shù)組與Scala數(shù)組的隱式轉(zhuǎn)換
多維數(shù)組
//構(gòu)造行與列的二維數(shù)組:Array.ofDim方法
val multiDimArr = Array.ofDim[Double](3,4)
multiDimArr(0)(0) = 1.0 // 柯里表達(dá)式
// 構(gòu)造不規(guī)則多維數(shù)組
val multiDimArr2 = new Array[Array[Int]](3)
multiDimArr2(0) = new Array[Int](1)
multiDimArr2(1) = new Array[Int](2)
multiDimArr2(2) = new Array[Int](3)
multiDimArr2(1)(1) = 1
Java數(shù)組與Scala數(shù)據(jù)的隱式轉(zhuǎn)換
// Scala中的list是ArrayBuffer,無(wú)法直接傳入Java的API
import scala.collection.JavaConversions.bufferAsJavaList
import scala.collection.mutable.ArrayBuffer
val command = ArrayBuffer("javac", "filePath/test.java")
val processBuilder = new ProcessBuilder(command) // ProcessBuilder是java的類,command作為ArrayBuffer是無(wú)法直接傳入的,只能通過bufferAsJavaList隱式轉(zhuǎn)換
val process = processBuilder.start()
val res = process.waitFor()
import scala.collection.JavaConversions.asScalaBuffer
import scala.collection.mutable.Buffer
val cmd:Buffer[String] = processBuilder.command() // 逆方向轉(zhuǎn)換
2.4 Tuple拉鏈操作、Java與Scala的Map隱式轉(zhuǎn)換
Tuple拉鏈操作
// Tuple拉鏈操作就是zip操作,是Array類的方法,用于將兩個(gè)Array合并為一個(gè)Array
// 比如Array(v1)和Array(v2),使用zip操作合并后的格式為Array((v1, v2)),合并后的Array元素類型為Tuple
val students = Array("leo","jake")
val scores = Array(80,60)
val studentScores = students.zip(scores)
for ((stu, sco) <- studentScores) println(stu+" "+sco)
val studentScoreMap = studentScores.toMap
studentScoreMap("leo")
Java Map和Scala Map的隱式轉(zhuǎn)換
import scala.collection.JavaConversions.mapAsScalaMap
val javaScoreMap = new java.util.HashMap[String, Int]()
javaScoreMap.put("leo",80)
javaScoreMap.put("jack",70)
val scalaScoreMap: Scala.collection.immutable.Map[String, Int] = javaScoreMap
import scala.collection.JavaConversions.mapAsJavaMap
import java.awt.font.TextAttribute._
val scalaAttrMap = Map(FAMILY -> "Serif", SIZE -> 12)
val font = new java.awt.Font(scalaAttrMap)
2.6 package與import實(shí)戰(zhàn)詳解
package定義
// package第一種定義方式:多層級(jí)package定義(不可取)
package com {
package spark {
package scala {
class Test {}
}
}
}
// package第二種定義方式:串聯(lián)式package定義(也不可取)
package com.spark.scala {
package service {
class Test {}
}
}
//package第三種定義方式:文件頂部package定義
package com.spark.scala.service
class Test{}
package特性
同一個(gè)包定義,可以在不同的scala源文件中;一個(gè)scala源文件內(nèi),可以包含兩個(gè)包
-
子包中的類,可以訪問父包中的類
-
相對(duì)包名與絕對(duì)包名
-
定義package對(duì)象(使用較少)
package內(nèi)的成員,可以直接訪問package對(duì)象內(nèi)的成員
package可見性
package com.spark.scala.
class Person{
private[scala] val name = "leo"
private[spark] val age = 25 // 只在spark包下面的成員可見
}
import特性
- 用import xxx.xxx._的格式可以導(dǎo)入包下所有成員
- scala與java不同之處在于,任何地方都可以使用import,比如類內(nèi)、方法內(nèi),這種方式的好處在于,可以在一定作用域范圍內(nèi)使用導(dǎo)入
- 選擇器、重命名、隱藏
import spark.scala.{aaa, bbb}
import spark.scala.{HashMap => scalaHashMap} // 重命名
import scala.util.{HashMap => _, _} // 導(dǎo)入scala.util包下所有的類,但是隱藏掉HashMap類
- 隱式導(dǎo)入
// 每個(gè)scala程序默認(rèn)都會(huì)隱式導(dǎo)入以下幾個(gè)包所有的成員
import java.lang._
import scala._
import Predef._
2.7 重寫field的提前定義、Scala繼承層級(jí)、對(duì)象相等性
重寫field的提前定義
class Student{
val classNum: Int = 10
val classScores: Array[Int] = new Array[Int](classNum)
}
class PEStudent extends Student{
override val classNum: Int = 3
}
val s = new Student()
val pes = new PEStudent()
s.classScores // Array[Int] = Array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
pes.classScores // Array[Int] = Array() 為空
- 子類PEStudent的構(gòu)造函數(shù)(無(wú)參)調(diào)用父類Student的構(gòu)造函數(shù)(無(wú)參)
- 父類的構(gòu)造函數(shù)初始化field(結(jié)果正確)
- 父類的構(gòu)造函數(shù)使用field執(zhí)行其他構(gòu)造代碼(classScores),但是此時(shí)其他構(gòu)造代碼如果使用了該field,而且field要被子類重寫,那么它的getter方法被自動(dòng)重寫,返回初始化的值0(比如Int),因此pes.classScores返回長(zhǎng)度為0的數(shù)組
- 子類的構(gòu)造函數(shù)再執(zhí)行,重寫field(pes.classNum = 3)
- 但是此時(shí)子類從父類繼承的其他構(gòu)造代碼,已經(jīng)出現(xiàn)錯(cuò)誤了(pes.classScores)
此時(shí),只能用Scala對(duì)象繼承的一個(gè)高級(jí)特性:提前定義,在父類構(gòu)造函數(shù)執(zhí)行前,先執(zhí)行子類的構(gòu)造函數(shù)中的某些代碼
class PEStudent extends{
override val classNum: Int = 3
} with Student
Scala繼承層級(jí)
Scala中,最頂端的兩個(gè)trait是Nothing和Null,Null trait唯一的對(duì)象就是null,其次是繼承了Nothing trait的Any類,接著AnyVal trait和AnyRef類,都繼承自Any類。
Any類是一個(gè)比較重要的類,其中定義了inInstanceOf和asInstanceOf等方法,以及equals、hashCode等對(duì)象的基本方法。
AnyRef類,增加了一些多線程的方法,比如wait、notify/notifyAll、synchronized等。
對(duì)象相等性
如何判斷兩個(gè)引用變量,是否指向同一個(gè)對(duì)象實(shí)例?
AnyRef的eq方法用于檢查兩個(gè)變量是否指向同一個(gè)對(duì)象實(shí)例,AnyRef的equals方法默認(rèn)調(diào)用eq方法實(shí)現(xiàn),也就是說,默認(rèn)情況下,判斷兩個(gè)變量相等,要求必須指向同一個(gè)對(duì)象實(shí)例。
此外,定義equals方法時(shí),也最好使用同樣的fields,重寫hashCode方法。
如果只是想簡(jiǎn)單通過是否指向同一個(gè)對(duì)象實(shí)例,判定變量是否相等,那么直接使用==即可
class Product(val name: String, val price: Double){
final override def equals(other: Any) = {
val that = other.asInstanceOf[Product]
if(that == null) false
else name == that.name && price == that.price
}
final override def hashCode = 13 * name.hashCode + 17 * price.hashCode
}
val p1 = new Product("p1", 1.0)
val p2 = new Product("p2", 2.0)
val p3 = p1 // p3 == p1 true
val p4 = new Product("p1", 1.0)
p4 == p1 // true
2.8 文件操作實(shí)戰(zhàn)
遍歷文件中的每一行
import scala.io.Source
/***1. 使用Source.getLines返回***/
val source = Source.fromFile(filePath,"UTF-8")
val lineIterator = source.getLines // 注意getLines調(diào)用后指針在文件尾部,再次調(diào)用返回空
for (line <- lineIterator) println(line)
/***2. 將Source.getLines返回的迭代器,轉(zhuǎn)換成數(shù)組***/
val source = Source.fromFile(filePath,"UTF-8")
val lines = source.getLines.toArray
for (line <- lines) println(line)
/***3. 調(diào)用Source.mkString,返回文本中所有的內(nèi)容***/
val source = Source.fromFile(filePath,"UTF-8")
val lines = source.mkString
source.close()
遍歷一個(gè)文件中的每個(gè)字符
val source = Source.fromFile(filePath,"UTF-8")
for (c <- source) print(c)
從URL以及字符串中讀取字符
val html = Source.fromURL("http://www.baidu.com", "UTF-8")
val str = Source.fromString("Hello World")
結(jié)合Java IO流,文件拷貝
import java.io._
val file = new File(filePath)
var len = file.length.toInt
val bytes = new Array[Byte](len)
val fis = new FileInputStream(file)
val fos = new FileOutputStream(new File(outputFilePath))
fis.read(buf)
fos.write(buf, 0, len)
fis.close()
fos.close()
遞歸遍歷子目錄
import java.io.File
def getDir(dir: File): Iterator[File] = {
val childDirs = dir.listFiles.filter{_.isDirectory}
childDirs.toIterator ++ childDirs.toIterator.flatMap(getDir _)
}
val iterators = getDir("/home/xxx/test")
for (i <- iterators) println(i)
序列化與反序列化(Java)
// 要實(shí)現(xiàn)序列化,一定要有@SerialVersionUID注解,定義一個(gè)版本號(hào),并繼承Serializable trait
@SerialVersionUID(1L) class Person(val name: String) extends Serializable
val leo = new Person("leo")
import java.io._
val oos = new ObjectOutputStream(new FileOutputStream(filePath))
oos.writeObject(leo)
oos.close()
val ois = new ObjectInputStream(new FileOutputStream(filePath))
val restoredLeo = ois.readObject().asInstanceOf[Person]
restoredLeo.name // restoredLeo與Leo不是一個(gè)對(duì)象
2.9 偏函數(shù)實(shí)戰(zhàn)詳解
偏函數(shù)
// 偏函數(shù)是沒有定義好明確的輸入?yún)?shù)的函數(shù),函數(shù)體是一連串的case語(yǔ)句
// 偏函數(shù)是PartialFunction[A, B]類的一個(gè)實(shí)例,該類有兩個(gè)方法,一個(gè)是apply()方法,直接調(diào)用可以通過函數(shù)體內(nèi)的case進(jìn)行匹配,返回結(jié)果;另一個(gè)是isDefinedAt()方法,可以返回一個(gè)輸入,是否跟任何一個(gè)case語(yǔ)句匹配
/***學(xué)生成績(jī)查詢案例***/
val getStudentGrade: PartialFunction[String, Int] = {
case "Leo" => 90; case "Jack" => 85; case "Marry" => 95
}
getStudentGrade("Leo")
getStudentGrade.isDefinedAt("Tom") // False
2.10 執(zhí)行外部命令
scala的程序是運(yùn)行在jvm進(jìn)程中的,如果scala程序想要執(zhí)行scala所在進(jìn)程之外的,比如本地os的命令時(shí),即執(zhí)行外部命令
import sys.process._
"ls -la .." ! //
2.11 正則表達(dá)式支持
// 定義一個(gè)正則表達(dá)式,使用String類的r方法
val pattern1 = "[a-z]+".r
// 獲取一個(gè)字符串中,匹配正則表達(dá)式的部分,使用findAllIn
for (matchString <- pattern1.findAllIn(str)) println(matchString)
// 同理,使用findFirstIn,可以獲取第一個(gè)匹配正則表達(dá)式的部分
pattern1.findFirstIn(str)
// 使用replaceAllIn,可以將匹配正則的部分替換掉
pattern1.replaceAllIn("hello world", "replacement")
// 使用replaceFirstIn,可以將第一個(gè)匹配正則的部分替換掉
pattern1.replaceFirstIn("hello world", "replacement")
2.11 提取器
提取器就是包含了一個(gè)unapply方法的對(duì)象,跟apply方法正好相反。
apply方法,是接受一堆參數(shù),然后構(gòu)造出一個(gè)對(duì)象。
unapply方法,是接受一個(gè)字符串,然后解析出對(duì)象的屬性值。
class Person(val name: String, val age: Int)
Object Person{
def apply(name: String, age: Int) = new Person(name, age)
def unapply(str: String) = {
val splitIndex = str.indexOf(" ") // 按空格切割
if (splitIndex == -1) None // 如果沒有空格
else Some((str.substring(0, splitIndex), str.substring(splitIndex + 1)))
}
}
val Person(name, age) = "leo 25"
name
age
樣例類的提取器
// case class類似于java中的javaBean,javaBean包含了一堆屬性(field),每個(gè)field都有一對(duì)getter和setter
// scala默認(rèn)給case class創(chuàng)建getter和setter方法
case class Person(name: String, age: Int)
val p = Person("leo", 25) // 樣例類默認(rèn)提供apply和unapply方法
p match {
// 模式匹配,如果是Person類,調(diào)用Person的unapply方法
case Person(name, age) => println(name + ": "+ age)
}
只有一個(gè)參數(shù)的提取器
如果你的類只有一個(gè)字段,即字符串里面只有一個(gè)字段,解析出來(lái)的字段是沒有辦法放在tuple中的,因?yàn)閟cala的tuple規(guī)定要2個(gè)及以上的值。
這個(gè)時(shí)候,在unapply方法中,只能將一個(gè)字段值,封裝在Some對(duì)象中返回。
class Person(val name: String)
Object Person {
def unapply(input: String): Option[String] = Some(input)
}
val Person(name) = "leo"
2.15 注解
在scala中可以給類、方法、field、?variable、parameter添加注解,并且scala支持給某個(gè)對(duì)象添加多個(gè)注解。
// 特例:如果要給類的主構(gòu)造函數(shù)添加注解,那么需要在構(gòu)造函數(shù)前添加注解,并加上一對(duì)圓括號(hào)。
class Person @Unchecked() (val name: String, val age: Int)
// 還可以給表達(dá)式添加注解,此時(shí)需要在表達(dá)式后面加上冒號(hào)以及注解,如:
val scores = Map("Leo" -> 90, "Jack" -> 85)
(scores.get("Leo"): @unchecked) match { case score => println(score) }
// 開發(fā)注解:要自己開發(fā)一個(gè)注解,就必須擴(kuò)展Annotation trait
class Test extends annotation.Annotation
@Test
class myTest
// 注解參數(shù)
class Test(var timeout: Int) extends annotation.Annotation
@Test(timeout = 100)
class myTest
常用注解介紹
@volatile var name = "leo" // 輕量級(jí)的java多線程并發(fā)安全控制
jvm中,多線程的每個(gè)線程都有自己的工作區(qū),還有一塊兒所有線程共享的工作區(qū),每次一個(gè)線程拿到一個(gè)公共的變量,都需要從共享區(qū)中拷貝一個(gè)副本到自己的工作區(qū)中使用和修改,修改完以后,再在一個(gè)合適的時(shí)機(jī),將副本的值寫回到共享區(qū)中。這里就會(huì)出現(xiàn)多線程并發(fā)訪問的安全問題。
volatile關(guān)鍵字修飾的變量,可以保證一個(gè)線程在從共享區(qū)獲取一個(gè)變量的副本時(shí),都會(huì)強(qiáng)制刷新一下這個(gè)變量的值,保證自己獲取到的變量的副本值是最新的。但這樣也不是百分百保險(xiǎn),仍可能出現(xiàn)錯(cuò)誤的風(fēng)險(xiǎn)。
@transient var name = "leo" // 瞬態(tài)字段,不會(huì)序列化這個(gè)字段
@SerialVersionUID(value) // 標(biāo)記類的序列化版本號(hào)(類可能在序列化保存之后改變了,這樣反序列化時(shí)會(huì)報(bào)錯(cuò))
@native // 標(biāo)注用c實(shí)現(xiàn)的本地方法
@throws(classOf[Exception]) def test(){} // 給方法標(biāo)記要拋出的checked異常
@varargs def test(args: String*){} // 標(biāo)記方法接受的是變長(zhǎng)參數(shù)
@BeanProperty // 標(biāo)記生成JavaBean風(fēng)格的getter和setter方法
@BooleanBeanProperty // 標(biāo)記生成is風(fēng)格的getter方法,用于boolean類型的field
@deprecated(message = "") // 讓編譯器提示警告
@unchecked // 讓編譯器提示類型轉(zhuǎn)換的警告
2.17 XML基礎(chǔ)操作
scala中定義xml
val books = <books><book>my book</book></books> // scala.xml.Elem,即一個(gè)xml元素
val books = <book>my book</book><book>your book</book> // 多個(gè)平級(jí)的元素,scala.xml.Nodeseq,節(jié)點(diǎn)序列
XML節(jié)點(diǎn)類型
Node類是所有XML節(jié)點(diǎn)類型的父類型,兩個(gè)重要的子類型是Text和Elem。
Elem表示一個(gè)XML元素,也就是一個(gè)XML節(jié)點(diǎn)。scala.xml.Elem類型的label屬性,返回的是標(biāo)簽名,child屬性,返回的是子元素。
scala.xml.NodeSeq類型,是一個(gè)元素序列,可以用for循環(huán),直接遍歷它。可以通過scala.xml.NodeBuffer類型,手動(dòng)創(chuàng)建一個(gè)節(jié)點(diǎn)序列。
val booksBuffer = new scala.xml.NodeBuffer
booksBuffer += <book>book1</book>
booksBuffer += <book>book2</book>
val books: scala.xml.NodeSeq = booksBuffer
xml元素的屬性
// scala.xml.Elem.attributes屬性,可以返回xml元素的屬性,是Seq{scala.xml.Node}類型的,繼續(xù)調(diào)用text屬性,可以獲取屬性的值
val book = <book id="1" price="10.0">book1</book>
val bookID = book.attributes("id").text
// 遍歷屬性
for (attr <- book.attributes) println(attr)
// 獲取屬性Map
book.attributes.asAttrMap
xml中嵌入scala代碼
val books = Array("book1", "book2")
<books><book>{books(0)}</book><book>{books(1)}</book></books>
<books>{ for (book <- books) yield <book>{book}</book>}</books>
// xml屬性中嵌入scala代碼
<book id="{ books(0) }">{ books(0) }</book>
xml修改元素
// scala中的xml表達(dá)式默認(rèn)是不可改變的,必須拷貝一份再修改
val books = <books><book>book1</book></books>
val booksCopy = books.copy(child = books.child ++ <book>book2</book>)
var book = <book id="1">book1</book>
import scala.xml._
// 修改一個(gè)屬性
val bookCopy = book % Attribute(null, "id", "2", Null)
// 添加一個(gè)屬性
val bookCopy = book % Attribute(null, "id", "2", Attribute(null, "price", "10.0", Null))
xml加載和寫入外部文檔
import scala.xml._
import java.io._
// 使用scala的XML類加載
val books = XML.loadFile(filePath)
// 使用Java的FileInputStream類加載
val books = XML.load(new FileInputStream(filePath))
// 用Java的InputStreamReader類加載
val books = XML.load(new InputStreamReader(new FileInputStream(filePath), "UTF-8"))
// 寫入外部xml文檔
XML.save(outFilePath, bookss)
2.21 集合元素操作
集合的常用操作
head last tail
length isEmpty
sum max min
count exists filter filterNot
takeWhile dropWhile
takeRight dropRight
sclie
contains startsWith endsWith
indexOf
intersect diff
map
val scoreMap = Map("leo" -> 90, "jack" -> 60)
val names = List("leo", "jack", "tom")
names.map(scoreMap(_)) // names映射為成績(jī)集合
flatMap
val scoreMap = Map("leo" -> List(80, 90), "jack" -> List(60, 70))
names.flatMap(scoreMap(_)) // List(80, 90, 60, 70)
collect
"abc".collect { case 'a' => 1; case 'b' => 2; case 'c' => 3} // Vector(1, 2, 3),"abc"中每個(gè)元素與偏函數(shù)匹配
foreach
names.foreach(println _)
reduce
List(1, 2, 3, 4).reduceLeft(_ - _) // 1-2-3-4
List(1, 2, 3, 4).reduceRight(_ - _) // 4-3-2-1
fold
List(1, 2, 3, 4).foldLeft(10){ (m, n) => println(m+": "+n); m -n } // 給定起始元素 10-1-2-3-4
List(1, 2, 3, 4).foldRight(10)(_ - _) // 不一樣,1-(2-(3-(4-10))))


