Scaladoc是什么:scala api文檔,包含了scala所有的api以及使用說明,class、object、trait、function、method、implicit等
為什么要查閱Scaladoc:如果只是寫一些普通的Scala程序,課程中講解(Scala編程詳解)的內(nèi)容基本夠用了;但是如果(在現(xiàn)在,或者未來,實(shí)際的工作環(huán)境中)要編寫復(fù)雜的scala程序,那么還是需要參考Scaladoc的。(純粹用scala開發(fā)spark應(yīng)用程序,應(yīng)該不會(huì)特別復(fù)雜;用scala構(gòu)建類似于spark的公司內(nèi)的分布式的大型系統(tǒng))
通過url:http://www.scala-lang.org/api/current/#package,可以在線瀏覽Scaladoc
以下是一些Scaladoc使用的tips(小貼士,小備注):
1、直接在左上角的搜索框中,搜索你需要的尋找的包、類即可
2、O和C,分別代表了某個(gè)類的伴生對(duì)象以及伴生類的概念
3、標(biāo)記為implicit的方法,代表的是隱式轉(zhuǎn)換
4、舉例:搜索StringOps,可以看到String的增強(qiáng)類,StringOps的所有方法說明
Scala編程進(jìn)階:跳出循環(huán)語句的3種方法
方法一:使用boolean控制變量
while循環(huán):
var flag = true
var res = 0
var n = 0
while(flag) {
res += n
n += 1
if (n == 5) {
flag = false
}
}
for循環(huán):(高級(jí)for循環(huán),加上了if守衛(wèi))
var flag = true
var res = 0
for (i <- 0 until 10 if flag) {
res += i
if (i == 4) flag = false
}
方法二:在嵌套函數(shù)中使用return
def add_outer() = {
var res = 0
def add_inner() {
for (i <- 0 until 10) {
if (i == 5) {
return
}
res += i
}
}
add_inner()
res
}
方法三:使用Breaks對(duì)象的break方法
跟java里面的break比較類似,相對(duì)來說,比較靈活好用;與breakable代碼塊配合使用
import scala.util.control.Breaks._
var res = 0
breakable {
for (i <- 0 until 10) {
if (i == 5) {
break;
}
res += i
}
}
Scala編程進(jìn)階:多維數(shù)組、Java數(shù)組與Scala數(shù)組的隱式轉(zhuǎn)換
多維數(shù)組
什么是多維數(shù)組?:數(shù)組的元素,還是數(shù)組,數(shù)組套數(shù)組,就是多維數(shù)組
構(gòu)造指定行與列的二維數(shù)組:Array.ofDim方法
val multiDimArr1 = Array.ofDim[Double](3, 4)
multiDimArr1(0)(0) = 1.0
構(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ù)組緩沖的隱式轉(zhuǎn)換
Scala代碼中,直接調(diào)用JDK(Java)的API,比如調(diào)用一個(gè)Java類的方法,勢(shì)必可能會(huì)傳入Java類型的list;Scala中構(gòu)造出來的list,其實(shí)是ArrayBuffer;你直接把Scala的ArrayBuffer傳入Java接收ArrayList的方法,肯定不行。
import scala.collection.JavaConversions.bufferAsJavaList
import scala.collection.mutable.ArrayBuffer
val command = ArrayBuffer("javac", "C:\\Users\\Administrator\\Desktop\\HelloWorld.java")
val processBuilder = new ProcessBuilder(command)
val process = processBuilder.start()
val res = process.waitFor()
import scala.collection.JavaConversions.asScalaBuffer
import scala.collection.mutable.Buffer
val cmd: Buffer[String] = processBuilder.command()
Tuple拉鏈操作、Java Map與Scala Map的隱式轉(zhuǎn)換
Tuple拉鏈操作
Tuple拉鏈操作指的就是zip操作
zip操作,是Array類的方法,用于將兩個(gè)Array,合并為一個(gè)Array
比如Array(v1)和Array(v2),使用zip操作合并后的格式為Array((v1,v2))
合并后的Array的元素類型為Tuple
val students = Array("Leo", "Jack", "Jen")
val scores = Array(80, 100, 90)
val studentScores = students.zip(scores)
for ((student, score) <- studentScores)
println(student + " " + score)
如果Array的元素類型是個(gè)Tuple,調(diào)用Array的toMap方法,可以將Array轉(zhuǎn)換為Map
studentScores.toMap
Java Map與Scala Map的隱式轉(zhuǎn)換
import scala.collection.JavaConversions.mapAsScalaMap
val javaScores = new java.util.HashMap[String, Int]()
javaScores.put("Alice", 10)
javaScores.put("Bob", 3)
javaScores.put("Cindy", 8)
val scalaScores: scala.collection.mutable.Map[String, Int] = javaScores
import scala.collection.JavaConversions.mapAsJavaMap
import java.awt.font.TextAttribute._
val scalaAttrMap = Map(FAMILY -> "Serif", SIZE -> 12)
val font = new java.awt.Font(scalaAttrMap)
擴(kuò)大內(nèi)部類作用域的2種方法、內(nèi)部類獲取外部類引用
內(nèi)部類的作用域:外部類對(duì)象
import scala.collection.mutable.ArrayBuffer
class Class {
class Student(val name: String)
val students = new ArrayBuffer[Student]
def register(name: String) = {
new Student(name)
}
}
val c1 = new Class
val leo = c1.register("leo")
c1.students += leo
val c2 = new Class
val jack = c2.register("jack")
c1.students += jack
擴(kuò)大內(nèi)部類作用域:伴生對(duì)象
object Class {
class Student(val name: String)
}
class Class {
val students = new ArrayBuffer[Class.Student]
def register(name: String) = {
new Class.Student(name)
}
}
val c1 = new Class
val leo = c1.register("leo")
c1.students += leo
val c2 = new Class
val jack = c2.register("jack")
c1.students += jack
擴(kuò)大內(nèi)部類作用域:類型投影
class Class {
class Student(val name: String)
val students = new ArrayBuffer[Class#Student]
def register(name: String) = {
new Student(name)
}
}
val c1 = new Class
val leo = c1.register("leo")
c1.students += leo
val c2 = new Class
val jack = c2.register("jack")
c1.students += jack
內(nèi)部類獲取外部類的引用
class Class(val name: String) { outer =>
class Student(val name: String) {
def introduceMyself = "Hello, I'm " + name + ", I'm very happy to join class " + outer.name
}
def register(name: String) = {
new Student(name)
}
}
val c1 = new Class("c1")
val leo = c1.register("leo")
leo.introduceMyself
package與import實(shí)戰(zhàn)詳解
為什么要有package的概念?
因?yàn)橐獙?duì)多個(gè)同名的類進(jìn)行命名空間的管理,避免同名類發(fā)生沖突
比如說,scala.collection.mutable.Map和scala.collection.immutable.Map
package
package定義的第一種方式: 多層級(jí)package定義(比較差的做法,一般不這么干)
package com {
package ibeifeng {
package scala {
class Test {}
}
}
}
package定義的第二種方式: 串聯(lián)式package定義(也不怎么樣,一般也不這么干)
package com.ibeifeng.scala {
package service {
class Test {}
}
}
package定義的第三種方式: 文件頂部package定義
package com.ibeifeng.scala.service
class Test {
}
package定義的第四種方式: IDE自動(dòng)生成包
- package特性一: 同一個(gè)包定義,可以在不同的scala源文件中的; 一個(gè)scala源文件內(nèi),可以包含兩個(gè)包
//Test1.scala
package com {
package ibeifeng {
package scala {
class Test1
}
}
}
//Test2.scala
package com {
package ibeifeng {
package scala {
class Test2
}
}
}
//Test3.scala
package com {
package ibeifeng {
package scala1 {
class Test
}
}
}
package com {
package ibeifeng {
package scala2 {
class Test
}
}
}
- package特性二: 子包中的類,可以訪問父包中的類
//Test.scala
package com {
package ibeifeng {
package scala {
object Utils {
def isNotEmpty(str: String): Boolean = str != null && str != ""
}
class Test
package service {
class MyService {
def sayHello(name: String) {
if(Utils.isNotEmpty(name)) {
println("Hello, " + name)
} else {
println("Who are you?")
}
}
}
}
}
}
}
object MainClass {
def main(args: Array[String]): Unit = {
val service = new com.ibeifeng.scala.service.MyService
service.sayHello("leo")
service.sayHello("")
}
}
- package特性三: 相對(duì)包名與絕對(duì)包名
package com {
package ibeifeng {
package scala {
object Utils {
def isNotEmpty(str: String): Boolean = str != null && str != ""
}
class Test
package collection {}
package service {
class MyService {
// 這會(huì)報(bào)錯(cuò),默認(rèn)使用相對(duì)報(bào)名,從com.ibeifeng.scala.collection包中,尋找mutable包下的ArrayBuffer類
// 但是找不到,所以會(huì)報(bào)錯(cuò)
// val names = new scala.collection.mutable.ArrayBuffer[String]
// 正確的做法是使用_root_,引用絕對(duì)包名
val names = new _root_.scala.collection.mutable.ArrayBuffer[String]
def sayHello(name: String) {
if(Utils.isNotEmpty(name)) {
println("Hello, " + name)
} else {
println("Who are you?")
}
}
}
}
}
}
}
- package特性四: 定義package對(duì)象(比較少)
package內(nèi)的成員,可以直接訪問package對(duì)象內(nèi)的成員
package com.ibeifeng.scala
package object service {
val defaultName = "Somebody"
}
package service {
class MyService {
def sayHello(name: String) {
if(name != null && name != "") {
println("Hello, " + name)
} else {
println("Hello, " + defaultName)
}
}
}
}
- package特性五: package可見性
package com.ibeifeng.scala
class Person {
private[scala] val name = "leo"
private[ibeifeng] val age = 25
}
import
如果沒有import,那么。。。你每次創(chuàng)建某個(gè)包下的類的對(duì)象,都得用new com.ibeifeng.scala.service.MyService這種冗長的格式。。。
所以如果用了import,那么。。。你只要先import com.ibeifeng.scala.service.MyService,然后再new MyService,即可。。。
import com.ibeifeng.scala.service.MyService;
object MainClass {
def main(args: Array[String]): Unit = {
val service = new MyService
service.sayHello("leo")
service.sayHello("")
}
}
import特性一: 用import com.ibeifeng.scala.service._這種格式,可以導(dǎo)入包下所有的成員
import特性二: scala與java不同之處在于,任何地方都可以使用import,比如類內(nèi)、方法內(nèi),這種方式的好處在于,可以在一定作用域范圍內(nèi)使用導(dǎo)入
object MainClass {
def main(args: Array[String]): Unit = {
import com.ibeifeng.scala.service._
val service = new MyService
service.sayHello("leo")
service.sayHello("")
}
}
import特性三: 選擇器、重命名、隱藏
import com.ibeifeng.scala.service.{ MyService },僅僅導(dǎo)入java.awt包下的Color和Font類
import com.ibeifeng.scala.service.{ MyService => MyServiceImpl },將導(dǎo)入的類進(jìn)行重命名
import com.ibeifeng.scala.service.{ MyService => _, _ },導(dǎo)入java.util包下所有的類,但是隱藏掉HashMap類import特性四: 隱式導(dǎo)入
每個(gè)scala程序默認(rèn)都會(huì)隱式導(dǎo)入以下幾個(gè)包下所有的成員
import java.lang._
import scala._
import Predef._
重寫field的提前定義、Scala繼承層級(jí)、對(duì)象相等性
重寫field的提前定義
默認(rèn)情況下,如果父類中的構(gòu)造函數(shù)代碼,用到了會(huì)被子類重寫的field; 那么出出現(xiàn)令人意想不到的一幕:
1、子類的構(gòu)造函數(shù)(無參)調(diào)用父類的構(gòu)造函數(shù)(無參)
2、父類的構(gòu)造函數(shù)初始化field(結(jié)果正確)
3、父類的構(gòu)造函數(shù)使用field執(zhí)行其他構(gòu)造代碼,但是此時(shí)其他構(gòu)造代碼如果使用了該field,而且field要被子類重寫,那么它的getter方法被重寫,返回0(比如Int)
4、子類的構(gòu)造函數(shù)再執(zhí)行,重寫field(結(jié)果也正確)
5、但是此時(shí)子類從父類繼承的其他構(gòu)造代碼,已經(jīng)出現(xiàn)了錯(cuò)誤了
class Student {
val classNumber: Int = 10
val classScores: Array[Int] = new Array[Int](classNumber)
}
class PEStudent {
override val classNumber: Int = 3
}
本來我們期望的是,PEStudent,可以從Student繼承來一個(gè)長度為3的classScores數(shù)組
結(jié)果。。。PEStudent對(duì)象,只有一個(gè)長度為0的classScores數(shù)組
此時(shí)只能使用Scala對(duì)象繼承的一個(gè)高級(jí)特性: 提前定義,在父類構(gòu)造函數(shù)執(zhí)行之前,先執(zhí)行子類的構(gòu)造函數(shù)中的某些代碼
class PEStudent extends student {
override val classNumber: Int = 3
} with Student
Scala的繼承層級(jí)
這里我們大概知道一下Scala的繼承層級(jí),我們寫的所有的Scala trait和class,都是默認(rèn)繼承自一些Scala根類的,有一些基礎(chǔ)的方法
Scala中,最頂端的兩個(gè)trait是Nothing和Null,Null trait唯一的對(duì)象就是null
其次是繼承了Nothing trait的Any類
接著Anyval trait和AnyRef類,都繼承自Any類
Any類是個(gè)比較重要的類,其中定義了isInstanceOf和asInstanceOf等方法,以及equals、hashCode等對(duì)象的基本方法
Any類,有點(diǎn)像Java中的Object基類
AnyRef類,增加了一些多線程的方法,比如wait、notify/notifyAll、synchronized等,也是屬于Java Object類的一部分
對(duì)象相等性
這里,我們要知道,在scala中,你如何判斷兩個(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方法,根據(jù)類的fields來判定是否相等
此外,定義equals方法時(shí),也最好使用同樣的fields,重寫hashCode方法
如果只是想要簡(jiǎn)單地通過是否指向同一個(gè)對(duì)象實(shí)例,判定變量是否相當(dāng),那么直接使用==操作符即可,默認(rèn)判斷null,然后調(diào)用equals方法
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
}
文件操作實(shí)戰(zhàn)詳解
遍歷一個(gè)文件中的每一行
必須導(dǎo)入scala.io.Source類: import scala.io.Source
方法一: 使用Source.getLines返回的迭代器
val source = Source.fromFile("C://Users//Administrator//Desktop//test.txt", "UTF-8")
val lineIterator = source.getLines
for (line <- lineIterator) println(line)
方法二: 將Source.getLines返回的迭代器,轉(zhuǎn)換成數(shù)組
這里說明一點(diǎn): 一個(gè)BufferedSource對(duì)象的getLines方法,只能調(diào)用一次,一次調(diào)用完之后,遍歷了迭代器里所有的內(nèi)容,就已經(jīng)把文件里的內(nèi)容讀取完了
如果反復(fù)調(diào)用source.getLines,是獲取不到內(nèi)容的
此時(shí),必須重新創(chuàng)建一個(gè)BufferedSource對(duì)象
val source = Source.fromFile("C://Users//Administrator//Desktop//test.txt", "UTF-8")
val lines = source.getLines.toArray
for(line <- lines) println(line)
方法三: 調(diào)用Source.mkString,返回文本中所有的內(nèi)容
val source = Source.fromFile("C://Users//Administrator//Desktop//test.txt", "UTF-8")
val lines = source.mkString
使用完BufferedSource對(duì)象之后,調(diào)用BufferedSource.close方法,關(guān)閉IO流資源
遍歷一個(gè)文件中的每一個(gè)字符
BufferedSource,也實(shí)現(xiàn)了一個(gè)Iterator[Char]的這么一個(gè)trait
val source = Source.fromFile("C://Users//Administrator//Desktop//test.txt", "UTF-8")
for(c <- source) print(c)
從URL以及字符串中讀取字符
val source = Source.fromURL("http://www.baidu.com", "UTF-8")
val source = Source.fromString("Hello World")
結(jié)合Java IO流,讀取任意文件
這里說明一點(diǎn),大家千萬不要以為,spark就是會(huì)scala就可以了
也千萬不要以為,scala,就是跟java一點(diǎn)關(guān)系都沒有,甚至于完全可以替代java
上述說法,都是很荒謬的,都是門外漢才會(huì)這么認(rèn)為
如果你真的深入讀了spark的源代碼
真的對(duì)scala掌握的很深入,你就會(huì)知道一點(diǎn)
spark的源碼實(shí)際上是由scala和java共同編寫而成的,Java的多線程
scala,本身的編程語言的功能,就不是特別的強(qiáng)大和完善,比如說,scala甚至不能很方便地寫文件,必須依賴于java的io流才可以
所以說,scala,其實(shí)主要就是針對(duì)某些特定領(lǐng)域的一些復(fù)雜系統(tǒng)的,比較適用的一種編程語言而已
完全無法替代java的,scala和java是相輔相成,榮辱與共的這么一種,共生關(guān)系
可以這么跟大家說
scala還有一種作用,可以用scala,編寫spark的作業(yè)
但是問題是,為什么,我們要用java開發(fā)hive udf、mapreduce、hbase client、zookeeper client,用Java開發(fā)storm的作業(yè)
然后作為一個(gè)大數(shù)據(jù)工程師,偏偏用到spark的時(shí)候,一定要用scala開發(fā)呢?
用spark開發(fā)作業(yè),用java,個(gè)人認(rèn)為,個(gè)人觀點(diǎn),是最合適的,最通用的,最可移植的,最方便維護(hù)的
scala,這套課程里,scala編程詳解、scala編程進(jìn)階
1、有些公司的技術(shù)leader,要求用scala開發(fā)spark作業(yè),我也沒辦法,我是極力反對(duì)的; 保證學(xué)員,學(xué)了這套課程以后,可以用scala去工作和面試
2、有些同學(xué),可能壓根兒不會(huì)java; 大多數(shù)是上學(xué)的時(shí)候,主要是搞算法的,或者只會(huì)c++,只會(huì)python; 這套課程學(xué)了,不用會(huì)java,那么也可以精通和使用spark
3、最重要的一點(diǎn),深入掌握scala所有的初中高級(jí)語法,才能透徹和深入的理解和閱讀spark的源碼
4、也有,但是很少,就是有些公司,可能會(huì)用scala,開發(fā)復(fù)雜的大型分布式后端系統(tǒng)
案例: 結(jié)合java IO流,做一個(gè)文件拷貝的案例
import java.io._
val fis = new FileInputStream(new File("C://Users//Administrator//Desktop//test.txt"))
val fos = new FileOutputStream(new File("C://Users//Administrator//Desktop//test3.txt"))
val buf = new Array[Byte](1024)
fis.read(buf)
fos.write(buf, 0, 1024)
fis.close()
fos.close()
結(jié)合Java IO流,寫文件
val pw = new PrintWriter("C://Users//Administrator//Desktop//test4.txt")
pw.println("Hello World")
pw.close()
遞歸遍歷子目錄
def getSubdirIterator(dir: File): Iterator[File] = {
val childDirs = dir.listFiles.filter(_.isDirectory)
childDirs.toIterator ++ childDirs.toIterator.flatMap(getSubdirIterator _)
}
val iterator = getSubdirIterator(new File("C://Users//Administrator//Desktop"))
for(d <- iterator) println(d)
序列化以及反序列化(Java序列化和反序列化機(jī)制)
如果要序列化,那么就必須讓類,有一個(gè)@SerialVersionUID,定義一個(gè)版本號(hào)
要讓類繼承一個(gè)Serializable trait
@SerialVersionUID(42L) class Person(val name: String) extends Serializable
val leo = new Person("leo")
import java.io._
val oos = new ObjectOutputStream(new FileOutputStream("C://Users//Administrator//Desktop//test.obj"))
oos.writeObject(leo)
oos.close()
val ois = new ObjectInputStream(new FileInputStream("C://Users//Administrator//Desktop//test.obj"))
val restoredLeo = ois.readObject().asInstanceOf[Person]
restoredLeo.name
偏函數(shù)實(shí)戰(zhàn)詳解
偏函數(shù),是一種高級(jí)的函數(shù)形式
簡(jiǎn)單來說,偏函數(shù)是什么,其實(shí)就是沒有定義好明確的輸入?yún)?shù)的函數(shù),函數(shù)體就是一連串的case語句
一般的函數(shù)
def getStudentGrade(name: String) = {
...
}
偏函數(shù)是PartialFunction[A, B]類的一個(gè)實(shí)例
這個(gè)類有兩個(gè)方法,一個(gè)是apply()方法,直接調(diào)用可以通過函數(shù)體內(nèi)的case進(jìn)行匹配,返回結(jié)果;
另一個(gè)是isDefinedAt()方法,可以返回一個(gè)輸入,是否跟任何一個(gè)case語句匹配
學(xué)生成績(jī)查詢案例
val getStudentGrade: PartialFunction[String, Int] = {
case "Leo" => 90; case "Jack" => 85; case "Marry" => 95
}
getStudentGrade("Leo")
getStudentGrade.isDefinedAt("Tom")
執(zhí)行外部命令
scala執(zhí)行外部命令
咱們的scala程序,實(shí)際上,寫好以后,跑起來,關(guān)鍵是,跑在哪里?
scala程序是運(yùn)行在java虛擬機(jī)中的,也就是咱們平時(shí)常說的jvm,所以我們之前能夠看到,scala可以直接調(diào)用jdk
jdk: java development kit,java基礎(chǔ)的開發(fā)api
scala的程序,是運(yùn)行在一個(gè)進(jìn)程中的
運(yùn)行在什么進(jìn)程中?是運(yùn)行在jvm虛擬機(jī)進(jìn)程中的
比如說,如果說,我們的scala程序,希望去執(zhí)行scala所在進(jìn)程之外的,比如說,本地操作系統(tǒng)的一個(gè)命令
也許執(zhí)行的本地操作系統(tǒng)的命令,會(huì)啟動(dòng)一個(gè)新的進(jìn)程,也許也不會(huì)
但是,如果想要實(shí)現(xiàn)這樣的效果和功能,scala能不能夠做到?
這個(gè)是可以的
scala實(shí)際上,是提供了這樣的支持的
也就是說,咱們的scala程序,運(yùn)行在一個(gè)獨(dú)立的進(jìn)程中,但是可以隨心所欲地執(zhí)行外部操作系統(tǒng)的其他命令
甚至是說,啟動(dòng)其他的進(jìn)程
案例: 使用scala編譯和執(zhí)行外部的java程序
//HelloWorld.java
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello World");
System.out.println("Hello Leo");
}
}
import sys.process._
"javac HelloWorld.java" !
"java HelloWorld" !
正則表達(dá)式支持
scala的正則表達(dá)式支持
正則表達(dá)式是什么?
一種語法,用一個(gè)表達(dá)式,來匹配一系列的字符串
[a-z]+: 一個(gè)或多個(gè)a~z范圍的26個(gè)小寫英文字母,比如hello,world
強(qiáng)調(diào)一點(diǎn),咱們不會(huì)去給大家詳細(xì)介紹正則表達(dá)式的語法
正則表達(dá)式這個(gè)東西,語法比較多
有興趣,有需要的話,那么建議直接百度一下,自己看一下介紹,就知道是怎么回事了
大致學(xué)習(xí)一下正則表達(dá)式的語法即可
我們這里呢,講解一下scala中,對(duì)這個(gè)正則表達(dá)式的支持
定義一個(gè)正則表達(dá)式,使用String類的r方法
此時(shí)返回的類型是scala.util.matching.Regex類的對(duì)象
val pattern1 = "[a-z]+".r
拿到一個(gè)正則表達(dá)式以后,我們一般會(huì)用它來做什么事情?
比如,我們會(huì)用正則表達(dá)式來匹配一些字符串,比如來看看,某個(gè)字符串是否符合表達(dá)式規(guī)定的范圍之內(nèi)
比如,從一個(gè)長的字符串中,提取出來,匹配正則表達(dá)式的各個(gè)部分
val str = "hello 123 world 456"
獲取一個(gè)字符串中,匹配正則表達(dá)式的部分,使用findAllIn,會(huì)獲取到一個(gè)Iterator,迭代器
然后就可以去遍歷各個(gè)匹配正則的部分,去進(jìn)行處理
for (matchString <- pattern1.findAllIn(str)) println(matchString)
同理,使用findFirstIn,可以獲取第一個(gè)匹配正則表達(dá)式的部分
pattern1.findFirstIn(str)
使用replaceAllIn,可以將匹配正則的部分,替換掉
pattern1.replaceFirstIn("hello world", "replacement")
使用replaceFirstIn,可以將第一個(gè)匹配正則的部分,替換掉
pattern1.replaceAllIn("hello world", "replacement")
提取器實(shí)戰(zhàn)詳解
apply方法
伴生類和伴生對(duì)象的概念,companion class和companion object
伴生對(duì)象里面,可以定義一個(gè)apply方法
直接調(diào)用類(參數(shù)),方式,就相當(dāng)于在調(diào)用apply方法
此時(shí)在apply方法中,通常來說(也不一定),會(huì)創(chuàng)建一個(gè)伴生類的對(duì)象,返回回去
這種方式,有一個(gè)好處,創(chuàng)建對(duì)象呢,非常的方便
不要每次都是new 類(參數(shù)),類(參數(shù))
unapply方法
和apply方法,顧名思義,那就是反過來
apply方法,可以理解為,接收一堆參數(shù),然后返回一個(gè)對(duì)象
unapply方法,可以理解為,接收一個(gè)字符串,解析成一個(gè)對(duì)象的各個(gè)字段
提取器就是一個(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
樣例類的提取器實(shí)戰(zhàn)詳解
樣例類的提取器
scala中的樣例類,說白了,也很簡(jiǎn)單
類似于java中的javabean,java中的JavaBean,是什么東東?
包含了一堆屬性,field; 每個(gè)field都有一對(duì)getter和setter方法
public class Person {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
scala中的樣例類,默認(rèn)就是提供apply方法和unapply方法的
case class Person(name: String, age: Int)
val p = Person("leo", 25)
p match {
case Person(name, age) => println(name + ": " + age)
}
只有一個(gè)參數(shù)的提取器
之前,已經(jīng)跟大家講解過普通的提取器
相當(dāng)于是,比如說,接收一個(gè)字符串,作為參數(shù)
然后從字符串里面解析出來多個(gè)字段值,然后將多個(gè)字段值封裝在一個(gè)tuple中
作為Some類型的對(duì)象,返回
現(xiàn)在我們來想一下,如果你的類只有一個(gè)字段
字符串里面只有一個(gè)字段
解析出來的一個(gè)字段,是沒有辦法放在tuple中的,因?yàn)閟cala中的tuple,規(guī)定了,必須要兩個(gè)以及兩個(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"
注解實(shí)戰(zhàn)詳解
什么是注解?
注解其實(shí)說白了,就是在我們的代碼中,加入一些特殊的標(biāo)記
特殊的標(biāo)記大概長什么樣子呢?
我們之前學(xué)過一個(gè)很常用,和很經(jīng)典的一個(gè)注解,其實(shí)就是@BeanProperty,讓編譯器自動(dòng)生成屬性的JavaBean風(fēng)格的getter和setter方法
除此之外,還在《文件操作實(shí)戰(zhàn)詳解》那一講,講過一個(gè)序列化的這個(gè)東西,@SerialUID(可能是錯(cuò)誤的),指定一個(gè)序列化的版本號(hào)
注解是用來干嘛的?
然后我們的scala編譯器,就可以在編譯的時(shí)候,在碰到注解的時(shí)候,做一些特殊的操作。一個(gè)非常經(jīng)典的例子就是
@BeanProperty注解,我們之前講解過,給某個(gè)field添加了這個(gè)注解之后,scala編譯器就會(huì)給field編譯出新的JavaBean風(fēng)格的getter和setter方法
scala中,在哪些地方可以添加注解呢?
scala中,可以給類、方法、field、local variable、constructor / method / function parameter添加注解
而且scala是支持給某個(gè)目標(biāo)添加多個(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" -> 60)
(scores.get("Leo"): @unchecked) match { case score => println(score) }
除此之外,還可以給類型參數(shù)和變量的類型定義添加注解
Scala中開發(fā)注解
要自己動(dòng)手開發(fā)一個(gè)注解,就必須擴(kuò)展Annotation trait,比如
class Test extends annotation.Annotation
@Test
class myTest
注解的參數(shù)
注解中,是可以有參數(shù)的,比如
class Test(var timeout: Int) extends annotation.Annotation
@Test(timeout = 100) class myTest
如果注解的參數(shù)是value的話,那么也可以不用指定注解的參數(shù)名,比如
class Test(var value: String) extends annotation.Annotation
常用注解介紹
dscala提供的常用注解
其實(shí)里面很多都是針對(duì)java中的概念和概念提供的
跟大家提示一下,再次證明了,你搞scala,真想搞好的話,先學(xué)和精通java
scala和java的關(guān)系是唇齒相依的
scala依賴java
java并不依賴scala
scala中,常用的一些注解,全部是針對(duì)java的一些概念
所以呢,在這里我們沒辦法給大家詳細(xì)講解java的概念,就直接介紹scala中針對(duì)java的一些注解
@volatile var name = "leo" 輕量級(jí)的java多線程并發(fā)安全控制
jvm,java虛擬機(jī)中,可以有多個(gè)線程
每個(gè)線程都有自己的工作區(qū),還有一塊兒所有線程共享的工作區(qū)
每次一個(gè)線程拿到一個(gè)公共的變量,都需要從共享區(qū)中拷貝一個(gè)副本到自己的工作區(qū)中使用,和修改
然后修改完以后,再在一個(gè)合適的時(shí)機(jī),將副本的值,寫回到共享區(qū)中
這里就會(huì)出現(xiàn)一個(gè)多線程并發(fā)訪問安全的問題
多個(gè)線程如果同時(shí)拷貝了變量副本,都做了不同的修改
然后依次將副本修改的值,寫回到共享區(qū)中,會(huì)依次覆蓋掉之前的一些副本值
就會(huì)出現(xiàn)變量的值,是不符合預(yù)期的
咱們的系統(tǒng),出現(xiàn)了錯(cuò)誤和bug
volatile關(guān)鍵字修飾的變量
它可以保證,一個(gè)線程在從共享區(qū)獲取一個(gè)變量的副本時(shí),都會(huì)強(qiáng)制刷新一下這個(gè)變量的值
保證自己獲取到的變量的副本值是最新的
所以這樣子做呢,是一種輕量級(jí)的多線程并發(fā)訪問控制辦法
但是也不是百分之百保險(xiǎn)的,還是有可能會(huì)出現(xiàn)錯(cuò)誤的風(fēng)險(xiǎn)
@transient var name = "leo" 瞬態(tài)字段,不會(huì)序列化這個(gè)字段
之前講序列化,默認(rèn)會(huì)將一個(gè)對(duì)象中所有的字段的值,都序列化到磁盤文件中去
然后反序列化的時(shí)候,還可以獲取這些字段的值
加了transient的字段,是瞬態(tài)的,序列化的時(shí)候,不會(huì)序列化這個(gè)字段
反序列化的時(shí)候,這個(gè)字段也就沒有值了
@SerialVersionUID(value) 標(biāo)記類的序列化版本號(hào)
序列化版本號(hào),這個(gè)什么意思
如果我們將一個(gè)類的對(duì)象序列化到磁盤文件上了
結(jié)果過了一段時(shí)間以后,這個(gè)類在代碼中改變了,此時(shí)如果你想將磁盤文件中的對(duì)象反序列化回來
就會(huì)報(bào)錯(cuò),因?yàn)槟愕男蛄谢膶?duì)象的結(jié)構(gòu)與代碼中的類結(jié)構(gòu)已經(jīng)不一樣了
針對(duì)這種問題,就應(yīng)該有一個(gè)序列化版本號(hào)
如果你的類改變了,就重新生成一個(gè)序列化版本號(hào)
反序列化的時(shí)候,就會(huì)發(fā)現(xiàn)序列化類型的版本號(hào)和代碼中的類的版本號(hào),不一樣
@native 標(biāo)注用c實(shí)現(xiàn)的本地方法
@throws(classOf[Exception]) def test() {} 給方法標(biāo)記要拋出的checked異常
@varargs def test(args: String*) {} 標(biāo)記方法接收的是變長參數(shù)
@BeanProperty 標(biāo)記生成JavaBean風(fēng)格的getter和setter方法
@BooleanBeanProperty 標(biāo)記生成is風(fēng)格的getter方法,用于boolean類型的field
@deprecated(message = "") 讓編譯器提示警告
@unchecked 讓編譯器提示類型轉(zhuǎn)換的警告
XML基礎(chǔ)操作實(shí)戰(zhàn)詳解
scala中定義xml
scala對(duì)xml有很好的支持,可以直接在scala代碼中定義一個(gè)xml文檔元素
val books = <books><book>my first scala book</book></books>
此時(shí)doc的類型是scala.xml.Elem,也就是一個(gè)xml元素
scala還可以直接定義多個(gè)同級(jí)別的xml元素
val books = <book>my first scala book</book><book>my first spark book</book>
此時(shí)doc的類型是scala.xml.NodeBuffer,也就是一個(gè)xml節(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)
還可以調(diào)用book.attributes.asAttrMap,獲取一個(gè)屬性Map
XML中嵌入scala代碼
在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修改元素實(shí)戰(zhàn)詳解
修改xml元素
默認(rèn)情況下,scala中的xml表達(dá)式是不可改變的;如果要修改xml元素的話,必須拷貝一份再修改
val books = <books><book>book1</book></books>
添加一個(gè)子元素
val booksCopy = books.copy(child = books.child ++ <book>book2</book>)
val 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))
說點(diǎn)閑話
如果大家真的對(duì)java比較精通的話
然后過來學(xué)習(xí)這個(gè)scala,就會(huì)發(fā)現(xiàn)有個(gè)特點(diǎn)
java的功能是非常強(qiáng)大的
但是,從各個(gè)方面來看,比如io、xml操作、第三方類庫的支持、socket、gui界面編程、jdbc訪問數(shù)據(jù)庫等等,scala都比java差很多
之所以現(xiàn)在scala有點(diǎn)火,有些人推崇這個(gè)scala
其實(shí)主要是因?yàn)閟park是用scala作為主要的語言開發(fā)的(但是spark底層的源碼,其實(shí)都是java)
類加載器、線程、反射、線程池等等這些東西,全部都是java底層,外部命令的執(zhí)行(ProcessBuilder)
XML加載和寫入外部文檔
加載和寫入外部xml文件
import scala.xml._
import java.io._
使用scala的XML類加載
val books = XML.loadFile("C://Users//Administrator//Desktop//books.xml")
使用Java的FileInputStream類加載
val books = XML.load(new FileInputStream("C://Users//Administrator//Desktop//books.xml"))
使用Java的InputStreamReader類指定加載編碼
val books = XML.load(new InputStreamReader(new FileInputStream("C://Users//Administrator//Desktop//books.xml"), "UTF-8"))
將內(nèi)存中的xml對(duì)象,寫入外部xml文檔
XML.save("C://Users//Administrator//Desktop//books2.xml", books)
集合元素操作
col :+ ele 將元素添加到集合尾部 Seq
ele +: col 將元素添加到集合頭部 Seq
col + ele 在集合尾部添加元素 Set、Map
col + (ele1, ele2) 將其他集合添加到集合的尾部 Set、Map
col - ele 將元素從集合中刪除 Set、Map、ArrayBuffer
col - (ele1, ele2) 將子集合從集合中刪除 Set、Map、ArrayBuffer
col1 ++ col2 將其他集合添加到集合尾部 Iterable
col2 ++: col1 將其他集合添加到集合頭部 Iterable
ele :: list 將元素添加到list的頭部 List
list2 ::: list1 將其他list添加到list的頭部 List
list1 ::: list2 將其他list添加到list的尾部 List
set1 | set2 取兩個(gè)set的并集 Set
set1 & set2 取兩個(gè)set的交集 Set
set1 &~ set2 取兩個(gè)set的diff Set
col += ele 給集合添加一個(gè)元素 可變集合
col += (ele1, ele2) 給集合添加一個(gè)集合 可變集合
col ++= col2 給集合添加一個(gè)集合 可變集合
col -= ele 從集合中刪除一個(gè)元素 可變集合
col -= (ele1, ele2) 從集合中刪除一個(gè)子集合 可變集合
col —= col2 從集合中刪除一個(gè)子集合 可變集合
ele +=: col 向集合頭部添加一個(gè)元素 ArrayBuffer
col2 ++=: col 向集合頭部添加一個(gè)集合 ArrayBuffer
集合的常用操作方法
head、last、tail
length、isEmpty
sum、max、min
count、exists、filter、filterNot
takeWhile、dropWhile
take、drop、splitAt
takeRight、dropRight
sclie
contains、startsWith、endsWith
indexOf
intersect、diff
map、flatMap、collect、foreach實(shí)戰(zhàn)詳解
map操作,一對(duì)一映射
val scoreMap = Map("leo" -> 90, "jack" -> 60, "tom" -> 70)
val names = List("leo", "jack", "tom")
names.map(scoreMap(_))
flatMap操作,一對(duì)多映射
val scoreMap = Map("leo" -> List(80, 90, 60), "jack" -> List(70, 90, 50), "tom" -> List(60,70,40))
names.map(scoreMap(_))
names.flatMap(scoreMap(_))
collect操作,結(jié)合偏函數(shù)使用
"abc".collect { case 'a' => 1; case 'b' => 2; case 'c' => 3 }
foreach操作,遍歷
names.foreach(println _)
reduce和fold實(shí)戰(zhàn)詳解
reduce操作
List(1, 2, 3, 4).reduceLeft(_ - _)
List(1, 2, 3, 4).reduceRight(_ - _)
fold操作
List(1, 2, 3, 4).foldLeft(10)(_ - _)
List(1, 2, 3, 4).foldRight(10)(_ - _)