摘要:當調用該函數或方法時,如果沒有傳該參數的值,Scala會嘗試在變量作用域中找到一個與指定類型相匹配的使用implicit修飾的對象,即隱式值,注入到函數參數中函數體使用。分析 :本題考查了函數的使用、讀取文件、可變集合Map、迭代器、循環,統計單詞的個數在很多編程題中都會出現。

點擊上方“ 大數據與人工智能 ”,“星標或置頂公衆號”

第一時間獲取好內容

導語

本篇文章爲大家帶來Scala面試指南,本文會結合數據分析工程師在工作中會用到的知識點和網絡上搜集的Scala常用考點,組成一份Scala精選題庫,並附上詳細的解答,力圖爲Scala面試者掃清知識盲點,提煉經典考題。

爲什麼要考察Scala?

1

開發需要

最簡單和重要的理由是開發需要,大數據分析工程師是需要掌握大數據相關組件的,而很多大數據組件是由Scala開發的-如Spark和Kafka,所以相關的開發,Scala就成爲了首選開發語言。

2

語言本身的魅力

Scala作爲一門面向對象的函數式編程語言,如其官網宣稱的:“Object-OrientedMeets Functional”,這一句當屬對Scala最抽象的精準描述,它把近二十年間大行其道的面向對象編程與舊而有之的函數式編程有機結合起來,形成其獨特的魔力,可以使你的代碼更簡潔高效易於理解。同樣的需求,不同水平的Scala工程師寫出來的代碼會有很大不同,所以考察Scala代碼能力就能大致看出其編程水平。

那如何準備Scala的面試呢?

Scala精選題庫

學過Scala的同學肯定都會吐槽Scala難學,它將面向對象和函數式編程這兩種不同的軟件工程範式結合起來,它還有一個複雜的類型系統,所以對於Scala的考察涉及到的知識點非常多。想要通過Scala的面試,除了平時在學習和工作中的總結以外,刷題是一個很好的辦法,本文會結合數據分析工程師工作中需要掌握的知識點做一個篩選,最終挑選出如下的考題,主要分爲問答題和手寫題,仔細看看有沒有你不知道的知識點?

問答題

語言特性類

這一部分可能會考察Scala語言特性、相關概念類的知識點,或者與其他語言的比較,考察的是對Scala語言宏觀上的認知。下面給出了經常會被問到的兩道題目,其他相關的知識點-如與其他語言的比較,可以自行延伸補充。

1

scala語言有什 麼特點?

Scala 是一種有趣的語言。它一方面吸收繼承了多種語言中的優秀特性,一方面又沒有拋棄 Java 這個強大的平臺,它運行在 JVM 之上,輕鬆實現和豐富的 Java 類庫互聯互通。它既支持面向對象的編程方式,又支持函數式編程。它寫出的程序像動態語言一樣簡潔,但事實上它卻是嚴格意義上的靜態語言。總結起來就是面向對象和函數式的完美結合。

2

什麼是函數式編程?有什麼優點?

簡單來說,"函數式編程"是一種編程範式(programming paradigm),也就是如何編寫程序的方法論。它屬於結構化編程的一種,主要思想是把運算過程儘量寫成一系列嵌套的函數調用。

優點

1)代碼簡潔,開發快速,大量使用函數,減少了代碼的重複,因此程序比較短,開發速度較快;

2)接近自然語言,易於理解,函數式編程的自由度很高,可以寫出很接近自然語言的代碼;

3)代碼管理方便,一個函數只需要給相應的參數,最終返回一個值,它不依賴也不改變外界的狀態,只爲了完成相應的運算,所以一個函數可以看做一個獨立的單元,這樣就方便單元測試和組合。

4)易於併發操作,不修改變量也就不存在鎖線程的問題,可以放心地用於併發編程。

變量相關

變量相關的知識點會涉及修飾符、類型、參數等概念,以及如何定義變量、關鍵字的區別等問題,是比較基礎的知識點,主要還是在於多總結然後進行理解。

