ScalaDoc的使用

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)(_ - _)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,333評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,491評(píng)論 3 416
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,263評(píng)論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,946評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,708評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,186評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評(píng)論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,409評(píng)論 0 288
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,939評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,774評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,976評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評(píng)論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,209評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,641評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,872評(píng)論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,650評(píng)論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,958評(píng)論 2 373

推薦閱讀更多精彩內(nèi)容