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