3

var,val和def三個關鍵字之間的區別?

var是變量聲明關鍵字,類似於Java中的變量,變量值可以更改,但是變量類型不能更改。

val常量聲明關鍵字,不允許修改值。

def關鍵字用於創建方法。

4

Scala訪問修飾符有哪些?

Scala 訪問修飾符基本和Java的一樣,分別有:private,protected,public。如果沒有指定訪問修飾符,默認情況下,Scala 對象的訪問級別都是 public。

5

Scala中Unit類型是什麼?

Unit類型類似於Java中的void類型,代表沒有任何意義的值類型。

6

Scala類型系統中Nil,Null,None,Nothing四種類型的區別?

在Scala中這四個類型名稱很類似,作用卻是完全不同的。下圖是Scala類型圖,有助於理解它們間的區別。       

Nil 代表一個List空類型,等同List[Nothing],根據List的定義List[+A],所有Nil是所有List[T]的子類;

Null 是所有AnyRef的子類,在Scala的類型系統中,AnyRef是Any的子類,同時Any子類的還有AnyVal, null是Null的唯一對象;

None 是一個object,是Option的子類型,代表沒有值;

Nothing 是所有類型的子類,也是Null的子類。Nothing沒有對象,但是可以用來定義類型。例如,如果一個方法拋出異常,則異常的返回值類型就是Nothing(雖然不會返回)。

7

你知道vararg參數的用法嗎?

當函數的參數個數有多個,或者不固定的時候,可以使用vararg參數,具體的使用方式是在參數類型後面加一個“*”,相應的參數在函數體中就是一個集合,根據需要對參數進行解析。如果要傳入的參數在集合中,可以將這個集合作爲參數傳入,需要在集合名後面加上“:_*”。

表達式相關

從表達式開始涉及的知識點會越來越複雜,會涉及到匹配表達式、循環、正則,另外也要關注if……else、值綁定和通配符如何使用。

8

說說你對匹配表達式/模式匹配的理解?什麼是模式守衛?

匹配表達式也就是match操作,類似C和Java中的“switch”語句,逐個匹配case表達式中的值,然後進行返回。模式校位目的是爲匹配表達式增加條件邏輯,具體的做法是在case後面的匹配語句增加一個if表達式。

9

yield是如何工作的?

Scala中的yield的主要作用是記住每次迭代中的有關值,並逐一存入到一個數組中。

用法如下:for {子句} yield {變量或表達式},yield後面的語句其實就是一個循環體,只不過最終會將所有的循環結果放到一個集合中進行返回。

10

如何使用正則匹配?

Scala 的正則表達式繼承了 Java 的語法規則,Java 則大部分使用了 Perl 語言的規則。Scala 通過 Scala.util.matching 包中的 Regex 類來支持正則表達式,參考示例代碼如下:

import scala.util.matching.Regex



val pattern = "Scala".r

val str = "Scala is Scalable and cool"

println(pattern findFirstIn str)

//執行結果:Some(Scala)

常用的匹配運算有:findFirstIn-找到首個匹配項,findAllIn-找到所有匹配項,replaceFirstIn-找到首個匹配項並進行替換,replaceAllIn-找到所有的匹配項並進行替換。

函數相關

函數在Scala中是一等公民,對這一塊的考察應該是最多的,函數如何定義?什麼是方法?偏函數、閉包、科裏化等概念如何理解?高階函數有哪些?什麼是尾遞歸?什麼是部分應用函數?除此之外也要關注嵌套函數、函數字面量、傳名參數這些知識點。

11

方法和函數的區別?

方法是定義在類中的函數,這個類進行實例化後會有一個同名的方法,一般調用方法的做法是使用綴點記法-實例名.方法名(參數……)

12

什麼是偏函數?

有偏函數也有全函數,全函數是指能夠正確地支持滿足輸入參數類型的所有可能的值,而偏函數是指不能夠完全支持滿足輸入參數類型的可能的值,如果輸入了這樣的值函數無法正常工作。比如一個開平方根的函數輸入的是一個負值,那麼函數就無法工作。

