463
技術社區[雲棲]
《Spark大數據分析:核心概念、技術及實踐》Scala編程
Scala編程
Scala是當前熱門的現代編程語言之一。它是編程語言界的凱迪拉克。它是一門強大且優美的語言。學會了它,對你的職業生涯大有裨益。
用不同的編程語言都可以編寫大數據應用程序,比如Java、Python、C++、Scala等。Hadoop本身就是用Java編寫的。盡管大多數的Hadoop應用程序都是用Java編寫的,但它也支持用其他語言來編寫。類似地,Spark是由Scala編寫的,但是它也支持其他的語言,包括Scala、Java、Python和R。
對於開發大數據應用程序而言,Scala是一門很棒的語言。使用它有諸多好處。首先,開發者使用Scala能顯著提高生產力。其次,它能幫助開發者減少bug,寫出健壯的代碼。最後,Spark就是用Scala編寫的,因而對於開發Spark應用而言使用Scala是最自然的。
本章把Scala當作一門通用語言來進行介紹。本章的目的並不是讓你成為Scala專家,而是讓你有足夠多的Scala知識從而能理解並使用Scala來編寫Spark應用。本書的示例代碼全部都由Scala編寫,所以掌握Scala,將更容易理解本書的內容。如果你已經學會了Scala,那麼你可以跳過這一章。
基於上述目的,本章隻介紹Scala編程的基礎知識。為了能更高效地使用Scala,了解函數式編程是很重要的,所以本章將首先介紹函數式編程。最後,將介紹如何編寫一個單獨的Scala應用程序。
2.1 函數式編程
函數式編程是一種編程範式,它把函數作為代碼的基本構成元素,避免使用命令式編程中的可變變量以及循環等控製結構。它把計算當作對數學函數的求值,函數的輸出完全依賴於傳遞給函數的參數值。程序就是由這些函數構成的。總之,在函數式編程語言中函數是一等公民。
在這幾年,函數式編程引起了大量的關注。甚至一些主流語言(比如C++、Java和Python)都添加了對函數式編程的支持。它的流行主要有如下幾個原因。
首先,使用函數式編程能極大地提升生產效率。相比於命令式編程,在解決同樣的問題上,函數式編程隻需要更少的代碼。舉例來說,在Java中一個用一百行代碼實現的功能,用Scala來實現隻需要10行或20行即可。函數式編程能帶來5~10倍的生產效率提升。
其次,函數式編程更容易編寫多並發或多線程的應用。隨著多核、多CPU計算機的來臨,編寫多進程應用顯得愈發重要。在當前提升單CPU晶體管數量已經越來越難的情況下,硬件產商開始采用增加CPU數量、增加內核數的方式來維持摩爾定律的有效性。現在多核計算機已經很普遍了。應用程序也需要利用多核帶來的優勢。相對於命令式編程語言,使用函數式編程語言更易於編寫利用多核的應用程序。
再者,函數式編程能幫助你寫出健壯的代碼。它可以幫你避免一些常見的編程錯誤。而且一般來說,應用中bug的數量與代碼和行數成正比。由於函數式編程相對於命令式編程通常代碼行數更少,因此使用它將帶來更少的bug。
最後,使用函數式編程語言更容易寫出便於閱讀、理解的優雅代碼。如果運用得當,用函數式編程語言寫出來的代碼看上去是很漂亮的,既不複雜也不混亂。你將從你的代碼中得到巨大的快樂和滿足。
本節將會介紹函數式編程中的幾個重要概念。
2.1.1 函數
函數是一段可以執行的代碼。通過函數,程序員可以將一個大型的程序分割成一些方便管理的代碼片段。在函數式編程中,應用程序就是構建在函數之上的。
盡管很多編程語言都支持函數的感念,但是函數式編程語言把函數當成一等公民。而且,函數是可以隨意組合的,並沒有其他的副作用。
一等公民
函數式編程把函數當成一等公民。函數擁有和變量、值一樣的地位。函數可以像變量一樣使用。如果你把函數式編程中的函數和命令式編程語言(比如C)中的函數對比,更容易理解這個概念。
命令式編程語言中變量和函數是區別對待的。舉例來說,C語言中是不允許在函數內部定義函數的,它也不允許把一個函數當成參數傳給另外一個函數。
函數式編程允許把函數當成參數傳遞給另一個函數。函數也可以當成返回值從另一個函數中返回。函數可以在任何地方定義,包括在其他函數內部。它可以寫成匿名函數字麵量,然後像字符串字麵量一樣作為參數傳遞給另一個函數。
可組合
函數式編程中,函數是可以隨意組合的。函數組合指的是可以把一些簡單的函數組合在一起從而創建一個複雜的函數,它是一個數學概念也是一個計算機科學概念。比如,兩個可組合的函數可以組合成一個新函數。考慮下麵兩個數學函數。
f(x)=x*2
g(x)=x+2
函數f以一個數字作為輸入,將它的兩倍作為輸出。函數g以一個數字作為輸入,將它的值加2作為輸出。
如下所示,可以將f和g組合為一個新的函數。
h(x)=f(g(x))=f(x+2)=(x+2)*2
對於同樣的輸入,函數h的效果和先調用函數g處理輸入,然後將函數g的輸出作為參數調用函數f是一樣的。
為了解決複雜的問題,可以將它分解成幾個小問題,函數組合在這種方式下是很有用的。對於每個小問題可以寫若幹個函數,最後再把它們組合起來,從而解決這個複雜的問題。
無副作用
在函數式編程中函數並沒有什麼副作用。函數的返回值完全依賴於傳遞給它的參數。函數的行為並不會隨著時間的改變而改變。對於給定的參數值,無論調用這個函數多少次,始終返回同樣的結果。換句話說,函數是無狀態的,它既不依賴也不會改變任何全局變量的值。
函數的無副作用性有如下幾個好處。首先,它們可以按任意順序組合在一起。其次,便於理解代碼。最後,使用這樣的函數便於編寫多線程的應用程序。
簡單
函數式編程中的函數是很簡單的。一個函數由幾行代碼構成,並且隻做一件事。一個簡單的函數是便於理解的。函數也保證了代碼的健壯性以及高質量。
可組合的簡單函數很適合用來實現複雜的算法,並且不容易出錯。函數式編程鼓勵人們不斷把問題分解成若幹個子問題,直到一個子問題能夠用一個簡單函數去解決為止。然後,把這些簡單函數組合起來就能得到解決這個複雜問題的函數了。
2.1.2 不可變數據結構
函數式編程強調使用不可變數據結構。純函數方式程序並不會使用任何可變數據結構或變量。換句話說,數據從不會在原地修改,這與命令式編程語言(比如C/C++、Java和Python)相比是不同的。沒有函數式編程背景的人很難想象沒有可變變量的程序是什麼樣子的。實際上,使用不可變數據結構寫代碼並非難事。
使用不可變數據結構具有如下好處。首先,它能減少bug。使用不可變數據結構編寫的代碼便於理解。另外,函數式編程語言的編譯器會強製使用不可變的數據結構。因而,很多bug在編譯期間就能捕獲。
其次,使用不可變數據結構便於編寫多線程應用程序。編寫一個能充分利用多核的應用程序並不容易。競爭條件和數據損壞在多線程應用程序中是常見的問題。使用不可變數據結構有助於避免這些問題。
2.1.3 一切皆表達式
在函數式編程中,每一個語句都是一個表達式,都會有返回值。比如Scala中的if-else控製結構就是一個有返回值的表達式。這與可以在if-else中寫多個語句的命令式編程語言顯著不同。
這一特性有助於不用可變變量編寫應用程序。
2.2 Scala基礎
Scala是一門支持麵向對象編程和函數式編程的混合語言。它支持函數式編程的概念,比如不可變數據結構,把函數視為一等公民。在麵向對象方麵,它也支持類、對象、特質、封裝、繼承、多態和其他麵向對象的概念。
Scala是一門靜態類型語言。Scala應用程序是由Scala編譯器編譯的。Scala是類型安全的,Scala編譯器在編譯期間會強製檢查類型安全性。這有助於減少應用程序中的bug。
最後,Scala是一門基於JVM的語言。Scala編譯器會將Scala應用程序編譯成Java字節碼,Java字節碼運行在JVM上。從字節碼的層麵來看,Scala應用程序和Java應用程序沒有區別。
因為Scala是基於JVM的,所以它能和Java無縫互操作。在Java應用程序中可以使用Scala開發的庫。更重要的是,Scala應用程序可以使用任何Java庫,而無須任何包裝器或膠水代碼。Scala應用程序可以直接受益於近二十年來人們用Java開發出來的各種現有的庫。
盡管Scala是一門混合了麵向對象編程和函數式編程的語言,但是它還是側重於函數式編程。這一點使它成為一門強大的語言。相比於把Scala當成麵向對象語言使用而言,把Scala當成函數式編程語言來用更能讓你受益匪淺。
本書無法覆蓋Scala的方方麵麵。想要說明白Scala的每個細節需要一本更厚的書才行。本書隻介紹編寫Spark應用程序所需要的基礎知識。假設你有一定的編程經驗,故本書不會介紹編程的基礎知識。
Scala是一門強大的語言。伴隨著強大性而來的是它的複雜性。有些人想要立刻學會Scala的所有語言特性,因而被嚇壞了。然而,你並不需要知道它的每個細節,就能高效地使用它。隻要你學會了本章所涵蓋的基礎知識,你就能開始高效地開發Scala應用程序了。
2.2.1 起步
學習一門語言的最好方式就是使用它。如果你能運行一下附帶的示例代碼,你將更好地理解本章的內容。
可以用任何的文本編輯器編寫Scala代碼,用scalac編譯它,用Scala執行。或者也可以使用由Typesafe提供的基於瀏覽器的IDE。當然,也可以使用基於Eclipse的Scala IDE、Intellij IDEA或NetBeans IDE。可以從www.scala-lang.org/download下載Scala二進製文件、Typesafe Activator和上述各種IDE。
學習Scala最快捷的途徑就是使用Scala解釋器,它提供了一個交互式的shell用於編寫代碼。它是一個REPL(讀取、求值、輸出、循環)工具。當你在Scala shell中輸入一個表達式時,它會對其求值,將結果輸出到控製台上,然後等待你輸入下一個表達式。安裝交互式Scala shell就像下載Scala二進製文件然後解壓它一樣簡單。Scala shell的名字是scala。它位於bin目錄下,隻須在終端輸入Scala就能啟動它。
現在,你應當看到如圖2-1所示的Scala shell提示符。
圖2-1 Scala shell提示符
現在,可以輸入任何Scala表達式。下麵是一個例子。
在你按下回車鍵之後,Scala解釋器就會執行代碼,然後將結果輸出在控製台上。可以用Scala shell執行本章的示例代碼。
讓我們開始學習Scala吧。
2.2.2 基礎類型
和其他的編程語言類似,Scala提供一些基礎類型和作用在它們之上的操作。Scala中的基礎類型清單如表2-1所示。
表2-1 Scala中的基礎變量類型
變 量 類 型 描 述 變 量 類 型 描 述
Byte 8位有符號整數 Double 32位雙精度浮點數
Short 16位有符號整數 Char 16位無符號
Int 32位有符號整數 Unicode 字符
Long 64位有符號整數 String 字符串
Float 32位單精度浮點數 Boolean true或false
需要注意的是,Scala並沒有基本類型。Scala的每一個基礎類型都是一個類。當把一個Scala應用程序編譯成Java字節碼的時候,編譯器會自動把Scala基礎類型轉變成Java基本類型,這樣有助於提高應用程序的性能。
2.2.3 變量
Scala有兩種類型的變量:可變和不可變的。不過,盡量不要使用可變變量。純函數式程序是不會使用可變變量的。然而,有時使用可變變量會使代碼更簡單。因此,Scala也支持可變變量。當然,使用它的時候要小心。
可變變量使用關鍵字var聲明,不可變變量使用關鍵字val聲明。
val與命令式編程語言(如C/C++和Java)中的變量類似。它可以在創建之後重新賦值。創建、修改可變變量的語法如下。
val在初始化之後就不可以重新賦值了。創建val的語法如下。
上麵的代碼在添加了下麵這行語句之後會發生什麼呢?
編譯器會報錯。
需要著重指出的是,Scala編譯器提供了種種便利。首先,以分號作為語句結尾是可選的。其次,有必要的話,編譯器會進行類型推斷。Scala是一門靜態類型的語言,所以一切都是有類型的。然而,在Scala編譯器能自動推導出類型的情況下,開發者不需要為它聲明類型。使用Scala編程會使得代碼更簡短精煉。
下麵兩個語句是等價的。
2.2.4 函數
如前所述,函數是一段可執行的代碼,這段代碼最終返回一個值。它和數學中函數的概念類似,讀取輸入,然後返回一個輸出。
Scala把函數當成一等公民。函數可以當成變量使用。它可以作為輸入傳遞給其他函數。它也可以定義成匿名函數字麵量,就像字符串字麵量一樣。它可以作為變量的值。它也可以在其他函數的函數體內定義。它還可以作為其他函數的返回值返回。
Scala中用關鍵字def來定義函數。函數定義以函數名開頭,緊跟著是以逗號作為分隔符的輸入參數列表,每個參數後麵跟著它們各自的類型,參數列表放在一對圓括號中。在右圓括號後麵是一個冒號、函數返回值類型、等號和函數體。函數體可以被大括號包裹,沒有亦可。下麵是一個例子。
在上麵的例子中,函數名是add。它有兩個都是Int類型的參數,返回一個Int類型的返回值。這個函數隻是將兩個參數相加,而後將累加和作為返回值返回。
這個函數可以簡化成下麵這樣。
這個簡化版的函數和之前的是一樣的。返回值的類型省略了,因為編譯器可以根據代碼推導出來。然而,還是不推薦省略返回值類型。
簡化版中大括號同樣也省略了。隻有當函數體中有不止一條語句時,才需要大括號。
關鍵字return也省略了,因為它是可選的。在Scala中,所有語句都是表達式,表達式總是會返回一個值。因此,簡化版函數的返回值就是函數體中最後一個語句作為表達式所返回的值。
上麵的代碼片段隻是一個例子,說明了使用Scala可以寫出簡潔的代碼,提高代碼的可讀性和可維護性。
Scala支持多種類型的函數,這一點在下麵進行介紹。
方法
方法是指類的成員函數。它的定義和使用方法和函數類似,唯一的區別是它可以訪問類裏麵所有的成員。
局部函數
在其他函數中或在方法中定義的函數稱為局部函數。它僅可使用輸入參數和包含它的函數內的變量。它隻在包含其定義的函數內可見。這一實用的特性使得你可以在函數內聚合多條語句,而不會汙染整個應用程序的命名空間。
高階方法
把函數作為輸入參數的方法被稱為高階方法。類似的,高階函數指的是把函數作為參數的函數。高階方法和高階函數有助於減少重複代碼,從而使得代碼更簡潔。
下麵是一個高階函數的例子。
encode函數接受兩個參數,返回一個Long類型的值。第一個參數是Int類型的。第二個參數是一個函數f,它接受一個Int類型的參數,返回一個Long類型的值。encode函數首先將第一個參數乘以10,然後將結果作為參數調用函數f。
在介紹Scala集合的時候我們會看到更多的高階函數。
函數字麵量
函數字麵量是指源代碼中的匿名函數。在應用程序中,可以像字符串字麵量一樣使用它。它可以作為高階方法或高階函數的參數,也可以賦值給變量。
函數字麵量的定義由處於圓括號中的輸入函數列表、右箭頭和函數體構成。包裹函數體的大括號是可選的。下麵是一個函數字麵量的例子。
如果函數體隻由一條語句構成,那麼大括號是可以省略的。上麵定義的函數字麵量的簡化版如下所示。
之前定義的高階函數encode可以被當成函數字麵量使用,如下所示。
閉包
在函數對象的函數體中,隻能使用參數和函數字麵量中定義的局部變量。然而,Scala中函數字麵量卻可以使用其所處作用域中的變量。閉包就是這種可以使用了非參數非局部變量的函數字麵量。有時候人們把閉包和函數字麵量當成同一術語,但是從技術上說,它們是不一樣的。
下麵是一個閉包的例子。
在上麵的代碼中,局部函數encode的第二個參數是個函數。這個函數字麵量使用了兩個變量n和seed。n是函數的參數,而seed卻不是。在這個作為函數encode參數的函數字麵量中,seed是從其所處的作用域獲得的,並用在函數體中。
2.2.5 類
類是麵向對象編程中的概念。它是一種高層的編程抽象。簡單地說,它是一種將數據和操作結合在一起的代碼組織方式。在概念上,它用屬性和行為來表示一個實體。
Scala中的類和其他麵向對象編程語言中的類似。它由字段和方法構成。字段就是一個變量,用於存儲數據。方法就是一段可執行的代碼,是在類中定義的函數。方法可以訪問類中的所有字段。
類就是一個在運行期間創建對象的模板。對象就是一個類實例。類在源代碼中定義,而對象隻存在於運行期間。Scala使用關鍵字class來定義一個類。類的定義以類名開頭,緊跟著參數列表,參數列表以逗號作為分隔符,然後是處於大括號中的字段和方法。
下麵是一個例子。
類實例使用關鍵字new創建。
類通常用作可變數據結構。對象都有一個隨時變化的狀態。因此,類中的字段一般都是可變變量。
因為Scala運行在JVM之上,所以你不必顯式刪除對象。Java的垃圾回收器會自動刪除那些不再使用的對象。
2.2.6 單例
在麵向對象編程中一個常見的設計模式就是單例,它是指那些隻可以實例化一次的類。Scala使用關鍵字object來定義單例對象。
2.2.7 樣本類
樣本類是指使用case修飾符的類,下麵是一個例子。
對於樣本類,Scala提供了一些語法上的便利。首先,樣本類會添加與類名一致的工廠方法。因此,不必使用new關鍵字就可以創建一個樣本類的類實例。舉例來說,下麵的這段代碼就是合法的。
其次,樣本類參數列表中的所有參數隱式獲得val前綴。換句話說,Scala把上麵定義的樣本類Message當成如下定義。
val前綴把類參數轉變成了不可變的類字段。故可以從外部訪問它們。
最後,Scala為樣本類添加了方法toString、hashCode、equals、copy。這些方法使得樣本類便於使用。
在創建不可變對象時樣本類相當有用,而且樣本類還支持模式匹配。模式匹配將在下麵進行介紹。
2.2.8 模式匹配
模式匹配是Scala中的概念,它看上去類似於其他語言的switch語句。然而,它卻是一個比switch語句要強大得多的工具。它就像瑞士軍刀一樣能解決各種各樣的問題。
模式匹配的一個簡單用法就是替代多層的if-else語句。如果代碼中有多於兩個分支的if-else語句,它就難以閱讀了。在這種場景下,使用模式匹配能提高代碼的可讀性。
作為一個例子,考慮這樣一個簡單的函數,它以表示顏色的字符串作為參數,如果是紅色返回1,如果是藍色返回2,如果是綠色返回3,如果是黃色返回4,如果是其他顏色返回0。
Scala使用關鍵字match來替代關鍵字switch。每一個可能的選項前麵都跟著關鍵字case。如果有一個選項匹配,那麼該選項右箭頭右邊的代碼將會執行。下劃線表示默認選項。如果任何選項都不匹配,那麼默認選項對應的代碼就會執行。
上麵的例子雖然簡單,但是它說明模式匹配的幾個特性。首先,一旦有一個選項匹配,那麼該選項對應的代碼就會執行。不同於switch語句,每一個選項對應的代碼中不需要有break語句。匹配選項之後的其他選項對應代碼並不會被執行。
其次,每一個選項對應的代碼都是表達式,表達式返回一個值。因此,模式匹配語句本身就是一個返回一個值的表達式。下麵的代碼說明了這一點。
2.2.9 操作符
Scala為基礎類型提供了豐富的操作符。然而,Scala沒有內置操作符。在Scala中,每一個基礎類型都是一個類,每一個操作符都是一個方法。使用操作符等價於調用方法。考慮下麵的例子。
+並不是Scala的內置操作符。它是定義在Int類中的一個方法。上麵代碼中的最後一條語句等價於如下代碼。
Scala允許以操作符的方式來調用方法。
2.2.10 特質
特質是類繼承關係中的接口。它的這種抽象機製有助於開發者寫出模塊化、可複用、可擴展的代碼。
從概念上說,一個接口可以定義多個方法。Java中的接口隻有函數簽名,沒有實現。繼承這個接口的類必須實現這些接口方法。
Scala的特質類似於Java中的接口。然後,不同於Java中的接口,Scala特質可以有方法的實現。而且它還可以有字段。這樣,繼承類就可以複用這些字段和特質中實現的方法了。
特質看上去像是抽象類,它們都有字段和方法。區別在於一個類隻能繼承一個抽象類,但是可以繼承多個特質。
下麵是一個特質的例子。
2.2.11 元組
元組是一個容器,用於存放兩個或多個不同類型的元素。它是不可變的。它自從創建之後就不能修改了。它的語法簡單,如下所示。
當你想要把一些不相關的元素聚合在一起時,元組就派上用場了。當所有元素都是同一類型時,可以使用集合,比如數組或列表。當元素是不同類型但是之間有聯係時,可以使用類,把它們當成類字段來存儲。但是在某些場景下使用類沒有必要。比如你想要有一個有多個返回值的函數,此時元組比類更合適。
元組的下標從1開始。下麵這個例子展示怎麼訪問元組中的元素。
2.2.12 Option類型
Option是一種數據類型,用來表示值是可選的,即要麼無值要麼有值。它要麼是樣本類Some的實例,要麼是單例對象None的實例。Some類的實例可以存儲任何類型的數據,用來表示有值。None對象表示無值。
Option類型可以在函數或方法中作為值返回。返回Some(x)表示有值,x是真正的返回值。返回None表示無值。從函數返回的Option類型對象可以用於模式匹配中。
下麵的例子說明了這些用法。
使用Option類型有助於避免空指針異常。在很多語言中,null用於表示無值。以C/C++/Java中一個返回整數的函數為例,如果對於給定的參數沒有合法的整數可以返回,函數可能返回null。調用者如果沒有檢查返回值是否為null,而直接使用,就可以能導致程序崩潰。在Scala中,由於有了嚴格類型檢查和Option類型,這樣的錯誤得以避免。
2.2.13 集合
集合是一種容器類的數據結構,可以容納零個或多個元素。它是一種抽象的數據結構。它支持聲明式編程。它有方便使用的接口,使用這些接口就不必手動遍曆所有元素了。
Scala有豐富的集合類,集合類包含各種類型。所有的集合類都有同樣的接口。因此隻要你熟悉了其中的一種集合,對於其他的集合類型你也能熟練使用。
Scala的集合類可以分為三類:序列、集合、map。本節將介紹Scala最常用的集合類。
序列
序列表示有先後次序的元素集合。由於元素是有次序的,因此可以根據位置來訪問集合中的元素。舉例來說,可以訪問序列中第n個元素。
數組
數組是一個有索引的元素序列。數組中的所有元素都是相同類型的。它是可變的數據結構。可以修改數組中的元素,但是你不能在它創建之後增加元素。它是定長的。
Scala數組類似其他語言中的數組。訪問其中的任意一個元素都占用固定的時間。數組的索引從0開始。要訪問元素或修改元素,可以通過索引值達成,索引值位於括號內。下麵是一個例子。
關於數組的基本操作如下:
通過索引值訪問元素
通過索引值修改元素
列表
列表是一個線性的元素序列,其中存放一堆相同類型的元素。它是一種遞歸的結構,而不像數組(扁平的數據結構)。和數組不同,它是不可變的,創建後即不可修改。列表是Scala和其他函數式語言中最常使用的一種數據結構。
盡管可以根據索引來訪問列表中的元素,但這並不高效。訪問時間和元素在列表中的的位置成正比。
下麵的代碼展示了幾種創建列表的方式。
關於列表的基本操作如下:
訪問第一個元素。為此,List類提供了一個叫作head的方法。
訪問第一個元素之後的所有元素。為此,List類提供了一個叫作tail的方法。
判斷列表是否為空。為此,List類提供了一個叫作isEmpty的方法,當列表為空時,它返回true。
向量
向量是一個結合了列表和數組各自特性的類。它擁有數組和列表各自的性能優點。根據索引訪問元素占用固定的時間,線性訪問元素也占用固定的時間。向量支持快速修改和訪問任意位置的元素。
下麵是一個例子。
集合
集合是一個無序的集合,其中的每一個元素都不同。它沒有重複的元素,而且,也沒法通過索引來訪問元素,因為它沒有索引。
下麵是一個例子。
集合支持兩種基本操作。
contains:如果當前集合包含這個元素,則返回true。元素作為參數傳遞進來。
isEmpty:如果當前集合為空,則返回true。
map
map是一個鍵-值對集合。在其他語言中,它也叫作字典、關聯數組或hash map。它是一個高效的數據結構,適合根據鍵找對應的值。千萬不要把它和Hadoop MapReduce中的map混淆了。Hadoop MapReduce中的map指在集合上的一種操作。
下麵這段代碼展示了如何創建並使用map。
Scala還有其他集合類,這裏就不一一介紹了。然而,隻要掌握了以上內容,這些就足以高效地使用Scala了。
集合類上的高階方法
Scala集合的強大之處就在於這些高階方法。這些高階方法把函數當成參數。需要注意的是,這些高階方法並沒有改變集合。
本節將介紹一些常用的高階方法。例子中使用的是List集合,但是所有的Scala集合類都支持這些高階方法。
map
map方法的參數是一個函數,它將這個函數作用於集合中的每一個元素,返回由其返回值所組成的集合。這個返回的集合中的元素個數和調用map的集合元素個數一致。然而,返回集合中的元素類型有可能不一樣。
下麵是一個例子。
在上麵的例子中,需要注意的是,xs的類型是List[Int],而ys的類型是List[Double]。
如果一個函數隻有一個參數,那麼包裹參數列表的圓括號可以用大括號代替。下麵的兩條語句是等價的。
就像之前說的一樣,Scala允許以操作符的方式調用任何方法。為了進一步提高了代碼的可讀性。上麵的代碼可以改寫成如下這樣。
Scala會根據集合中元素類型對函數字麵量中的參數進行類型推導,故可以省略參數類型。下麵的兩條語句是等價的。
如果函數字麵量的參數隻在函數體內使用一次,那麼右箭頭及其左邊部分都可以省略。可以隻寫函數字麵量的主體。下麵的兩條語句是等價的。
下劃線表示集合中的元素,它作為參數傳遞給map中的函數字麵量。上麵的代碼可以解讀為將xs中的每個元素乘以10。
總之,下麵分別是詳細版和簡化版的代碼。
如你所見,使用Scala能很方便地寫出易讀的簡潔代碼。
flatMap
Scala集合的flatMap方法類似於map,它的參數是一個函數,它把這個函數作用於集合中的每一個元素,返回另外一個集合。這個函數作用於原集合中的一個元素之後會返回一個集合。這樣,最後就會得到一個元素都是集合的集合。使用map方法,就是這樣的結果。但是使用flatMap會得到一個扁平化的集合。
下麵是一個使用flatMap的例子。
toList方法將創建一個列表,裏麵包含原有集合的所有元素。這個方法能將字符串、數組、集合或其他集合類型轉變成一個列表。
filter
filter方法將謂詞函數作用於集合中的每個元素,返回另一個集合,其中隻包含計算結果為真的元素。謂詞函數指的是返回一個布爾值的函數。它要麼返回true,要麼返回false。
foreach
foreach方法的參數是一個函數,它把這個函數作用於集合中的每一個元素,但是不返回任何東西。它和map類似,唯一的區別在於map返回一個集合,而foreach不返回任何東西。由於它的無返回值特性它很少使用。
reduce
reduce方法返回一個值。顧名思義,它將一個集合整合成一個值。它的參數是一個函數,這個函數有兩個參數,並返回一個值。從本質上說,這個函數是一個二元操作符,並且滿足結合律和交換律。
下麵是一些例子。
下麵是一個找出句子中最長單詞的例子。
需要注意的是,Hadoop MapReduce中的map/reduce和我們上麵說的map/reduce是相似的。事實上,Haddoop MapReduce借用了函數式編程中的這些概念。
2.3 一個單獨的Scala應用程序
到目前為止,你看到不少Scala代碼片段。在這一節,我們將會編寫一個完整的Scala應用程序,你可以編譯它,運行它。
一個單獨的Scala應用程序需要一個具有main方法的單例對象。這個main方法以一個Array[String]類型的參數作為輸入。它並不返回任何值。它是這個Scala應用程序的入口。這個有main方法的單例可以隨意起名。
下麵展示的是一個輸出Hello World!的Scala應用程序。
你可以把上麵的代碼寫到文件中,編譯並運行它。Scala源代碼文件以.Scala作為後綴名。但這不是必需的。不過建議以代碼中的類名或單例名作為文件名。比如,上麵的代碼文件應該叫作HelloWorld.scala。
2.4 總結
Scala是一門運行在JVM之上的靜態類型語言,它用來開發多線程和分布式的應用程序。它結合了麵向對象編程和函數式編程各自的優點。而且,它可以和Java無縫集成在一起。可以在Scala中使用Java的庫,反之亦然。
使用Scala不僅能讓開發者顯著提高生產力和代碼質量,還可以開發出健壯的多線程和分布式應用程序。
Spark本身是用Scala編寫的。它隻是眾多使用Scala編寫的流行的分布式係統中的一個代表。
最後更新:2017-05-19 16:38:06