Scala字符串
Scala中字符串也是分爲兩種: 可變長度的StringBuilder和不可變長度的String, 其操作用法與Java幾乎一致.
接下來, 通過代碼來查看常用方法
//定義字符串
val str1 = "Hello Scala"
var str2 = "Hello Scala"
var str2_1 = "hello scala"
//字符串比較
println(str1 == str2)
println(str1.equals(str2))
println(str1.equalsIgnoreCase(str2_1))
//上述三個比較全部返回true
//按字典順序比較兩個字符串
println(str1.compareTo(str3))
//按字典順序比較兩個字符串,不考慮大小寫
println(str1.compareToIgnoreCase(str3))
//從0開始返回指定位置的字符
println(str1.charAt(6))
//追加
println(str2.concat(" Language"))
//是否以指定的後綴結束
println(str1.endsWith("la"))
//使用默認字符集將String編碼爲 byte 序列
println(str1.getBytes)
//哈希碼
println(str1.hashCode)
//指定子字符串在此字符串中第一次出現處的索引
println(str1.indexOf("ca"))
//字符串對象的規範化表示形式
println(str1.intern())
//指定子字符串在此字符串中最後一次出現處的索引
println(str1.lastIndexOf("al"))
//長度
println(str1.length)
//匹配正則表達式
println(str1.matches("d+"))
//替換字符
println(str1.replace('a','o'))
//根據字符切割, 需要注意Scala中從數組中取元素使用小括號
println(str1.split(" ")(1))
//是否以指定字符串開始
println(str1.startsWith("Hel"))
//截取子字符串
println(str1.substring(3))
println(str1.substring(3,7))
//大小寫
println(str1.toLowerCase())
println(str1.toUpperCase())
//去空格
println(str1.trim)
//使用StringBuilder
val strBuilder = new StringBuilder
//拼接字符串
strBuilder.append("Hello ")
strBuilder.append("Scala")
println(strBuilder)
//反轉
println(strBuilder.reverse)
//返回容量
println(strBuilder.capacity)
//指定位置插入
println(strBuilder.insert(6,"Spark "))
Scala 集合
1. 數組
Java中使用 new String[10]的形式可以創建數組, 但Scala中創建數組需要用到Array關鍵詞, 用[ ]指定數組中元素的泛型, 取值使用小括號(index).
//創建Int類型的數組, 默認值爲0
val nums = new Array[Int](10)
//創建String類型的數組, 默認值爲null
val strs = new Array[String](10)
//創建Boolean類型的數組, 默認值爲false
val bools = new Array[Boolean](10)
//通過索引遍歷數組,給元素賦值
for (index <- 0 until nums.length) nums(index) = index + 1
//數組遍歷,編碼的逐步簡化
nums.foreach ( (x: Int) => print(x + " ") )
println()
nums.foreach ( (x => print(x + " ")) )
println()
nums.foreach(print(_))
println()
nums.foreach(print)
foreach函數傳入一個函數參數, 由於Scala支持類型推測, 可以將參數函數的參數類型省略; 在參數函數中, 該函數的參數只出現一次, 因爲可以使用下劃線_代替(如果有多個可以使用_.1/_.2); 最後由於Scala語言的靈活性, 只需傳入print這個函數也會遍歷打印整個集合.
創建二維數組分兩步: 創建一個泛型爲數組的數組, 然後對這個數組遍歷,
val secArray = new Array[Array[String]](5)
for (index <- 0 until secArray.length){
secArray(index) = new Array[String](5)
}
//填充數據
for (i <- 0 until secArray.length;j <- 0 until secArray(i).length) {
secArray(i)(j) = i * j + ""
}
secArray.foreach(array => array.foreach(println))
2. list
Scala中列表的定義使用List關鍵詞. List集合是一個不可變的集合. 下面來看創建List已經list調用的方法.
//創建列表
val list = List(1,2,3,4,5)
//對列表遍歷
list.foreach(println)
//contains判斷是否包含某個元素
println(list.contains(6))
//反序,返回一個新的List
list.reverse.foreach(println)
//去前n個元素,返回一個新的List
list.take(3).foreach(println)
//刪除前n個元素,返回一個新的List
list.drop(2).foreach(println)
//判斷集合中是否有元素滿足判斷條件
println(list.exists(_ > 4))
//把List中的元素用設置的字符(串)進行拼接
list.mkString("==").foreach(print)
/*map是一個高階函數,需要一個函數參數
返回值是That,意思是誰調用的map返回的類型跟調用map方法的對象的類型一致
這裏map返回的仍然是list,因此在map中可對每一個元素進行相同操作
map返回的list的泛型由編碼傳入的函數返回類型決定,如下(_ * 100)返回的list的泛型就是Int
*/
list.map(println)
list.map(_ * 100).foreach(println)
val logList = List("Hello Scala" , "Hello Spark")
/*由上述介紹可知,split()返回一個數組,因此map返回的類型是泛型爲數組類型的list
需要對返回的list進行兩次遍歷,第一次遍歷得到Array,第二次遍歷拿到String
*/
logList.map(_.split(" ")).foreach(_.foreach(println))
/*
如果想直接拿到String,需要:
扁平操作
用到的函數是flatMap,flatMap返回的類型也是調用該方法的類型,但它可以直接得到String類型的單詞
*/
logList.flatMap(_.split(" ")).foreach(println)
對map和flatMap的理解可參考下圖:
Nil創建一個空List
Nil.foreach(println)
//::操作可用來添加元素
val list1 = 1::2::Nil
list1.foreach(println)
需要注意的是, 上述創建的list均爲不可變長度的list, 即list中的元素只有在創建時才能添加. 創建可變長度的list, 需要用到ListBuffer, 看代碼:
//創建一個ListBuffer,需要導包scala.collection.mutable.ListBuffer
val listBuffer = new ListBuffer[String]
//使用+=添加元素
listBuffer.+=("hello")
listBuffer.+=("Scala")
listBuffer.foreach(println)
//使用-=去除元素
listBuffer.-=("hello")
3. set
Scala中使用Set關鍵詞定義無重複項的集合.
Set常用方法展示:
//創建Set集合,Scala中會自動去除重複的元素
val set1 = Set(1,1,1,2,2,3)
//遍歷Set即可使用foreach也可使用for循環
set1.foreach(x => print( x + "\t"))
val set2 = Set(1,2,3,5,7)
//求兩個集合的交集
set1.intersect(set2).foreach(println)
set1.&(set2).foreach(println)
//求差集
set2.diff(set1).foreach(println)
set2.&~(set1).foreach(println)
//求子集,如果set1中包含set2,則返回true.注意是set1包含set2返回true
println(set2.subsetOf(set1))
//求最大值
println(set1.max)
//求最小值
println(set1.min)
//轉成List類型
set1.toList.map(println)
//轉成字符串類型
set1.mkString("-").foreach(print)
4. Map
Scala中使用Map關鍵字創建KV鍵值對格式的數據類型.
4.1 創建map集合
val map = Map(
"1" -> "Hello",
2 -> "Scala",
3 -> "Spark"
)
創建Map時, 使用->來分隔key和value, KV類型可不相同, 中間使用逗號進行分隔.
4.2 map遍歷
遍歷map有三種方式, 即可使用foreach, 也可使用與Java中相同用法的迭代器, 還可使用for循環.
方式一: foreach
map.foreach(println)
此時, 打印的是一個個二元組類型的數據, 關於元組我們後文中會詳細介紹, 此處只展示一下二元組的樣子: (1,Hello); (2,Scala); (3,Spark).
方式二: 迭代器
val keyIterator = map.keys.iterator
while (keyIterator.hasNext){
val key = keyIterator.next()
println(key + "--" + map.get(key).get)
}
此時需注意:
map.get(key)返回值, 返回提示:
an option value containing the value associated with key in this map, or None if none exists.
即返回的是一個Option類型的對象, 如果能夠獲取到值, 則返回的是一個Some(Option的子類)類型的數據, 例如打印會輸出Some(Hello), 再通過get方法就可以獲取到其值;
如果沒有值會返回一個None(Option的子類)類型的數據, 該類型不能使用get方法獲取值(本來就無值, 強行取值當然要出異常)
看get方法的提示(如下), 元素必須存在, 否則拋出NoSuchElementException的異常.
Returns the option's value. Note: The option must be nonEmpty.
Throws:
Predef.NoSuchElementException - if the option is empty.
既然這樣, 對於None類型的數據就不能使用get了, 而是使用getOrElse(“default”)方法, 該方法會先去map集合中查找數據, 如果找不到會返回參數中設置的默認值. 例如,
//在上述map定義的情況下執行下述代碼,會在終端打印default
println(map.get(4).getOrElse("default"))
1
2
方式三: for循環
for(k <- map) println(k._1 + "--" + k._2)
此處, 將map中的每一對KV以二元組(1, Hello)的形式賦給k這一循環變量. 可通過k._1來獲取第一個位置的值, k._2獲取第二個位置的值.
4.3 Map合併
//合併map
val map1 = Map(
(1,"a"),
(2,"b"),
(3,"c")
)
val map2 = Map(
(1,"aa"),
(2,"bb"),
(2,90),
(4,22),
(4,"dd")
)
map1.++:(map2).foreach(println)
++和++:的區別
函數 調用 含義
++ map1.++(map2) map1中加入map2
++: map1.++:(map2) map2中加入map1
注意:map在合併時會將相同key的value替換
4.4 Map其他常見方法
//filter過濾,慮去不符合條件的記錄
map.filter(x => {
Integer.parseInt(x._1 + "") >= 2
}).foreach(println)
//count對符合條件的記錄計數
val count = map.count(x => {
Integer.parseInt(x._1 + "") >= 2
})
println(count);
/* 對於filter和count中條件設置使用Integer.parseInt(x._1 + "")是因爲:
* 定義map時,第一個key使用的是String類型,但在傳入函數時每一個KV轉化爲一個二元組(Any,String)類型,x._1獲取Any類型的值,+""將Any轉化爲String,最後再獲取Int值進行判斷.
*/
//contains判斷是否包含某個key
println(map.contains(2))
//exist判斷是否包含符合條件的記錄
println(map.exists(x =>{
x._2.equals("Scala")
}))
5. 元組
元組是Scala中很特殊的一種集合, 可以創建二元組, 三元組, 四元組等等, 所有元組都是由一對小括號包裹, 元素之間使用逗號分隔.
元組與List的區別: list創建時如果指定好泛型, 那麼list中的元素必須是這個泛型的元素; 元組創建後, 可以包含任意類型的元素.
創建元組即可使用關鍵字Tuple, 也可直接用小括號創建, 可以加 “new” 關鍵字, 也可不加. 取值時使用 "tuple._XX"獲取元組中的值.
元組的創建和使用
//創建元組
val tuple = new Tuple1(1)
val tuple2 = Tuple2("zhangsan",2)
val tuple3 = Tuple3(1,2.0,true)
val tuple4 = (1,2,3,4)
val tuple18 = (1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18)
//注意:使用Tuple關鍵字最多支持22個元素
val tuple22 = Tuple22(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22)
//使用
println(tuple2._1 + "\t" + tuple2._2)
//元組中嵌套元組
val t = Tuple2((1,2),("zhangsan","lisi"))
println(t._1._2)
元組的遍歷
//tuple.productIterator可以得到迭代器, 然後用來遍歷
val tupleIterator = tuple22.productIterator
while(tupleIterator.hasNext){
println(tupleIterator.next())
}
toString, swap方法
//toString, 將元組中的所有元素拼接成一個字符串
println(tuple3.toString())
//swap翻轉,只對二元組有效
println(tuple2.swap)
trait特性
Scala中的trait特性相對於Java而言就是接口. 雖然從功能上兩者極其相似, 但trait比接口還要強大許多: trait中可以定義屬性和方法的實現, 這點又有點像抽象類; Scala的類可以支持繼承多個trait, 從結果來看即實現多繼承.
Scala中定義trait特性與類相似, 不同在於需要使用"trait"關鍵字. 其他注意點在代碼註釋中做出說明:
trait Read {
val readType = "Read"
val gender = "m"
//實現trait中方法
def read(name:String){
println(name+" is reading")
}
}
trait Listen {
val listenType = "Listen"
val gender = "m"
//實現trait中方法
def listen(name:String){
println(name + " is listenning")
}
}
//繼承trait使用extends關鍵字,多個trait之間使用with連接
class Person extends Read with Listen{
//繼承多個trait時,如果有同名方法或屬性,必須使用“override”重新定義
override val gender = "f"
}
object test {
def main(args: Array[String]): Unit = {
val person = new Person()
person.read("zhangsan")
person.listen("lisi")
println(person.listenType)
println(person.readType)
println(person.gender)
}
}
object Lesson_Trait2 {
def main(args: Array[String]): Unit = {
val p1 = new Point(1,2)
val p2 = new Point(1,3)
println(p1.isEqule(p2))
println(p1.isNotEqule(p2))
}
}
trait Equle{
//不實現trait中方法
def isEqule(x:Any) :Boolean
//實現trait中方法
def isNotEqule(x : Any) = {
!isEqule(x)
}
}
class Point(x:Int, y:Int) extends Equle {
val xx = x
val yy = y
def isEqule(p:Any) = {
/*
* isInstanceOf:判斷是否爲指定類型
* asInstanceOf:轉換爲指定類型
*/
p.isInstanceOf[Point] && p.asInstanceOf[Point].xx==xx
}
}
模式匹配match-case
Scala中的模式匹配match-case就相當於Java中的switch-case. Scala 提供強大的模式匹配機制, 即可匹配值又可匹配類型. 一個模式匹配包含一系列備選項, 每個備選項都以case關鍵字開始. 並且每個備選項都包含了一個模式以及一到多個表達式, 箭頭符號 => 隔開了模式和表達式。
object Lesson_Match {
def main(args: Array[String]): Unit = {
val tuple = Tuple7(1,2,3f,4,"abc",55d,true)
val tupleIterator = tuple.productIterator
while(tupleIterator.hasNext){
matchTest(tupleIterator.next())
}
}
/**
* 注意
* 1.模式匹配不僅可以匹配值,還可以匹配類型
* 2.模式匹配中,如果匹配到對應的類型或值,就不再繼續往下匹配
* 3.模式匹配中,都匹配不上時,會匹配到case _ ,相當於default
*/
def matchTest(x:Any) ={
x match {
//匹配值
case 1 => println("result is 1")
case 2 => println("result is 2")
case 3 => println("result is 3")
//匹配類型
case x:Int => println("type is Int")
case x:String => println("type is String")
case x :Double => println("type is Double")
case _ => println("no match")
}
}
}
由於匹配到對應的類型或值時, 就不再繼續往下匹配, 所有在編寫備選項時要將範圍小的放在前面, 否則就會失去意義. 這就類似於try-catch中處理異常時, 也要先從小範圍開始.
樣例類case classes
使用case關鍵字定義的類就是樣例類(case classes), 樣例類實現類構造參數的getter方法 (構造參數默認被聲明爲val) , 當構造參數類型聲明爲var時, 樣例類會實現參數的setter和getter方法.
樣例類默認實現toString, equals, copy和hashCode等方法. 樣例類在創建對象時可new, 也可不new.
//使用case關鍵字定義樣例類
case class Person(name:String, age:Int)
object Lesson_CaseClass {
def main(args: Array[String]): Unit = {
//創建樣例類對象,可new可不new
val p1 = new Person("zhangsan",18)
val p2 = Person("lisi",20)
val p3 = Person("wangwu",22)
val list = List(p1,p2,p3)
list.foreach { x => {
x match {
case Person("zhangsan",18) => println("zhs")
case Person("lisi",20) => println("lisi")
case p:Person => println("is a person")
case _ => println("no match")
}
} }
}
}
併發 Actor Model
Actor Model相當於Java中的Thread, Actor Model用來編寫並行計算或分佈式系統的高層次抽象. Actor不需要擔心多線程模式下共享鎖的問題, 可用性極高.
Actors將狀態和行爲封裝在一個輕量級的進程/線程中, 但它不和其他Actors分享狀態, 每個Actors有自己的世界觀, 當需要和其他Actors交互時, 通過發送異步的, 非堵塞的(fire-and-forget)事件和消息來交互. 發送消息後不必等另外Actors回覆, 也不必暫停, 每個Actors有自己的消息隊列, 進來的消息按先來後到排列, 這就有很好的併發策略和可伸縮性, 可以建立性能良好的事件驅動系統.
Actor的特徵:
ActorModel是消息傳遞模型,基本特徵就是消息傳遞
消息發送是異步的,非阻塞的
消息一旦發送成功,不能修改 (類似發郵件)
Actor之間傳遞時,自己決定決定去檢查消息,而不是一直等待,是異步非阻塞的
定義Actor需要繼承Actor這一trait, 實現act這一方法, 並且使用感嘆號! 來發送消息.
一個簡單實例:
import scala.actors.Actor
//自定義Actor
class myActor extends Actor{
def act(){
while(true){
receive {
case x:String => println("save String ="+ x)
case x:Int => println("save Int")
case _ => println("save default")
}
}
}
}
object Lesson_Actor {
def main(args: Array[String]): Unit = {
//創建actor的消息接收和傳遞
val actor =new myActor()
//啓動
actor.start()
//發送消息寫法
actor ! "Hello Scala Actor !"
}
}
Actor與Actor之間通信:
//創建樣例類,用來發送
case class Message(actor:Actor,msg:Any)
class Actor1 extends Actor{
def act(){
while(true){
//對接收的消息進行模式匹配
receive{
case msg :Message => {
println("i sava msg! = "+ msg.msg)
//回覆消息
msg.actor!"i love you too !"
}
case msg :String => println(msg)
case _ => println("default msg!")
}
}
}
}
//爲了實現Actor中的通信,需要拿到另一個Actor的對象
class Actor2(actor :Actor) extends Actor{
//發送消息
actor ! Message(this,"i love you !")
def act(){
while(true){
receive{
case msg :String => {
if(msg.equals("i love you too !")){
println(msg)
actor! "could we have a date !"
}
}
case _ => println("default msg!")
}
}
}
}
object Lesson_Actor2 {
def main(args: Array[String]): Unit = {
val actor1 = new Actor1()
actor1.start()
val actor2 = new Actor2(actor1)
actor2.start()
}
}
Scala隱式轉換系統
隱式轉換是指在編寫程序時, 儘量少的去編寫代碼, 讓編譯器去嘗試在編譯期間自動推導出某些信息來, 這就類似於在Scala中定義變量時不需要指定變量類型. 這種特性可以極大的減少代碼量, 提高代碼質量.
Scala中提供強大的隱式轉換系統, 分別爲: 隱式值, 隱式視圖和隱式類.
隱式值
先來看一個隱式值的Demo:
object Lesson_Implicit1 {
def main(args: Array[String]): Unit = {
implicit val name = "Scala Study"
sayName
}
def sayName(implicit name:String) = {
println("say love to " + name)
}
}
這裏將name變量聲明爲implicit, 編譯器在執行sayName方法時發現缺少一個String類型的參數, 此時會搜索作用域內類型爲String的隱式值, 並將搜索到的隱式值作爲sayName的參數值進行傳遞.
需要注意:
隱式轉換必須滿足無歧義規則, 否則會報錯:
ambiguous implicit values: both value a of type String and value name of type String match expected type String
在同一個作用域禁止聲明兩個類型一致的變量,防止在搜索的時候會猶豫不決
聲明隱式參數的類型最好是自定義的數據類型,一般不要使用Int,String這些常用類型,防止碰巧衝突.
隱式視圖
隱式視圖就是把一種類型自動轉換爲另一種類型. 還是先來看代碼:
object Lesson_Implicit2 {
def main(args: Array[String]): Unit = {
//聲明隱式視圖
implicit def stringToInt(num:String) = Integer.parseInt(num)
println(addNum("1000"))
}
def addNum(num:Int) = { num + 1000 }
}
這裏addNum方法參數是String類型, 不符合定義要求, 此時編譯器搜索作用域發現有個隱式方法, 正好這個方法的參數是String, 返回是Int. 然後就會調用這個隱式方法, 返回一個Int值並將它傳給addNum方法.
隱式類
隱式類是指把一個對象自動轉換爲另一種類型的對象, 轉換後可以調用原來不存在的方法.
package com.qb.scala
object Lesson_Implicit3 {
def main(args: Array[String]): Unit = {
//導入隱式類所在的包
import com.qb.scala.Util.StringLength
println("qwer".getLength())
}
}
object Util {
//定義一個隱式類,使用implicit關鍵字修飾
implicit class StringLength(val s : String){
def getLength() = s.length
}
}
這裏編譯器在qwer對象調用getLength方法時, 發現該對象並沒有getLength方法, 此時編譯器發現在作用域範圍內有隱式實體. 發現有符合的隱式類可以用來轉換成帶有getLength方法的Util類, 進而就可調用getLength方法.
需要注意:
隱式類所帶的構造參數有且只能有一個
必須在類, 伴生對象和包對象中定義隱式類
隱式類不能是case class(樣例類), case class 在定義時會自動生成伴生對象.
作用域中不能有與隱式類同名的標識符