13

什麼是高階函數?

高階函數在數據分析中使用到的頻率是很高的,可能你辛苦一天寫的函數代碼,一個高階函數輕鬆搞定。首先高階函數肯定是函數,不同的是輸入的參數和返回的值這兩項中的一項必須是函數才能叫高階函數。這個問題在回答的時候可以稍微拓展一下,介紹一下常用的的高階函數,比如:map、flatMap、filter、reduce、fold。

14

什麼是尾遞歸?

正常的遞歸,每一次遞歸操作,需要保存信息到堆棧中,當遞歸步驟達到一定量的時候,就可能會導致內存溢出,而尾遞歸,就是爲了解決這樣的問題,在尾遞歸中所有的計算都是在遞歸之前調用,也就是說遞歸一次計算一次,編譯器可以利用這個屬性避免堆棧錯誤,尾遞歸的調用可以使信息不插入堆棧,從而優化尾遞歸。

15

說說你對閉包的理解?

簡單的理解就是:函數內部的變量不在其作用域時,仍然可以從外部進行訪問。一般的構成是在嵌套函數中,內部的函數體可以訪問外部函數體的變量,在本質上,閉包是將函數內部和函數外部連接起來的橋樑。

16

你瞭解部分應用函數嗎?

部分應用函數可以從字面含義進行解釋,只使用一個函數的部分功能-只使用部分參數,其他參數的值固定,可以將原函數直接調用,然後對於需要固定的參數,直接在參數中輸入相應的值,需要變化的參數使用“_”,需要注意的是通配符要指定類型,這樣生成的一個新函數就是部分應用函數。

17

什麼是函數柯里化?

柯里化指的是將原來接受兩個參數的函數變成新的接受一個參數的函數的過程。新的函數返回一個以原有的第二個參數作爲參數的函數,所以科裏化是一種返回函數的函數,目的是簡化參數,是函數編寫更加簡潔和趨向自然語言。

def mulNormal(x:Int,y:Int) = x * y //該函數每次執行需要兩個參數

def mulOneAtTime(x:Int) = (y:Int) => x * y //該函數先接受一個參數生成另外一個接受單個參數的函數

這樣的話,如果需要計算兩個數的乘積的話只需要調用:mulOneAtTime(5)(4)

18

call-by-value和call-by-name求值策略的區別?

1)call-by-value是在調用函數之前計算;

2)call-by-name是在需要時計算。

集合相關

集合雖然種類有限,但是如果不注意區分還是很容易弄混,需要掌握不同集合的特點、使用場景、常用的集合函數、集合間的轉換等。

19

Scala中的常用的集合有哪些?

這個問題主要考察集合的基礎知識,說出常用的集合,並對每種集合的特徵加以描述就可以了。

List列表: 以線性方式存儲,集合中可以存放重複對象;

Set集合 集合中的對象不按特定的方式排序,並且沒有重複對象;

Map映射: 是一種把鍵對象和值對象映射的集合,它的每一個元素都包含一對鍵對象和值對象;

()元組: 不同類型的值放到一起的集合;

Option選項: 單元素集合,用來表示一個值是可選的(有值或無值);

20

怎麼理解可變集合和不可變集合?

可變集合可以在適當的地方被更新或擴展,這意味着你可以修改,添加,移除一個集合的元素。而不可變集合類,相比之下,永遠不會改變。不過,你仍然可以模擬添加,移除或更新操作。但是這些操作將在每一種情況下都返回一個新的集合,同時原來的集合不發生改變。

標準的可變集合類型有:

collection.mutable.Buffer

collection.mutable.Set

collection.mutable.Map

標準的不可變集合類型有:

collection.immutable.List

collection.immutable.Set

collection.immutable.Map

21

集合之間可以轉換嗎?舉例說明下

集合之間是很容易相互轉換的,根據具體的需要調用相應的方法進行轉換,如:toList、toMap、toSet。

