原文地址html
本文只是帶你進入 Scala 的世界,包括安裝、不可變量 val、可變量 var、定義類、集合(包括列表(list)、集(set)、映射(map))以及集合遍歷和集合庫(能達到並行/併發效果)。java
題外話,若是 Java 爭氣的話,還就真不會出現像 Scala 這些語言。對於函數式編程風格的支持,尤爲是對於 Lambda 表達式的支持,能減小必需要編寫的邏輯無關的樣板代碼,讓程序員集中精力任務自己。而 Java 對 Lamdba 表達式的支持到 JavaSE8 才實現(你能夠查一下 Java SE8 的發佈時間,和其餘語言在語言層面上什麼時候支持的匿名函數、Lambda 表達式、函數式編程、並行編程……)。程序員
Scala,一門強類型定義的靜態類型語言,結合了面向對象編程與函數式編程思想,語法簡潔,徹底兼容 Java,運行在 JVM 上。JVM 上的其餘語言還有 Groovy、JRuby、Clojure,而 Scala 有什麼不一樣?——能同時提供函數式風格和良好併發支持的強類型語言,只有 Scala。JRuby 和 Groovy 都是動態語言(Scala 是靜態類型語言),它們不是函數式的,也沒法提供比 Java 更好的併發解決方案。另外一方面,Clojure 是一種混合型的函數式語言,它天生就是動態的,所以不是靜態類型。並且它的語法相似 Lisp,除非你很熟悉,不然這可不是一種易於掌握的語言(Lisp 是號稱高智商的人才能使用的語言,若是你看過《黑客與畫家》,應該記得做者的一句話,大意是,若是競爭對手採用 Lisp 開發 Web,那就應該當心了,言下之意是,Lisp 跟其餘語言相比,生產效率過高了,很容易實現一個想法)。算法
總結起來,Scala 特色體如今如下幾方面:編程
- 也運行在 JVM 上,使 Scala 能夠和現存的應用同時運行;
- 能夠直接使用 Java 類庫,使開發人員能夠利用現有的框架和遺留代碼;
- 與 Java 同樣都是靜態類型語言。遵循相同的編程哲學;
- 語法與 Java 比較接近,使得開發人員能夠快速掌握語言基礎(看怎麼寫,我以爲Scala的簡潔程度,會讓初學者崩潰);
- 既支持面向對象範型,也支持函數式編程範型,開發人員能夠逐步運用函數式編程的思想。
Scala 對 Java 的不一樣:api
- 類型推斷。Java 中必須聲明變量、實參或形參的類型。而在 Scala 中則不用,它會推斷出變量的類型;
- 函數式編程。Scala 將函數式編程的重要概念引入 Java,包括代碼塊、高階函數(high-order function)以及複雜的集合庫;
- 不變量。Java 也容許使用不變量,不過是經過一個不多使用的修飾符實現的。另外,Java 的 String 也是不變量的一個實現,而 Scala 會讓你決定一個變量是否可變,好比 val,仍是 var。這將對程序在併發環境中的行爲,產生巨大影響;
- 高級程序構造。Scala 很好地使用了基礎語言,並將有用的概念分層。包括併發 Actor 模型、使用高階函數 Ruby 風格的集合以及做爲一等對象類型(first-class)的 XML 處理。
文中代碼本人在 Scala 2.11 上編譯並運行經過。安全
第一步,先安裝好 Scala Typesafe stack,打開命令行窗口,鍵入「scala」,將啓動 REPL(讀入-運算 輸出 循環)交互式編碼環境。而後就能夠寫下你的第一行 Scala 代碼:閉包
scala> val columbus: Int = 1492
columbus: Int = 1492
scala>
聲明瞭一個類型爲 Int 變量,初始值爲 1492,就像在Java裏 Int columbus = 1492; 同樣。併發
Scala 把類型放在變量以後(反向聲明方式),使用 val 顯式地把變量聲明爲不可變的。若是想修改這個變量,將報錯:app
scala> columbus=1500
<console>:8: error: reassignment to val
columbus=1500
^
scala>
錯誤消息精確地指出了錯誤位於行的位置。
再嘗試聲明這個變量,此次用 var,讓其可變。編譯器能推斷出 1492 是一個整數,因此,無需指定類型:
scala> var columbus = 1492
columbus: Int = 1492
scala> columbus = 1500
columbus: Int = 1500
scala>
接下來,定義一個類,名爲 Employee,有三個不可變的字段:name、age 和 company,擁有各自的缺省值。
scala> case class Employee(name:String="guest",
| age:Int=30,
| company:String="DevCode")
defined class Employee
scala>
不須要像 Java 中那樣還要定義字段以及 setter 和 getter 方法。這些對 Scala 來講,是樣板代碼,徹底是多餘的。
case 關鍵字至關於 Java 的 switch 語句,不過更靈活。它說明該類具備模式匹配的額外機制,以及其餘一些特性,包括用來建立實例的工廠方法(不須要使用 new 關鍵字來構造),一樣也不須要建立缺省的 getter 方法。
與 Java 不一樣,默認訪問控制是 public(而不是 protected),Scala 爲 public 變量自動建立一個 getter 方法。固然也能夠把字段定義成可變且/或私有(private)的,只要在前面使用 var(例如,case class Person(private var name:String)),這樣你就不能經過 guest.name 來訪問 name 字段。
接下來,用不一樣方式建立一些實例,看看其餘的特性,如命名參數和缺省參數(從Scala2.8開始引入):
scala> val guest=Employee()
guest: Employee = Employee(guest,30,DevCode)
scala> val guestAge=guest.age
guestAge: Int = 30
scala> val anna=Employee("Anna")
anna: Employee = Employee(Anna,30,DevCode)
scala> val thomas=Employee("Thomas",41)
thomas: Employee = Employee(Thomas,41,DevCode)
scala> val luke=Employee("Luke",company="LucasArt")
luke: Employee = Employee(Luke,30,LucasArt)
scala> val yoda=luke.copy("Yoda",age=800)
yoda: Employee = Employee(Yoda,800,LucasArt)
scala>
對於你定義的類,Scala 自動也提供了用來克隆類實例的 copy 方法。克隆方法,也是樣板代碼。今後,你就不用本身寫了。
不過,下面的寫法是不行的(可不是由於 Darth 不是 DevCode 的僱員!)。
scala> val darth=Employee("Darth","DevCode")
<console>:9: error: type mismatch;
found : String("DevCode")
required: Int
val darth=Employee("Darth","DevCode")
^
scala>
由於構造函數在這個位置須要的 age 做爲參數,而你又沒有顯性地進行命名,company=」DevCode」。
如今咱們再來看集合,這纔是真正讓人興奮的地方。Scala 集合類型包括列表(list)、集(set)和映射(map)。
有了泛型(Java5 以上),Java 遍歷一個列表,好比整數型列表,代碼以下所示:
List<Integer> numbers = new arrayList<Integer>();
numbers.add(1);
numbers.add(2);
numbers.add(3);
for(Integer n:numbers) {
System.out.println("Number "+n);
}
運行結果:
Number 1
Number 2
Number 3
Scala 對於可變集合和不可變集合進行了系統性地區別處理。但鼓勵使用不可變集合,所以在缺省狀況下建立的是不可變集合,經過模擬的方式實現添加、更新和刪除。注意,這些操做不是修改集合,而是返回新的集合。
與前面 Java 代碼等價的 Scala 代碼可能像下面這樣:
scala> val numbers=List(1,2,3)
numbers: List[Int] = List(1, 2, 3)
scala> for(n<-numbers) println("Number "+n)
Number 1
Number 2
Number 3
scala>
這裏的 for 循環語法結構很是接近於 Java 的命令式編程風格。在 Scala(以及 Java 虛擬機上其餘不少語言如:Groovy、JRuby 或 JPython)裏還有另一種方式來實現上面的邏輯。這種方式使用一種更加偏向函數編程的風格,引入了 Lambda 表達式(有時也稱爲閉包——closure)。簡單地說,Lambda 表達式就是你能夠拿來看成參數傳遞的函數。這些函數使用參數做爲輸入(在這個例子中就是 n 整型變量),返回語句做爲函數體的最終語句。他們的形式以下:
functionName { input =>
body
}
scala> numbers.foreach {n:Int=> println("Number "+n) }
Number 1
Number 2
Number 3
scala>
上面的例子中,函數體只有一條語句(println……),返回「空結果」Unit,大體至關於 Java 中的 void。
除了打印列表外,咱們更想對列表中元素作些處理和變換。讓咱們嘗試一些例子:
scala> val reversedList=numbers.reverse
reversedList: List[Int] = List(3, 2, 1)
scala> val numbersLessThan3=numbers.filter {n=>n<3}
numbersLessThan3: List[Int] = List(1, 2)
scala> val oddNumbers=numbers.filterNot {n=>n%2==0}
oddNumbers: List[Int] = List(1, 3)
scala> val highterNumbers=numbers.map {n=>n+10}
highterNumbers: List[Int] = List(11, 12, 13)
scala>
看到了吧,做用在集合上函數,你能夠不寫圓括號,少了不少樣板代碼。
變換 map 很是有用,它對列表的每一個元素應用閉包,返回一個新的列表。
咱們在這裏還想介紹最後的一個方法,就是 foldLeft,它把狀態從一個元素傳播到另外一個元素。好比,要算出一個列表裏全部元素的和,你須要累加,並在切換元素的時候保存中間的計數:
scala> val sumOfNumbers=numbers.foldLeft(0) {( total,element)=>
| total+element
| }
sumOfNumbers: Int = 6
scala>
做爲第一個變量傳遞給 foldLeft 的值0,是初始值(即在把函數用到第一個列表元素的時候 total=0)。而 (total,element) 表明了一個 Tuple2 二元組。
其實,Scala 已經提供了一個累加的方法,所以,上面能夠寫成:
scala> val sumOfNumbers=numbers.sum
sumOfNumbers: Int = 6
scala>
其餘集合方法,能夠參見 scaladoc API。這些方法組合起來(例如:numbers.reverse.filter……)使用會讓代碼更加簡潔,不過這樣會影響可讀性。
最後,{ n => n + 10 } 能夠簡單地寫成 (_ + 10),也就是說,n 是個匿名變量,叫什麼都行,還不用聲明。那麼,下劃線的地方表示須要用你列表中的每一個元素來填補的空白。(與「_」的功能相似,Groovy 保留了關鍵字 it,Python 則使用的是 self)。
其實,用 (_ + 10) 來代替 { n => n + 10 },就是將 n=>n 換成 _,由於,n 不須要聲明的內部匿名變量,有重複的嫌疑。這更說明 Scala 是何等簡潔。
scala> val hightNumbers=numbers.map (_+10)
hightNumbers: List[Int] = List(11, 12, 13)
scala>
介紹了對整數集合的基本處理後,能夠進入下一階段,如何對複雜集合的變換,例如,使用上面定義的 Employee 類:
scala> val allEmployees=List(luke, anna, guest, yoda, thomas)
allEmployees: List[Employee] = List(Employee(Luke,30,LucasArt), Employee(Anna,30
,DevCode), Employee(guest,30,DevCode), Employee(Yoda,800,LucasArt), Employee(Tho
mas,41,DevCode))
scala>
對這個員工集合列表,能夠應用匿名方法,用一個條件來過濾,查找 compay 爲 DevCode 的員工:
scala> val devCodeEmployees=allEmployees.filter {_.company=="DevCode"}
devCodeEmployees: List[Employee] = List(Employee(Anna,30,DevCode), Employee(gues
t,30,DevCode), Employee(Thomas,41,DevCode))
scala> val oldEmployees=allEmployees.filter(_.age>100).map(_.name)
oldEmployees: List[String] = List(Yoda)
scala>
假設 allEmployees 集合是使用 SQL 查詢得到的,相似 SELECT * FROM employees WHERE company = ‘DevCode’ 。如今用 groupBy 函數按 company 分組,就會獲得一個以 company 爲鍵、Employee 爲值的 Map:
scala> val sortedEmployees=allEmployees.groupBy(_.company)
sortedEmployees: scala.collection.immutable.Map[String,List[Employee]] = Map(Dev
Code -> List(Employee(Anna,30,DevCode), Employee(guest,30,DevCode), Employee(Tho
mas,41,DevCode)), LucasArt -> List(Employee(Luke,30,LucasArt), Employee(Yoda,800
,LucasArt)))
scala>
假設計算某個公司 company 的僱員平均年齡。即 age 字段的累加,而後除以僱員數量。先計算一下 DevCode 公司:
scala> devCodeEmployees
res3: List[Employee] = List(Employee(Anna,30,DevCode), Employee(guest,30,DevCod
), Employee(Thomas,41,DevCode))
scala> val devCodeAges=devCodeEmployees.map(_.age)
devCodeAges: List[Int] = List(30, 30, 41)
scala> val devCodeAverageAge=devCodeAges.sum / devCodeAges.size
devCodeAverageAge: Int = 33
scala>
回到上面的公司分組,Map (key:String ->value:List[Employee]),若是想計算分組內員工的平均年齡,只有幾行代碼:
scala> val averageAgeByCompany = sortedEmployees.map{ case(key,value)=>
| value(0).copy(name="average",age=(value.map(_.age).sum)/value.size)}
averageAgeByCompany: scala.collection.immutable.Iterable[Employee] = List(Employ
ee(average,33,DevCode), Employee(average,415,LucasArt))
scala>
這裏的「case(key,value)」說明了Scala提供的模式匹配機制是多麼強大。請參考Scala的文檔來獲取更多的信息。
到這裏咱們的任務就完成了。咱們實現的是一個簡單的Map-Reduce算法。因爲每一個公司僱員的歸併是徹底獨立於其餘公司,這個算法很是直觀地實現了並行計算。
在後面的附錄裏給出了此算法的等價的實現,分爲Java版本和Scala版本。
public class Employee {
final String name;
final Integer age;
final String company;
public Employee(String name, Integer age, String company) {
this.name = name == null ? "guest" : name;
this.age = age == null ? 30 : age;
this.company = company == null ? "DevCode" : company;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public String getCompany() {
return company;
}
@Override
public String toString() {
return "Employee [name=" + name + ", age=" + age + ",
company="
+ company + "]";
}
}
class Builder {
String name, company;
Integer age;
Builder(String name) {
this.name = name;
}
Employee build() {
return new Employee(name, age, company);
}
Builder age(Integer age) {
this.age = age;
return this;
}
Builder company(String company) {
this.company = company;
return this;
}
}
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multimaps;
public class MapReduce {
public static final void main(String[] args) {
Employee guest = new Builder("Guest").build();
Employee anna = new Builder("Anna").build();
Employee thomas = new Builder("Thomas").age(41).build();
Employee luke = new
Builder("Luke").company("LucasArt").build();
Employee yoda = new
Builder("Yoda").age(800).company("LucasArt").build();
Collection<Employee> employees = new ArrayList<Employee>();
employees.add(guest);
employees.add(anna);
employees.add(thomas);
employees.add(luke);
employees.add(yoda);
ImmutableListMultimap<String, Employee>
personsGroupByCompany = Multimaps.index(employees, new Function<Employee,String>() {
public String apply(Employee person) {
return person.getCompany();
}
});
ImmutableSet<String> companyNamesFromMap =
personsGroupByCompany.keySet();
List<Employee> averageAgeByCompany = new
ArrayList<Employee>();
for(String company: companyNamesFromMap) {
List<Employee> employeesForThisCompany =
personsGroupByCompany.get(company);
int sum = 0;
for(Employee employee: employeesForThisCompany) {
sum+= employee.getAge();
}
averageAgeByCompany.add(new
Employee("average",sum/employeesForThisCompany.size(),company));
}
System.out.println("Result: "+averageAgeByCompany);
}
}
case class Employee(name: String = "guest", age: Int = 30, company: String = "DevCode")
object MapReduce {
def main(args: Array[String]): Unit = {
val guest = Employee()
val anna = Employee("Anna")
val thomas = Employee("Thomas", 41)
val luke = Employee("Luke", company = "LucasArt")
val yoda = luke.copy("Yoda", age = 800)
val allEmployees = List(luke, anna, guest, yoda, thomas)
val sortedEmployees = allEmployees.groupBy(_.company)
val averageAgeByCompany = sortedEmployees.map { case (key, value) =>
value(0).copy(name = "average", age = (value.map(_.age).sum) / value.size)
}
println("Result: "+averageAgeByCompany)
}
}
Thomas Alexandre是DevCode的高級諮詢顧問,專一於Java和Scala軟件開發。他熱愛技術,熱衷於分享知識,永遠在尋求方法、採用新的開源軟件和標準來實現更加有效的編程。在十四年的Java開發經驗以外,過去幾年他集中精力在新的編程語言和Web框架上,例如Groovy/Grails和Scala/Lift。Thomas從法國里爾大學得到了計算機科學博士學位,在卡耐基梅隆大學度過了兩年的博士後研究生涯,研究方向是安全和電子商務。