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