22

如何實現Scala和Java集合的兼容性?

Scala在JVM上編譯運行的時候需要與JDK以及其他Java庫進行交互,這部分的交互就會涉及到Scala和Java集合之間轉換,默認情況下這兩個集合是不兼容的,所以在代碼中需要增加如下命令:

1)import collection.JavaConverters._

這樣就增加了asJava和asScala這兩個操作來實現它們集合之間的轉換。

2)import collection.JavaConversions._

這裏引入的是scala與java集合的隱式轉換,就不需要特意進行asJava和asScala的轉換,直接使用Java或者Scala的方法。

23

談談你對Scala中數組的理解

Array是一個大小固定的可變索引集合。Scala中集合是不包括Array的,Array類型實際上是Java數組類型的一個包裝器。Array中的第一個元素角標是0。

24

你知道迭代器嗎?

Scala Iterator(迭代器)不是一個集合,它是一種用於訪問集合的方法。迭代器 it 的兩個基本操作是 next 和 hasNext。調用 it.next() 會返回迭代器的下一個元素,並且更新迭代器的狀態。

調用 it.hasNext() 用於檢測集合中是否還有元素。

除此之外常用的還有:

it.max/min-查找最大值/最小值

it.size/length-迭代器中元素的個數

it.mkString-將所有元素轉換成字符串

it.sum-對所指數值型元素的和

25

Option類型的使用場景?

Option類型表示一個值的存在與否,一般在程序中需要返回一個空對象的時候,使用Option類型,如果返回null,程序會引起異常,而Option就不會。使用Option減少觸發NullPointerException異常的可能性。

26

Option,Try和Either三者的區別?

這3個都是用來處理函數沒有按預期執行的計算結果。

Option表示可選值,它的返回類型是Some(代表返回有效數據)或None(代表返回空值)。

Try類似於Java中的try/catch,如果計算成功,返回Success的實例,如果拋出異常,返回Failure,try中是需要捕獲異常的執行程序。

Either可以提供一些計算失敗的信息,Either有兩種可能返回類型:預期/正確/成功的 或 錯誤的信息。

面向對象類

對於面向對象的考察更多是概念,如對象、類、抽象類、單例對象、伴生對象、構造器、特質,如何繼承?還需要關注重載、apply/unapply方法、包裝語法。

27

object和class的區別?

1)object是類的單例對象,開發人員無需用new關鍵字實例化

2)object不能提供構造器參數,也就是說object必須是無參的。

3)main方法只能在object中有效,Scala 中沒有 static 關鍵字,對於一個class來說,所有的方法和成員變量在實例被 new 出來之前都是無法訪問的因此class文件中的main方法也就沒什麼用了,而object中所有成員變量和方法默認都是static的,所以object中可以直接訪問main方法。

28

什麼是伴生對象和伴生類?

在Scala中,單例對象object與class名稱相同時,該對象被稱爲該類的伴生對象,該類被稱爲該對象的伴生類。伴生類和伴生對象要處在同一個源文件中,伴生對象和伴生類可以互相訪問其私有成員,不與伴生類同名的對象稱之爲孤立對象。

29

類的參數加和不加關鍵字(val和var)有區別嗎?

有區別的,不加關鍵字的話,這個參數只能用於類的實例化,一旦實例化後這些參數就不可以使用了,如果加關鍵字的話這些參數就成爲類中的一個字段。

30

case class(樣本類)是什麼?

case class是不可實例化的類,一旦構建了這個類,它會自動生成一些方法和伴生對象,注意的是這個伴生對象也會自動生成一些自己的方法。它具有以下特性:

(1)構造對象時,不需要new;

(2)類中的參數默認添加val關鍵字,即參數不能修改,如果需要的話也可以使用var;

(3)默認實現了toString,equals,hashcode,copy,apply,unapply等方法;

31

abstract class(抽象類)和trait(特質)的區別?

