Scala精華之處就在這里,拿去,面試也不怕
本文轉(zhuǎn)載自微信公眾號(hào)「大數(shù)據(jù)左右手」,作者左右 。轉(zhuǎn)載本文請(qǐng)聯(lián)系大數(shù)據(jù)左右手公眾號(hào)。
前言
Scala作為一門面向?qū)ο蟮暮瘮?shù)式編程語(yǔ)言,把面向?qū)ο缶幊膛c函數(shù)式編程結(jié)合起來(lái),使得代碼更簡(jiǎn)潔高效易于理解。這就是Scala得到青睞的初衷。
Scala作為一門JVM的語(yǔ)言,大數(shù)據(jù)生態(tài)的大部分組件都是Java語(yǔ)言開發(fā)的,而Scala可以與Java無(wú)縫混編,因此可以很好地融合到大數(shù)據(jù)生態(tài)圈。
主要內(nèi)容
一些基礎(chǔ)東西不再羅列,比如開發(fā)環(huán)境,循環(huán),異常,泛型等等,本篇只介紹獨(dú)到,特殊的精華地方,注重概念理解與用法。
1.變量和數(shù)據(jù)類型
2.函數(shù)式編程
(a)高階函數(shù)
(b)匿名函數(shù)
(c)閉包
(d)函數(shù)柯里化
3.面向?qū)ο?/strong>
(a)類與對(duì)象
(b)伴生對(duì)象
(c)特質(zhì)
4.模式匹配
5.隱式轉(zhuǎn)換
變量和數(shù)據(jù)類型
變量(var聲明變量,val聲明常量)
var 修飾的變量可改變
val 修飾的變量不可改變
但真的如此嗎?
對(duì)于以下的定義
- class A(a: Int) {
- var value = a
- }
- class B(b: Int) {
- val value = new A(b)
- }
效果測(cè)試
- val x = new B(1)
- x = new B(1) // 錯(cuò)誤,因?yàn)?nbsp;x 為 val 修飾的,引用不可改變
- x.value = new A(1) // 錯(cuò)誤,因?yàn)?nbsp;x.value 為 val 修飾的,引用不可改變
- x.value.value = 1 // 正確,x.value.value 為var 修飾的,可以重新賦值
事實(shí)上,var 修飾的對(duì)象引用可以改變,val 修飾的則不可改變,但對(duì)象的狀態(tài)卻是可以改變的。
可變與不可變的理解
我們知道scala中的List是不可變的,Map是可變和不可變的。觀察下面的例子
var可變和List不可變的組合
- var list = List("左","右")
- list += "手"
理解就是
var list指向的對(duì)象是 List("左","右")
后面修改list的指向,因?yàn)槭强勺兊膙ar修飾,list又可以指向新的 List("左","右","手")
如果是以下(會(huì)報(bào)錯(cuò)的)
- val list = List("左","右")
- list += "手"
val var與Map可變和不可變
- var map = Map(
- "左" -> 1,
- "右" ->1,
- )
- map+=("手"->1)
- val map=scala.collection.mutable.Map(
- "左" -> 1,
- "右" ->1,
- )
- map+=("手"->1)
理解
不可變的Map在添加元素的時(shí)候,原來(lái)的Map不變,生成一個(gè)新的Map來(lái)保存原來(lái)的map+添加的元素。
可變的Map在添加元素的時(shí)候,并不用新生成一個(gè)Map,而是直接將元素添加到原來(lái)的Map中。
val不可變的只是指針,跟對(duì)象map沒(méi)有關(guān)系。
數(shù)據(jù)類型
數(shù)據(jù)類型 | 描述 |
---|---|
Byte | 8位有符號(hào)補(bǔ)碼整數(shù)。數(shù)值區(qū)間為 -128 到 127 |
Short | 16位有符號(hào)補(bǔ)碼整數(shù)。數(shù)值區(qū)間為 -32768 到 32767 |
Int | 32位有符號(hào)補(bǔ)碼整數(shù)。數(shù)值區(qū)間為 -2147483648 到 2147483647 |
Long | 64位有符號(hào)補(bǔ)碼整數(shù)。數(shù)值區(qū)間為 -9223372036854775808 到 9223372036854775807 |
Float | 32 位, IEEE 754 標(biāo)準(zhǔn)的單精度浮點(diǎn)數(shù) |
Double | 64 位 IEEE 754 標(biāo)準(zhǔn)的雙精度浮點(diǎn)數(shù) |
Char | 16位無(wú)符號(hào)Unicode字符, 區(qū)間值為 U+0000 到 U+FFFF |
String | 字符序列 |
Boolean | true或false |
Unit | 表示無(wú)值,和其他語(yǔ)言中void等同。用作不返回任何結(jié)果的方法的結(jié)果類型。Unit只有一個(gè)實(shí)例值,寫成()。 |
Null | null 或空引用 |
Nothing | Nothing類型在Scala的類層級(jí)的最底端;它是任何其他類型的子類型。 |
Any | Any是所有其他類的超類 |
AnyRef | AnyRef類是Scala里所有引用類(reference class)的基類 |
函數(shù)式編程
高階函數(shù)
高階函數(shù)是指使用其他函數(shù)作為參數(shù)、或者返回一個(gè)函數(shù)作為結(jié)果的函數(shù)。在Scala中函數(shù)是"一等公民"。
簡(jiǎn)單例子
- val list=List(1,2,3,4)
- val function= (x:Int) => x*2
- val value=list.map(function)
方法為函數(shù)
- def main(args: Array[String]): Unit = {
- val list=List(1,2,3,4)
- val value=list.map(function)
- }
- def function (x:Int)=x*2
返回函數(shù)的函數(shù)
- def calculate(symbol:String): (String,String)=>String ={
- symbol match {
- case "拼接方式1" => (a:String,b:String)=> s"拼接方式1:$a , $b"
- case "拼接方式2" => (a:String,b:String)=> s"拼接方式2: $b , $a"
- }
- }
- val function: (String, String) => String = calculate("拼接方式2")
- println(function("大數(shù)據(jù)", "左右手"))
匿名函數(shù)
Scala 中定義匿名函數(shù)的語(yǔ)法很簡(jiǎn)單,箭頭左邊是參數(shù)列表,右邊是函數(shù)體。
使用匿名函數(shù)后,我們的代碼變得更簡(jiǎn)潔了。
- var inc = (x:Int) => x+1
- var x = inc(7)-1
也可無(wú)參數(shù)
- var user = () => println("大數(shù)據(jù)左右手")
閉包
閉包是一個(gè)函數(shù),返回值依賴于聲明在函數(shù)外部的一個(gè)或多個(gè)變量。
閉包通常來(lái)講可以簡(jiǎn)單的認(rèn)為是可以訪問(wèn)一個(gè)函數(shù)里面局部變量的另外一個(gè)函數(shù)。
簡(jiǎn)單理解就是:函數(shù)內(nèi)部的變量不在其作用域時(shí),仍然可以從外部進(jìn)行訪問(wèn)。
- val function= (x:Int) => x*2
閉包的實(shí)質(zhì)就是代碼與用到的非局部變量的混合
閉包 = 代碼 + 用到的非局部變量
- val fact=2
- val function= (x:Int) => x*fact
函數(shù)柯里化
柯里化指的是將原來(lái)接受兩個(gè)參數(shù)的函數(shù)變成新的接受一個(gè)參數(shù)的函數(shù)的過(guò)程。新的函數(shù)返回一個(gè)以原有第二個(gè)參數(shù)為參數(shù)的函數(shù)。
先定義一個(gè)簡(jiǎn)單的
- def add(x:Int,y:Int)=x+y
- 使用
- add(1,2)
函數(shù)變形(這種方式就叫柯里化)
- def add(x:Int,y:Int)=x+y
- 使用
- add(1,2)
實(shí)現(xiàn)過(guò)程
add(1)(2) 實(shí)際上是依次調(diào)用兩個(gè)普通函數(shù)(非柯里化函數(shù))
第一次調(diào)用使用一個(gè)參數(shù) x,返回一個(gè)函數(shù)類型的值。
第二次使用參數(shù)y調(diào)用這個(gè)函數(shù)類型的值。
- 接收一個(gè)x為參數(shù),返回一個(gè)匿名函數(shù)
- 接收一個(gè)Int型參數(shù)y,函數(shù)體為x+y。
- def add(x:Int)=(y:Int)=>x+y
- (1)
- val result = add(1) // result= (y:Int)=>1+y
- (2)
- val sum = result(2)
- (3)
- sum=3
面向?qū)ο?/h3>
類和對(duì)象
類是對(duì)象的抽象,而對(duì)象是類的具體實(shí)例。類是抽象的,不占用內(nèi)存,而對(duì)象是具體的,占用存儲(chǔ)空間。類是用于創(chuàng)建對(duì)象的藍(lán)圖,它是一個(gè)定義包括在特定類型的對(duì)象中的方法和變量的軟件模板。
類可以帶有類參數(shù)
類參數(shù)可以直接在類的主體中使用。類參數(shù)同樣可以使用var作前綴,還可以使用private、protected、override修飾。scala編譯器會(huì)收集類參數(shù)并創(chuàng)造出帶同樣的參數(shù)的類的主構(gòu)造器。,并將類內(nèi)部任何既不是字段也不是方法定義的代碼編譯至主構(gòu)造器中。
- class Test(val a: Int, val b: Int) {
- //
- }
樣例類
case class一般被翻譯成樣例類,它是一種特殊的類,能夠被優(yōu)化以用于模式匹配。
當(dāng)一個(gè)類被聲名為case class的時(shí)候。具有以下功能:
- 構(gòu)造器中的參數(shù)如果不被聲明為var的話,它默認(rèn)的是val類型的。
- 自動(dòng)創(chuàng)建伴生對(duì)象,同時(shí)在里面給我們實(shí)現(xiàn)子apply方法,使我們?cè)谑褂玫臅r(shí)候可以不直接使用new創(chuàng)建對(duì)象。
- 伴生對(duì)象中同樣會(huì)幫我們實(shí)現(xiàn)unapply方法,從而可以將case class應(yīng)用于模式匹配。
- 實(shí)現(xiàn)自己的toString、hashCode、copy、equals方法
- case class person(
- name:String,
- age:Int
- )
對(duì)象與伴生對(duì)象
Scala單例對(duì)象是十分重要的,沒(méi)有像在Java一樣,有靜態(tài)類、靜態(tài)成員、靜態(tài)方法,但是Scala提供了object對(duì)象,這個(gè)object對(duì)象類似于Java的靜態(tài)類,它的成員、它的方法都默認(rèn)是靜態(tài)的。
定義單例對(duì)象并不代表定義了類,因此你不可以使用它來(lái)new對(duì)象。當(dāng)單例對(duì)象與某個(gè)類共享同一個(gè)名稱時(shí),它就被稱為這個(gè)類的伴生對(duì)象。
類和它的伴生對(duì)象必須定義在同一個(gè)源文件里。類被稱為這個(gè)單例對(duì)象的伴生類。
類和它的伴生對(duì)象可以互相訪問(wèn)其私有成員。
- object Test {
- private var name="大數(shù)據(jù)"
- def main(args: Array[String]): Unit = {
- val test = new Test()
- println(test.update_name())
- }
- }
- class Test{
- def update_name(): String ={
- Test.name="左右手"
- Test.name
- }
- }
特質(zhì)(trait)
scala trait相當(dāng)于java 的接口,實(shí)際上它比接口還功能強(qiáng)大。與接口不同的是,它還可以定義屬性和方法的實(shí)現(xiàn)。
一般情況下scala的類只能夠繼承單一父類,但是如果是trait 的話就可以繼承多個(gè),從結(jié)果來(lái)看就是實(shí)現(xiàn)了多重繼承(關(guān)鍵字with)。其實(shí)scala trait更像java的抽象類。
- object Test extends UserImp with AddressImp {
- override def getUserName(): String = ???
- override def getAddress(): String = ???
- }
- trait UserImp{
- def getUserName():String
- }
- trait AddressImp{
- def getAddress():String
- }
模式匹配
以java 的 switch 為例,java 的 switch 僅僅會(huì)做一些基本類型的匹配,然后執(zhí)行一些動(dòng)作,并且是沒(méi)有返回值的。
而 scala 的 pattern matching match 則要強(qiáng)大得多,除了可以匹配數(shù)值,同時(shí)它還能匹配類型。
- def calculate(symbol:String): (String,String)=>String ={
- symbol match {
- case "拼接方式1" => (a:String,b:String)=> s"拼接方式1:$a , $b"
- case "拼接方式2" => (a:String,b:String)=> s"拼接方式2: $b , $a"
- }
- }
讓我吃驚的是(就短短幾行)
- 快排
- def quickSort(list: List[Int]): List[Int] = list match {
- case Nil => Nil
- case List() => List()
- case head :: tail =>
- val (left, right) = tail.partition(_ < head)
- quickSort(left) ::: head :: quickSort(right)
- }
- 歸并
- def merge(left: List[Int], right: List[Int]): List[Int] = (left, right) match {
- case (Nil, _) => right
- case (_, Nil) => left
- case (x :: xTail, y :: yTail) =>
- if (x <= y) x :: merge(xTail, right)
- else y :: merge(left, yTail)
- }
隱式轉(zhuǎn)換
Scala提供的隱式轉(zhuǎn)換和隱式參數(shù)功能,是非常有特色的功能。是Java等編程語(yǔ)言所沒(méi)有的功能。它可以允許你手動(dòng)指定,將某種類型的對(duì)象轉(zhuǎn)換成其他類型的對(duì)象。通過(guò)這些功能,可以實(shí)現(xiàn)非常強(qiáng)大,而且特殊的功能。
規(guī)則
(1)在使用隱式轉(zhuǎn)換之前,需要用import把隱式轉(zhuǎn)換引用到當(dāng)前的作用域里或者就在作用域里定義隱式轉(zhuǎn)換。
(2)隱式轉(zhuǎn)換只能在無(wú)其他可用轉(zhuǎn)換的前提下才能操作。如果在同一作用域里,對(duì)同一源類型定義一個(gè)以上的隱式轉(zhuǎn)換函數(shù),如果多種隱式轉(zhuǎn)換函數(shù)都可以匹配,那么編譯器將報(bào)錯(cuò),所以在使用時(shí)請(qǐng)移除不必要的隱式定義。
數(shù)據(jù)類型的隱式轉(zhuǎn)換
String類型是不能自動(dòng)轉(zhuǎn)換為Int類型的,所以當(dāng)給一個(gè)Int類型的變量或常量賦予String類型的值時(shí)編譯器將報(bào)錯(cuò)。但是.....
- implicit def strToInt(str: String) = str.toInt
- def main(args: Array[String]): Unit = {
- val a:Int="100"
- print(a)
- }
參數(shù)的隱式轉(zhuǎn)換
所謂的隱式參數(shù),指的是在函數(shù)或者方法中,定義一個(gè)用implicit修飾的參數(shù),此時(shí)Scala會(huì)嘗試找到一個(gè)指定類型的,用implicit修飾的對(duì)象,即隱式值,并注入?yún)?shù)。
- object Test {
- private var name="大數(shù)據(jù)"
- implicit val test = new Test
- def getName(implicit test:Test): Unit ={
- println(test.update_name())
- }
- def main(args: Array[String]): Unit = {
- getName
- }
- }
- class Test{
- def update_name(): String ={
- Test.name="左右手"
- Test.name
- }
- }