scala
scala安裝
1.scp 遠程拷貝到linux(jdk-1.8 和 Scala , idea)
2. 解壓 tar -zxvf fileName -C filePath
3.配置環境變量
jdk 的和 Scala的
vim /etc/profile
source /etc/profile
4.開啓idea
bin/idea.sh
scala 使用方式
1. touch test.scala
vim test.scala
object ScalaTest{
def main (args:Array[String]){
val a=15
val b=6
println("a-B \t"+(a-b))
println("a*B \t"+(a*b))
println("a/B \t"+(a/b))
println("a%B \t"+(a%b))
}
}
scalac test.scala
scala test.scala
2.[zhangsan@master bin]$scala
Welcome to Scala 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_171).
Type in expressions for evaluation. Or try :help.
scala>println("helloword");
helloword
3.Idea
object ScalaTest {
def main(args:Array[String]): Unit ={
val c="qq9"
val d="pp6"
val e="qq6"
println("c+d\t"+(c+d))
println("c+e\t"+(c+e))
}
}
函數的聲明
class Counter{
private var value=0
//完整版
def incre1()={value+=1}
def get1():Int={value}
//只有一行可省略{}
def incre2()=value+=1
def get2()=value
//返回值爲空可省略()
def incre3=value+=1
def get3=value
//無返回值或爲空的返回值可不寫=
def incre4{value+=1}
def get4={value}
}
object ClassSetGet {
def main(args: Array[String]): Unit = {
val g1=new Counter();
val g2=new Counter;//無參數可省略()
g1.incre1();
println(g1.get1());
}
}
總結
1.只有一行可省略{}
方法的構造 2.返回值爲空可省略()
3.無返回值或爲空的返回值可不寫=
4.方法聲明時無參數可省略()
編譯和執行
在命令窗口執行時
vim xxx.scala
編寫代碼
scalac xxx.scala//編譯,
這時會生成幾個文件找到main方法的object名字
例如object yyy{main...}
scala yyy //執行
getter和setter
class A_Person{
private var name: String =""
private var age:Int=0
def ageValue=age
def ageValue_=(newAge:Int): Unit ={
age=newAge
}
def nameValue: String =name
def nameValue_=(newName:String): Unit ={
name=newName
}
}
object ClassSetGet {
def main(args: Array[String]): Unit = {
val test=new A_Person()
println(test.ageValue)
test.ageValue=19;
//test.ageValue=10;
println(test.ageValue)
}
}
0
19
//也可寫成Java風格
def getAge=age
def setAge(age:Int){
this.age=age
}
//再進行調用
主輔構造器
1.主構造器
class Ma(var name:String,var age:Int){
def getName=name;
def getAge=age
def setName(name:String)={
this.name=name
}
def setAge(age:Int)={
this.age=age
}
}
object MaTest {
def main(args: Array[String]): Unit = {
val t=new Ma("lyb",24)
println(t.name+" "+t.age)
println(t.getName+" "+t.getAge)
t.setAge(18);t.setName("zhangs")
println(t.getName+" "+t.getAge)
}
}
lyb 24
lyb 24
zhangs18
2.多輔構造器
class FuTest{
private var name=""
private var age=0
//一個輔助
def this( name:String, age:Int)={
this()
this.age=age
this.name=name;
}//第二個輔助
def this(name:String){
this()
this.name=name
}
def getName=name;
def getAge=age
def setName(name:String)={
this.name=name
}
def setAge(age:Int)={
this.age=age
}
}
object Fu {
def main(args: Array[String]): Unit = {
val t=new FuTest("lyb",24)
println(t.getName+" "+t.getAge)
val p=new FuTest("zhangfei")
println(p.getName+" "+p.getAge)
t.setAge(18);t.setName("liuyuebin")
println(t.getName+" "+t.getAge)
}
}
lyb 24
zhangfei 0
liuyuebin 18
總結
主構造器:構造器就是整個類體字段的全構造---->class(var 字段:類型){}
只允許一個
輔構造器:相當於Java的構造方法;可以寫多個構造字段
可以寫多個構造方法
def this(字段:類型){
this()//調用本類的無參構造***必寫
//如果不寫this.字段就無法調用類中的字段
this.字段=字段
}
伴生與單例對象
單例對象:
Java中單例模式
public class Single {
private Single(){}
private static Single s;
public static Single getIntance(){
if(s==null){
synchronized (Single.class){
if(s==null){
s=new Single();
}
}
}
return s;
}
Scala中就是 object 對象名{}}
伴生對象
//類名與對象名相同
class BanShengObject{
private var name=""
private var id=BanShengObject.id
def this(name:String)={
this()
this.name=name
}
}
object BanShengObject {
private var id=10
def getId() ={
id+=1
id
}
def main(args: Array[String]): Unit = {
val t=new BanShengObject("lyb")
println(t.id)
println(BanShengObject.getId())
println(t.name)
}
}
總結
1.單例對象
object 對象名{}
通過new 類名().(字段/方法)來調用類中的私有字段和方法
2.伴生對象
class 類名{}
object 類名{}
特徵:
在類中和對象中通過 對象名.(字段/方法) 來調用對象中的私有字段和方法
要求類和對象在同一個文件中
實際上就實現了Java中靜態(static)方法的功能;
Scala源代碼編譯後都會變成JVM字節碼。
實際上,源代碼編譯後,Scala裏面的class和object在Java層面都會被合二爲一
class裏面的成員成了實例成員,object成員成了static成員。
Apply和UpDate
apply
class ApplyDemo {
def apply() = println("apply method in class is called!")
def greetingOfClass = println("Greeting method in class is called.")
}
object ApplyDemo {
def apply() = { println("apply method in object is called."); new ApplyDemo() }
}
object ApplyTest {
def main(args: Array[String]): Unit = {
val a = ApplyDemo() // 這裏調用伴生對象中的apply方法
a.greetingOfClass
a() // 這裏調用伴生類中的apply方法
}
}
update
val myArray = Array(1,2,3,4,5,6)
myArray(1)=3;//調用update和
myArray.update(1,3)//等價調用的update
總結
1.apply:用括號傳遞給變量(對象)一個或多個參數時,Scala 會把它轉換成對apply方法的調用
apply分爲兩種
<1>伴生類中的apply
<2>伴生生對象中的apply
2.update:當對帶有括號幷包括一到若干參數的對象進行賦值時,編譯器將調用對象的update方法,
在調用時,將括號裏的參數和等號右邊的對象一起作爲update方法的輸入參數來執行調用
繼承和特質(Trait=Java中的interface)
繼承–extends
特徵:
(1)重寫父類的非抽象方法必須寫override
(2)只有主構造器可以調用超類的主構造器;
(3)在子類中重寫超類的抽象方法時,不需要使用override關鍵字;
(4)可以重寫超類中的字段。
(5)不允許類從多個超類繼承。
注意:
(1)定義一個抽象類,需要使用關鍵字abstract;
(2)定義一個抽象類的抽象方法,不要關鍵字abstract,要把方法體空着,不寫方法體就可以。
def student()不能寫def student(){}
(3)抽象類中定義的字段,只要沒有給出初始化值,就表示是一個抽象字段。
不要寫abstract 寫了編譯會報錯
抽象字段必須要聲明類型,比如:val carBrand: String,就把carBrand聲明爲字符串類型。
不能省略類型,否則編譯會報錯
特質 trait
與Java中的interface相同,是代碼重用的基本單元,可以同時擁有抽象方法和具體方法;
Scala中,一個類只能繼承自一個超類,卻可以實現實現多個特質,從而重用特質中的方法和字段,實現了多重繼承;
trait 名稱{}
使用extends關鍵字混入CarId特質,後面可以反覆使用with關鍵字混入更多特質
class 類名 extends trait1 with trait2 with ....{}
函數式編程
1、樣例類
1、樣例類是種特殊的類,經過優化多用於模式匹配。
2、用case class進行聲明,case class其實有點類似於Java中的JavaBean的概念。即只定義field,並且由Scala編譯時自動提供getter和setter方法,但是沒有method,自動提供 toString、equals、hashCode和copy,apply,unapply繼承了Product和Serializable
3、 case class的主構造函數接收的參數通常不需要使用var或val修飾,Scala自動就會使用val修飾(但是如果你自己使用var修飾,那麼還是會按照var來)
4、 Scala自動爲case class.定義了伴生對象,也就是object,並且定義了apply()方法,該方法接收主構造函數中相同的參數,並返回case class對象
case類在模式匹配和actor中經常使用到,當一個類被定義成爲case類後,Scala會自動幫你創建一個伴生對象並幫你實現了一系列方法
案例
1.實現了apply方法,意味着不需要使用new關鍵字就能創建該類對象
scala> case class People(name:String,age:Int)
scala> val p = People(“lyb”,28) //省略了new關鍵字
p: People = People(lyb,28)
2.實現了unapply方法,可以通過模式匹配來獲取類屬性,是scala中抽取器的實現和模式匹配的關鍵方法。
3.實現類構造參數的getter方法(構造方法默認被聲明爲val),但當構造參數聲明爲var類型時,將會實現setter和getter方法(不建議將構造參數聲明爲var)
scala> p.name
res0: String = luge
scala> p.name = “hezong” //報錯,因爲構造參數被聲明爲val所以並沒有幫你實現setter方法
< console >:10: error: reassignment to val
p.name = "hezong
4.默認實現了toString,equals,copy和hashCode等方法
5.通過反編譯產看定義一個case類時編譯器的結果
case class Person(name:String ,age :Int)
通過終端編譯該文件後生成兩個class文件,Person.class和Person$.class
通過javap 命令產看反編譯文件
javap -private類名
case本就旨在創建的是不可變數據,所以在使用模式匹配時顯得極爲容易
2、模式匹配
1.Scala是沒有Java中的switch case語法的,相對應的,Scala提供了更加強大的match case語法,即模式匹配,類替代switch case—>match case也被稱爲模式匹配
2.Scala的match case與Java的switch case最大的不同點在於,Java的switch case僅能匹配變量的值,比1、2、3等;而Scala的match case可以匹配各種情況,比如變量的類型、集合的元素、有值或無值
3.match case的語法如下:變量 match { case 值[:類型]=> 代碼 }。如果值爲下劃線,則代表了不滿足以上所有情況下的默認情況如何處理。此外,match case中,只要一個case分支滿足並處理了,就不會繼續判斷下一個case分支了。(與Java不同,java的switch case需要用break阻止)
4.match case語法最基本的應用,就是對變量的值進行模式匹配
常量匹配
如果匹配成功,則執行 => 後面的代碼
匹配的順序是從上到下,匹配到一個就行執行對應的代碼
=> 後面的代碼塊,不需要些break,會自動退出match
如果一個都沒有匹配到,則執行case _後面的代碼塊
oper match {
case ‘+’ => res = n1 + n2
case ‘-’ => res = n1 - n2
case ‘*’ => n1 * n2
case ‘/’ => n1 / n2
//if後面稱爲條件守衛
case _ if(oper == ‘o’) => println(“oper is o”)
case _ => println(“oper error”)//通用匹配
}
類型匹配
object Test {
def main(args: Array[String]): Unit = {
for(elem<- List(1.3,2,“spark”,'Haoop)){
val str=elem match{
case i:Int => i + " is an int value"
case d:Double => d + " is an double value"
case “spark” => “spark is found”
case s:String => s + " is an string value"
case _ => “This is an unexpected value”
}
println(str)
}
}
}
Array匹配
object Test {
def main(args: Array[String]): Unit = {
val arr=Array(8,3,1)
def arrayMatch(arr: Any) = arr match {
case Array(0) => println(“只有一個0元素的數組”)
case Array(0, _) => println(“以0開頭的,擁有2個元素的數組”)
case Array(0, _, 3) => println(“以1開頭,3結尾,中間爲任意元素的三個元素的數組”)
case Array(8, *) => println(“以8開頭的,任意個元素的數組”)
case Array(, _) => println(“數組種有兩個元素”)
//匹配數組種有三個元素,將數組中的元素賦值給x,y,z然後我們就可以做順序的變換了
case Array(x, y, z) => Array(z, x, y)
}
arrayMatch(arr)
}
}
List集合匹配與Array匹配類似
模式匹配
class Person
case class Teacher(name:String,subject:String) extends Person
case class Student(name:String,classroom:Int) extends Person
case class Worker(name:String,work:String) extends Person
case class Stranger() extends Person
object test{
def main(args: Array[String]): Unit = {
def entranceGuard(p:Person): Unit ={
p match {
case Student(name,classroom)=>println(s"hello,$name,welcome to school,your classroom is name,welcome to school,your teach name,you should leave school afternoon")
case Worker(name,work)=>println(s"hello,$name,you should leave school 2 hours later")
case _=>println(s"stranger,you can not into school")
}
}
entranceGuard(Worker(“luge”,“runner”))
}
}
Option類型
Scala 爲可選值定義了一個Option的標準類型。這種類型只有兩種形式。一種是用樣例子類Some包裝起來的——Some(x)形式,其中x是實際值,另一種是樣例對象None,代表缺省的值或者說沒有值。
Option支持泛型
Scala集合類的某些標準操作會產生可選值。比如Map類的get方法返回一個Option,如果對於給定的鍵有對應的值,就會把值包裝到Some中返回,否則就返回None
總結
Option類型在Scala中經常用到。與之相較,在Java中最常用的是代表沒有值的null。例如java.util.HashMap的get方法要麼返回儲存參HashMap中的值,要麼返回null。這種方式對Java起效,不過可能會隱藏錯誤,因爲很難在實際記住程序中那個變量可以爲null。如果變量允許爲null,那麼在每次使用時都必須檢查是否爲null。一旦忘記檢查,就很難避免運行時發生的NullPointerException異常。又因爲這種異常不是經常發生,所以想要通過測試發現是很困難的。對於Scala來說,這種方式根本不起作用,因爲可以在哈希映射中存儲值類型,而null不是值類型的合法元素。也就是說,HashMap[Int, Int] 不能返回null來表明沒有元素。
Scala鼓勵對Option的使用說明值是可選的。這種處理可選值的方式有若干超越Java的優點。首先,對於代碼讀者來說,Option[String] 類型的變量是可選的String,這比String類型的變量或可能有時是null來說要更爲明顯。但最重要的是,之前描述的因爲使用可能爲null而沒有首先檢查是否爲null的變量產生的編程錯誤在Scala中就變成了類型錯誤。如果變量是Option[String]類型,而你把它當作String類型使用,是無法通過編譯的。
object CaseOps {
def main(args: Array[String]): Unit = {
caseOps
}
def caseOps: Unit = {
def optionOps: Unit = {
val capitals = Map("France" -> "Paris", "Japan" -> "Tokyo", "BeiJing" -> "通州")
println(capitals.get("Japan") + show(capitals.get("Japan")))
println(capitals.get("BeiJing") + show(capitals.get("India")))
}
def show(x: Option[String]) = x match {
case Some(s) => s
case None => "?"
}
optionOps
}
}
Option[T]實際上就是一個容器,可以把它看做是一個集合,只不過這個集合中要麼只包含一個元素(被包裝在Some中返回),要麼就不存在元素(返回None);
既然是一個集合,當然可以對它使用map、foreach或者filter等方法;
foreach遍歷遇到None的時候,什麼也不做,不會執行println操作。
getOrElse()方法
如果some指定了一個數,那從這裏面取getOrElse方法結果都是該指定的數,若Option爲空,則取出的getOrElse值是後面的指定的值,與option無關。就是一個默認的缺省值一樣。
object Test5 {
def main(args: Array[String]): Unit = {
var map=Map[Int,String]()
map+=(1->"one",2->"two")
println(map.getOrElse(1,"default"))
println(map.getOrElse(2,"default"))
println(map.getOrElse(3,"default"))
}
}
函數
1、函數字面量
理解:函數的“值”,就是“函數字面量”。
函數的類型和值
傳統定義函數格式:
def func(value: Int) :Int = {value += 1} //函數中有int型參數value,函數返回類型爲Int
上面定義的函數類型可表示爲: (Int) => Int,也就是輸入參數爲Int類型,返回值Int類型,若只有輸入參數,可以省略括號,即Int => Int,這樣就是體現了函數的“類型”。
那麼對於函數的值,可以將函數中定義的類型聲明部分去掉,剩下的就是函數的“值”:
(value) => (value += 1) ,注意這個值需要使用 => 表示,而不是=
有了上述概念,我們就能清楚地明白函數的類型和值的關係,並能夠將其剝離出來。
一般我們聲明變量時:
val a : Int = 5 //其中Int是類型,5是值
那麼同樣可以聲明Scala中的函數:
val b : Int => Int = { (value) => value+=1 } //其中Int => Int是類型,{}中的東西是函數的值,此時b就是一個函數類型了
2、匿名函數、Lambda表達式
匿名函數用於快速完成一個函數的定義。
例如:(num : Int) => {num * 2} 這種形式的匿名函數也稱作Lambda表達式。其基本格式是:
(參數) => 表達式 //若參數只有一個則括號可省略
定義好的匿名函數可以直接將其賦給變量,後面可以直接拿來使用:
val func1 = Int => Int = (num : Int) => num*2 //將匿名函數作爲Int => Int函數的值
println(fun1(3)) //可以直接用變量來使用這個匿名函數
Scala具有類型推斷功能,那麼定義的函數也可以像定義變量那樣在合適的時候省去類型的指定:
val func1 = Int => Int = {(num:Int) => num2}
等價於:
val func2 = {(num:Int) => num2}
3、佔位符
爲了讓函數字面量更加簡潔,可以使用下劃線作爲一個或多個參數的佔位符,只要每個參數在函數字面量內僅出現一次。
val list1 = List(1,2,3,4,5)
list1.filter(x => x >3) //lambda表達式
list1.filter(_ > 3) //這種寫法與上面的寫法是一樣的
使用把下劃線當作是參數的佔位符,但是編譯器可能並不認識,例如:
val f = + 運行時會報錯,因爲編譯器推斷不出來類型。
應爲佔位符參數指定類型: val f = (:Int) + (:Int)
4、高階函數
1. 概念
Scala 混合了面向對象和函數式的特性,我們通常將可以作爲參數傳遞到方法中的表達式叫做函數。在函數式編程語言中,函數是“頭等公民”,高階函數包含:作爲值的函數、匿名函數、閉包、柯里化等等。
2. 作爲值的函數
可以像任何其他數據類型一樣被傳遞和操作的函數,每當你想要給算法傳入具體動作時這個特性就會變得非常有用。
定義函數時格式:val 變量名 = (輸入參數類型和個數) => 函數實現和返回值類型
“=”表示將函數賦給一個變量
“=>”左面表示輸入參數名稱、類型和個數,右邊表示函數的實現和返回值類型
3. 匿名函數
在 Scala 中,你不需要給每一個函數命名,沒有將函數賦給變量的函數叫做匿名函數
用佔位符
4. 柯里化
4.1 什麼是柯里化
柯里化(Currying)指的是把原來接受多個參數的函數變換成接受一個參數的函數過程,並且返回接受餘下的參數且返回結果爲一個新函數的技術。
4.2 案例
普通函數定義
scala> def a(x:Int,y:Int)=x+y
a: (x: Int, y: Int)Int
scala> a(1,2)
res0: Int = 3
使用“柯里化”技術來定義這個加法函數,原來函數使用一個參數列表,“柯里化”,把函數定義爲多個參數列表:
第一種定義方式:
scala> def b(x:Int)(y:Int)=x+y
b: (x: Int)(y: Int)Int
scala> b(1)(2)
res1: Int = 3
當調用 b (1)(2)時,實際上是依次調用兩個普通函數(非柯里化函數),
第一次調用使用一個參數 x,返回一個函數類型的值,
第二次使用參數 y 調用這個函數類型的值。
第二種定義方式:
首先定義第一個函數:
scala> def first(x:Int)=(y:Int)=>x+y
first: (x: Int)Int => Int
然後使用參數 1 調用這個函數來生成第二個函數:
scala> val second =first(1)_
second: Int => Int =
scala> second(2)
res2: Int = 3
5、偏函數
- 偏函數
偏函數(Partial Function),是一個數學概念它不是"函數"的一種, 它跟函數是平行的概念。
Scala中的Partia Function是一個Trait,其的類型爲PartialFunction[A,B],其中接收一個類型爲A的參數,返回一個類型爲B的結果。
在Scala通常使用偏函數進行類型轉換,拆解。可以很容易的把tuple拆解成key和value。(常用、重點)
val a = List((“hadoop”, 10), (“HBase”, 5), (“Hive”, 5), (“kafka”, 1), (“flume”, 1))
a.map{case (key, value) => value}
偏函數 與 全函數(其概念來自於數學)
偏函數,只接受和處理其參數定義域範圍內的子集,對於這個參數範圍外的參數則拋出異常;
def fraction(d: Int) = 42 / d
val fraction: PartialFunction[Int, Int] =
{case d: Int if d != 0 => 42 / d}
fraction(0) // 都報錯,錯誤類型不一樣
集合 (可變與不可變)
1.分類
Traversable-->Iterable-->
Seq(序列)
IndexedSeq
LinearSeq
Set(集)
SortedSet
BitSet
Map(映射)
SortMap
2.List
<1>列表和數組的不同
(1)列表是不可變的。即列表的元素不能通過賦值改變。
(2)列表的結構是遞歸的,而數組是平的;
(3)數組存放的是相同類型的;列表可以存放的不同類型
(4)列表沒有長度限制,遍歷時用迭代(數組是for)
<2> 操作
1.獲取
val list=List("a",1,"22")
val b=list.tail.tail
-->b: List[Any] = List(22)
list.head 獲取第一個元素
list.tail 獲取除了第一個的所有元素
2.增加(:: )
val mylist=5::list
//是把5增加到已有的列表前端獲取新的列表
//列表不變 但val mylist = list::5
//是不對的 最後只能是列表
val a=1::2::3::4::Nil
//Nil代表空列表
3.拼接 (::: )
val list1=List(1,2,3)
val list2=List(4,5,6)
val list=list1:::list2 //list1在前
4.求和
val b=list1.sum //求列表中的和
底層實現使用遞歸
def sum (list:List[Int]):Int={
//1
if (list==Nil) 0 else list.head+sum(list.tail)
//2
case Nil =>0
case head::tail =>head +sum(tail)
}
5.flatten和flatMap
flatten 壓實:把嵌套在列表中的結構展開
List(List(1,2,3,4),List(a,b,List(c,d))).flatten
-->List(1,2,3,4,a,b,List(c,d))//只能展開一層
flatMap=flatten+map
List(List(1,2,3,4),List(3,4)).flatMap(_.map(_*2))
List(List(1,2,3,4),List(3,4)).flatten.map(_*2)
//注意最多兩層List否則報錯
6.GroupBy(條件) 分組
(條件:一般爲元組下標)
List((1,2),(1,3),(2,3),(3,3),(3,4)).groupBy(_._1)
//根據第一個元素分組
7.reduce (_ + _)
包含reduceLeft(_+_)//默認
包含reduceRight(_+_)//從右往左
8.fold()
//操作和reduce類似但其需要一個初始的種子值開始
val a=List(1,2,3,4)
a.fold(10)(_*_)
-->10*1*2*3*4=240
a.fold(10)((x,y)=>{println(s"x=$x,y=$y");x*y})
//柯理化
foldLeft()
foldRight()
9.遍歷(foreach map)
val a=List(1,2,3,4)
a.foreach(println)
a.foreach(println _)
a.foreach(ele=>print(ele+" "))
a.map(_>2) //返回的是boolean數組
a.map(_*2) //換回一個新數組
foreach是遍歷
map是一個集合->另一個集合
10.filter(條件) 過濾選擇
val a=List(1,2,3,4)
a.filter(_%2==0)
a.filter(_.startWith("?"))
a.filter(關於集合的條件)
3.Set
<1>定義
集合包括可變集和不可變集,
scala.collection.mutable包
scala.collection.immutable包,
缺省情況下創建的是不可變;
<2>操作
1.移除可變
val set=Set(1,2,3,4)
set.remove(1)
val a=Set(1,2,3,4,5)
val b=Set(3,4,5,6,7)
2.交集 (& instersect )
a&b
a.instersect(b)
a instersect b
3.並集 (++ | union)
a++b
a|b
a.union(b) a union b
4.差集 (–、 &~ 、diff)
a--b
a&~b
a.diff(b)
4.Seq
1.定義
具有一定長度的可迭代訪問的對象
Vector是ArrayBuffer的不可變版本,可通過下標快速的隨機訪問
在Java中vector支持線程安全但效率低,一般用ArrayList
2.常用類型
String、List、Queue、Stack
val a=(1 to 10) = Range(1,10)
val a=(1 to 10 by 2) = Range(1,10,2)
3.操作
(1)迭代器 Iterator
Next(返回下一個元素)
hasNext(是否有下一個)
for和迭代器的區別
for會暴露數據的組織信息,組織結構,數據順序
迭代器Next(返回下一個元素)hasNext(是否有下一個)
(2)List操作見上面
函數式編程
1.隱函數
1.1 定義:
implicit def 函數名(x:輸入類型)={x.to輸出類型}
implicit def pp(x:Double)=x.toInt
val x:Int=3.14
x:Int =3
//這個定義不可取容易語義混淆並且與自帶的衝突
Scala會考慮如下的隱式轉換規則:
1、位於源或目標類型的伴生對象中的隱式函數
2、位於當前作用域可以以單個標示符指代的隱式函數
import java.io.File
import scala.io.Source
//隱式的增強File類的方法
class RichFile(val commonFile: File) {
def read = Source.fromFile(commonFile.getPath).mkString
}
object RichFile {
//隱式轉換方法,功能是提供“低級類型”到“增強類型”的轉換
implicit def file2RichFile(commonFile: File) = new RichFile(commonFile)
}
object MainApp{
def main(args: Array[String]): Unit = {
//導入隱式轉換
import RichFile._
//低級類型自動具有了增強類型的方法
println(new File("c://words.txt").read)
}
}
//先將 File 轉換爲 RichFile,再調用 RichFile.read 方法
1.2 隱式參數
定義:
隱式參數有點類似缺省參數,如果在調用方法時沒有提供某個參數,
編譯器會在當前作用域查找是否有符合條件的 implicit 對象可以作爲參數傳入;
不同於缺省參數,隱式參數的值可以在方法調用前的上下文中指定,這使隱式參數更加靈活;
函數或方法可以帶有一個標記爲implicit的參數列表。這種情況下,編譯器將會查找缺省值,提供給該函數或方法。
注意:
1)當函數沒有柯里化時,implicit關鍵字會作用於函數列表中的的所有參數;
2)隱式參數使用時要麼全部不指定,要麼全不指定,不能只指定部分;
3)同類型的隱式值只能在作用域內出現一次,即不能在同一個作用域中定義多個相同類型的隱式值;
4)在指定隱式參數時,implicit 關鍵字只能出現在參數開頭;
5)如果想要實現參數的部分隱式參數,只能使用函數的柯里化;
如要實現這種形式的函數:
def test(x:Int, implicit y: Double),必須使用柯里化實現:
def test(x: Int)(implicit y: Double)
6) 柯里化的函數, implicit 關鍵字只能作用於最後一個參數。否則,不合法;
7)implicit 關鍵字在隱式參數中只能出現一次,柯里化的函數也不例外;
8)匿名函數不能使用隱式參數;
9)柯里化的函數如果有隱式參數,則不能使用其偏應用函數;
object context{
implicit val aa = "guanyu" // 隱式值
}
object ImplicitDemo {
// 隱式參數,給定了缺省值
def say (implicit name:String = "zhangfei"): Unit = println(s"hello $name")
def main(args: Array[String]): Unit = {
// 不使用隱式參數
say
say("liubei")
import context._
say // 使用隱式參數,import語句導入了一個隱式參數
}
}
hello zhangfei
hello liubei
hello guanyu
1.3 比較器
Comparable可以認爲是一個內比較器,實現了Comparable接口的類有一個特點,就是這些類是可以和自己比較的,至於具體和另一個實現了Comparable接口的類如何比較,則依賴 compareTo方法的實現,compareTo方法也被稱爲自然比較方法。
如果開發者add進入一個Collection的對象想要Collections的sort方法幫你自動進行排序的話,
那麼這個對象必須實現Comparable接口。compareTo方法的返回值是int,有三種情況:
1、比較者大於被比較者(也就是compareTo方法裏面的對象),那麼返回正整數
2、比較者等於被比較者,那麼返回0
3、比較者小於被比較者,那麼返回負整數
private static class Person implements Comparable<Person>{
int age;
String name;
...
/**
* @desc 實現 “Comparable<String>” 的接口,即重寫compareTo<T t>函數。
* 這裏是通過“person的名字”進行比較的
*/
@Override
public int compareTo(Person person) {
return name.compareTo(person.name);
//return this.name - person.name;
}
Comparator可以認爲是是一個外比較器,個人認爲有兩種情況可以使用實現Comparator接口的方式:
1、一個對象不支持自己和自己比較(沒有實現Comparable接口),但是又想對兩個對象進行比較。
2、一個對象實現了Comparable接口,但是開發者認爲compareTo方法中的比較方式並不是自己想要的那種比較方式。
Comparator接口裏面有一個compare方法,方法有兩個參數T o1和T o2,是泛型的表示方式,分別表示待比較的兩個對象,方法返回值和Comparable接口一樣是int,有三種情況:
1、o1大於o2,返回正整數
2、o1等於o2,返回0
3、o1小於o3,返回負整數
private static class AscAgeComparator implements Comparator<Person> {
@Override
//升序
public int compare(Person p1, Person p2) {
return p1.getAge() - p2.getAge();
}
}
Ordered 混入(mix)Java的Comparable接口
Ordering 則混入Comparator接口
實現Comparable接口的類,其對象具有了可比較性;自己比較
實現comparator接口的類,則提供一個外部比較器,用於比較兩個對象。
Comparable是排序接口。若一個類實現了Comparable接口,就意味着該類支持排序。實現了Comparable接口的類的對象的列表或數組可以通過Collections.sort或Arrays.sort進行自動排序。
Comparator是比較接口。通過實現Comparator來新建一個比較器,然後通過這個比較器對類進行排序。
Ordered與Ordering的區別與之相類似:
Ordering特質定義了相同類型間的比較方式,但這種內部比較方式是單一的
Ordered則是提供比較器模板,可以自定義多種比較方式
定義compare方法,Ordered特質會利用這個方法定義<、>、<=、>=。從而,Ordered特質讓你可以通過僅僅實現一個compare方法,使類具有了全套的比較方法:
case class Person1(name: String)
case class Person2(name: String) extends Ordered[Person2] {
def compare(other: Person2) = name compare other.name
}
val p1 = Person1("Tom")
val p2 = Person1("Bob")
val p3 = Person2("Tom")
val p4 = Person2("Bob")
// p1、p1 不能比較,因爲沒有混入Ordered接口
p1 < p2
p3 < p4
// 有兩種等價的定義方式(數字類型有減法,字符串沒有減法操作)
case class Person2(age: Int) extends Ordered[Person2] {
def compare(other: Person2) = age compare other.age }
case class Person2(age: Int) extends Ordered[Person2] {
def compare(other: Person2) = this.age - other.age }
Sort/SortBy/SortWith
單集合
val xs = Seq(1, 5, 3, 4, 6, 2)
xs.sorted // 升序
xs.sortBy(d=>d)
xs.sortWith(_<_)
xs.sorted.reverse // 降序
xs.sortBy(d=>d).reverse
xs.sortWith(_>_)
類的排序
排序規則:按年齡升序;按名稱降序
case class Person(name:String, age:Int)
val p1 = Person("brid", 100)
val p2 = Person("dog", 23)
val p3 = Person("pig", 25)
val p4 = Person("cat", 23)
val pairs = Array(p1, p2, p3, p4)
// 排序方法一
// 年齡升序,名稱降序
pairs.sortBy(person => (person.age, person.name)) (Ordering.Tuple2(Ordering.Int, Ordering.String.reverse))
// 年齡升序,名稱升序
pairs.sortBy(person => (person.age, person.name))
case class Person(name:String, age:Int) extends Ordered[Person]{
override def compare(other : Person): Int = {
if (age - other.age != 0) age - other.age
else other.name.compare(name)
}
}
val p1 = Person("bird", 100)
val p2 = Person("dog", 23)
val p3 = Person("pig", 25)
val p4 = Person("cat", 23)
val pairs = Array(p1, p2, p3, p4)
pairs.sorted
總結:
ordered-->comparable
ordering-->comparator
排序定義規則
case class ClassName(變量1:類型,變量2:類型):返回值類型={
// def compare(other:ClassName)=this.變量1-other.變量1
// def compare(other:ClassName)=變量1.compare(other.變量1)
override def compare(other:ClassName):返回值類型={
if(this.變量1-other.變量1 !=0 )
this.變量1-other.變量1
}else
other.變量1.compare(this.變量1)
}
2.遞歸和尾遞歸
val l=List(1,2,1,3)
l.sum //遞歸的實現
def sum(x:List[Int]):Int={
if(x.isEmpty)或者if(x==Nil)
0
else
x.head+sum(x.tail)
}
遞歸算法需要保持調用堆棧,效率較低,如果調用次數較多,會耗盡內存或棧溢出。然而,尾遞歸可以克服這一缺點。
尾遞歸是指遞歸調用是函數的最後一個語句,而且其結果被直接返回,這是一類特殊的遞歸調用。
由於遞歸結果總是直接返回,尾遞歸比較方便轉換
爲循環,因此編譯器容易對它進行優化。
3.函數式編程 — — 特性
1.immutable data 不可變數據:默認變量是不可變的,
如果要改變變量,需要把變量copy出去修改。這樣一來,
可以讓程序少很多Bug。因爲,程序中的狀態不好維護,
在併發的時候更不好維護。(如果程序有個複雜的狀態,
當以後別人改代碼的時候,是很容易出bug,在並行中
這樣的問題就更多了)
2.first class functions 函數是一等公民:
可以讓函數像變量一樣來使用。也就是說,函數可以像變量
一樣被創建,修改,並當成變量一樣傳遞,返回或是在函數
中嵌套函數。
3.尾遞歸優化:
如果遞歸很深的話,stack受不了,並會導致性能大幅度下降。
使用尾遞歸優化技術——每次遞歸時都會重用stack,這樣能夠提
升性能,需要語言或編譯器的支持。
4. 函數式編程 — — 相關技術
1.少用循環和遍歷,多使用 map 、 reduce等函數。
函數式編程最常見的技術就是對一個集合做Map和Reduce操作。這比
起過程式的語言來說,在代碼上要更容易閱讀。(傳統過程式的語言
需要使用for/while循環,然後在各種變量中把數據倒過來倒過去的)
2.遞歸(recursing)
遞歸最大的好處就簡化代碼,把一個複雜的問題用很簡單的代碼描述出來。
歸的精髓是描述問題,而這正是函數式編程的精髓
3.柯里化(currying)
把一個函數的多個參數分解成多個函數,然後把函數多層封裝起來,每層
函數都返回一個函數去接收下一個參數這樣,可以簡化函數的多個參數
f(x:Int,y:Int)===>>f(x:Int)(y:Int)
={函數體}
4.高階函數(higher order function)
所謂高階函數就是函數當參數,
把傳入的函數做一個封裝,然後返回這個封裝函數。現象上就是函數傳進
傳出,就像面向對象滿天飛一樣
xx.map(_.map(_*2))
5.管道(pipeline)
把函數實例成一個一個的action,然後,把一組
action放到一個數組或是列表中,然後把數據傳給這個action list,數
據就像一個pipeline一樣順序地被各個函數所操作,最終得到我們想要的結果
5.函數和方法的區別
1.不同點
(1)Scala 中的方法與 Java 的類似,方法是組成類的一部分;
(2)Scala 中的函數則是一個完整的對象。
Scala 中用 22 個特質(trait)抽象出了函數的概念,
這 22 特質從 Function1 到 Function22;
a = (x: Int, y: Int) => x+y
a:(Int, Int) => Int = <function2> //兩個參數function2
(3)Scala 中通常用 val 語句定義函數,def 語句定義方法;
def 函數名(參數):返回值類型={函數體}
val 函數名=(變量2:類型,變量3:類型)=>{函數體:x+y}
val 函數名:(輸入類型,類型)=>返回值類型=(輸入值)=>{函數體}
(4)方法不能作爲單獨的表達式存在(參數爲空的方法除外)而函數可以:
def m1(x: Int) = x + x
val f1 = (x: Int) => x + x
m1 // 報錯,方法不能單獨存在
f1
val a = m1 // 報錯
val a = m1 _ // 將方法轉換爲函數
(5)函數必須要有參數列表,而方法可以沒有參數列表
def m1 = 100
def m2() = 100
def f1 = () => 100
val f4 = => 100 // 報錯,函數必須要參數列表
2.定義
函數:
def 函數名(參數):返回值類型={函數體}
方法
val 函數名=(變量2:類型,變量3:類型)=>{函數體:x+y}
val 函數名:(輸入類型,類型)=>返回值類型=(輸入值)=>{函數體}
// 定義了一個函數,下面三個方法等價。其中方法二最常見
val adder1: (Int, Int) => Int = (x, y) => x+y
val adder2 = (x: Int, y: Int) => x+y
val adder3 = new Function2[Int, Int, Int]{
def apply(x: Int, y: Int): Int = {
x + y
}
}
6.下劃線
(1)作用
1. 導入通配符
在Scala中是合法的方法名,導入包時要使用_代替。
Java import Java.util.*
scala import scala.util._
2. 類成員默認值
Java中類成員可以不賦初始值,編譯器會自動幫你設置一個合適的初始值:
class f{
String s;
}
Scala中必須要顯式指定,可以用_讓編譯器自動幫你設置初始值:
class f{
//默認爲null
var s:String=_
}
該語法只適用於類成員,而不適用於局部變量。
3.可變參數
val list=List(1,2,3,4)
list.map(_+2)
4.通配通配符
def list(lis:List[_])=lis.foreach(elem=>println(elem))
_可爲任意類型
(2)模式匹配
1.默認匹配
val list = List(12,21,21)
for(elem<-list)
val a=elem match{
case elem%2==0 => println(elem)
case _ =>println("xxx")
}
2.匹配集合元素
_* :任意長度的元素
// 匹配以0開頭,長度爲三的列表
val expr = List(0,1,2)
expr match {
case List(0, _, _) => println("found it")
case _ =>
}
// 匹配以0開頭,長度任意的列表
val expr = List(0,1,2,3,4)
expr match {
case List(0, _*) => println("found it")
case _ => println("no found")
}
// 匹配元組元素
val expr = (0,1)
expr match {
case (0, _) => println("found it")
case _ => println("no found")
}
// 將首元素賦值給head變量
val List(head, _*) = List("a", "b", "c", "d")
3.Scala特有語法
1.訪問Tuple元素
val t = (1, 2, 3) = val t=Tuple(1,2,3)
println(t._1, t._2, t._3)
2.簡寫函數字面值
如果函數的參數在函數體內只出現一次,則可以使用下劃線代替:
val f1 = (_: Int) + (_: Int)
//等價於
val f2 = (x: Int, y: Int) => x + y
list.foreach(println(_))
//等價於
list.foreach(e => println(e))
list.filter(_ > 0)
//等價於
list.filter(x => x > 0)
3.定義一元操作符 (1+1-->二元操作符)
-2
//等價於
//unary左置操作符
2.unary_-
4.定義賦值操作符
我們通過下劃線實現賦值操作符,從而可以精確地控制賦值過程:
class Foo {
def name = { "foo" }
def name_=(str: String) {
println("set name " + str)
}
}
val m = new Foo()
m.name = "Foo" //等價於: m.name_=("Foo")
5.定義部分應用函數
def sum(a: Int, b: Int, c: Int) = a + b + c
val b = sum(1, _: Int, 3)
// b: Int => Int = <function1>
b(2)
//6
可以爲某個函數只提供部分參數進行調用,
返回的結果是一個新的函數,即部分應用函數。
因爲只提供了部分參數,所以部分應用函數也因此而得名。
6.將方法轉換爲函數
def m1(x:Int)={x+x}
val p=m1 _
p(2)-->4
7.Type
通過type關鍵字來聲明類型;
經常被用來作爲複雜類型的別名,以提供可讀性;
type相當於聲明一個類型別名:
type x=類型
val c:x=值