在Scala工程中抽象類和特質是很有用的工具,這個問題需要先回答什麼是抽象類以及什麼是特質。

抽象類是在普通類的基礎上增加了abstract關鍵字,無法對其進行實例化,它是用來被子類繼承的,抽象類中可以只定義字段和方法,具體的值和實現在其子類中實現,子類也可以進行重寫。

特質是一種特殊的類,它支持多重繼承,但是trait不能有類參數也不能實例化。

總結一下它們的區別:

(1)一個類只能繼承一個抽象類,但是可以通過with關鍵字繼承多個特質;

(2)抽象類有帶參數的構造函數,特質不行。

32

如何進行多重繼承?

Scala類的擴展只支持一個父類,要想實現多重繼承有兩種方法:

1)多次擴展,假設4個類A、B、C、D——D繼承於C,C繼承於B、B繼承於A,那麼類D實例化後就可以使用A、B、C類中的變量和方法了,曲線實現了多重繼承;

2)使用特質-Trait,這是比較常用的方法,通過with關鍵字將多個特質加入,達到多重繼承的目的,讀取特質的順序是從右往左。

其他類

33

談談scala中的隱式轉換

當需要某個類中的一個方法,但是這個類沒有提供這樣的一個方法,需要進行類型轉換,轉換成提供了這個方法的類,然後再調用這個方法,想要這個類型轉換自動完成,就需要提前定義隱式轉換函數,這樣在使用要轉換類型的方法的時候就可以自動轉換。這個隱式轉換函數可以通過導入相關的包來完成-比如java和Scala幾個之間的相互轉換就可以導入 Scala.collection.JavaConversions 類中的函數來實現 ,也可以自己編寫。

34

什麼是隱式參數?

所謂隱式參數,指的是在函數或者方法中,定義使用implicit修飾的參數。當調用該函數或方法時,如果沒有傳該參數的值,Scala會嘗試在變量作用域中找到一個與指定類型相匹配的使用implicit修飾的對象,即隱式值,注入到函數參數中函數體使用。值得注意的是,隱式參數是根據類型匹配的,因此作用域中不能同時出現兩個相同類型的隱式變量,否則編譯時會拋出隱式變量模糊的異常。

35

如何處理異常?

Scala通過捕獲異常,捕獲後可以進行處理,或者拋出給上游程序,拋出異常的方法和 Java一樣,使用 throw 關鍵字。 如要要對一段代碼的執行進行異常檢測,使用try將這段代碼包起來,在catch語句中進行異常的匹配,借用了模式匹配的思想catch語句中是一系列的case字句。 需要注意的是與try……catch成對出現的還有finally語句-用於執行不管是正常處理還是有異常發生時都需要執行的步驟。

手寫題

通過手寫代碼(紙質試卷或者上機編碼)來考查應聘者的編碼能力是很常用的做法,手寫代碼直接考查面試者平時的代碼積累,大多數人平時寫代碼都是使用開發工具-例如IDEA,會有代碼提示、自動補全等功能,加快了編碼者的工作效率,但是同時也弱化了手寫代碼能力,如果是去面試還是要強化一下這方面的能力。以下試題大部分參考《快學Scala》課後習題,可以用來檢驗下自己的手寫代碼能力。

0

1

編寫一個函數printDown(n:Int),使用模式匹配,打印從n到0的數字。

分析:本題考查的知識點是函數的定義、模式匹配的使用、循環的使用

注意點:要考慮 n<0 的情況。

參考答案:

import scala.language.postfixOps//程序執行的時候需要導入的包

def printDown(n: Int) {

n match {

case n if n >= 0 => {

(0 to n reverse) foreach println

}

case n if n < 0 => {

n to 0 foreach println

}

}

}

0

2

編寫一段程序, 從文件中讀取單詞, 用一個可變映射來清點每一個單次出現的頻率,讀取這些單詞的操作可以使用java.util.Scanner:

val in = new java.util.Scanner(new java.io.File("myfile.txt:)),

while(in.hasNext()) 處理 in.next() 最後, 打印出所有單次和它們出現的次數。

分析 :本題考查了函數的使用、讀取文件、可變集合Map、迭代器、循環,統計單詞的個數在很多編程題中都會出現。

參考答案:

import java.io.File

import java.util.{Scanner, StringTokenizer}



def wordCount(path: String): Scala.collection.mutable.Map[String, Int] = {

import Scala.collection.mutable.Map



val res = Map[String, Int]()

val in = new Scanner(new File(path))

while (in.hasNext()) {

val st = new StringTokenizer(in.next())

while (st.hasMoreTokens()) {

val key = st.nextToken()

res(key) = res.getOrElse(key, 0) + 1

}

}

res

}

0

3

編寫一個Time類, 加入只讀屬性hours和minutes, 和一個檢查某一時刻是否早於另一時刻的方法before(other: Time): Boolean。 Time對象應該以new Time(hrs, min)方式構建,其中hrs小時數以軍用時間格式呈現(介於0和23之間)。

分析 :本題考查類的構建

參考答案:

def timeDemo(hr: Int, min: Int): Unit = {

class Time(hr: Int, min: Int) {

val hour = hr

val minute = min



def before(t: Time): Boolean = {

if (hour < t.hour) true else if (hour > t.hour) false else if (minute < t.minute) true else false

}

def <(t: Time): Boolean = before(t)

}

println(new Time(1, 2) < new Time(2, 1))

}

0

4

編寫一個函數,接受一個字符串的集合,以及一個字符串到整型值的映射,返回整型集合, 其值爲能和集合中某個字符串相對應的映射的值。 舉例來說,給定Array("Tom","Fred","Harry")和Map("Tom"->3,"Dick"->4,"Harry"->5),返回Array(3,5), 提示: 用flatMap將get返回的Option值組合在一起。

分析 :本題主要考查高階函數的用法

參考答案:

def highOrderFunction(arr: Array[String], map: Map[String, Int]): Array[Int] = {

arr.flatMap (map.get _ )

}

highOrderFunction(Array("a", "b"), Map("a" -> 100, "b" -> 2)) foreach println

0

5

實現冒泡排序算法

分析 :冒泡排序算法原理

1)比較相鄰的元素,如果第一個比第二個大,就交換; 

2)對每一對相鄰元素作同樣的工作,從開始第一對到結尾的最後一對;

3)針對所有的元素重複以上的步驟,除了最後一個; 

4)持續每次對越來越少的元素重複上面的步驟,直到沒有任何一對數字需要比較;

參考答案:

// 內層循環做排序

def bSort(data: Int, dataSet: List[Int]): List[Int] = dataSet match {

case List() => List(data)

case head :: tail => if (data <= head) data :: dataSet else head :: bSort(data, tail)

}



// 外層循環做拆分

def bubbleSort(l: List[Int]): List[Int] = l match {

case List() => List()

case head :: tail => bSort(head, bubbleSort(tail))

}



val list = List(97, 12, 43, 33, 7, 1, 2, 20)

println(bubbleSort(list))

//執行結果:List(1, 2, 7, 12, 20, 33, 43, 97

小結

本篇Scala面試指南通過精選題庫的方式將數據分析工作中涉及到的知識點儘可能完整地分佈到問題中,涉及到的Scala知識點有基礎的也有概念比較繞的,在手寫代碼類中的題目難易結合,希望這篇文章能夠幫助準備面試大數據分析相關崗位的數據從業者查漏補缺,完善自己的Scala知識庫。

參考文獻

[1] Scala學習手冊,作者: Jason Swartz

[2] 快學Scala,作者: Cay S. Horstmann

[3] 【譯】Scala面試問題(Scala interview questions),作者: IIGEOywq - https://www.jianshu.com/p/ace2bb24dc11

[4] scala面試題總結,作者:

郭小白 - https://www.cnblogs.com/Gxiaobai/p/10460336.html

-end-

相關文章