寫了一份更全面,帶習題的文檔在 https://fordeal-smalldata.git...
時間充裕的朋友能夠試試
語句(statement): 一段可執行的代碼
表達式(expression): 一段能夠被求值的代碼
在Java中語句和表達式是有區分的,表達式必須在return或者等號右側,而在scala中,一切都是表達式.php
一個例子:
假設咱們在公司的內網和外網要從不一樣的域名訪問同樣的機器html
//Java代碼 String urlString = null; String hostName = InetAddress.getLocalHost().getHostName(); if (isInnerHost(hostName)) { urlString = "http://inner.host"; } else { urlString = "http://outter.host"; }
剛轉到scala的人極可能這麼寫java
var urlString: String = null var hostName = InetAddress.getLocalHost.getHostName if (isInnerHost(hostName)) { urlString = "http://inner.host" } else { urlString = "http://outter.host" }
咱們讓它更像scala一點吧mysql
val hostName = InetAddress.getLocalHost.getHostName val urlString = if (isInnerHost(hostName)) { "http://inner.host" } else { "http://outter.host" }
這樣作的好處都有啥?
不少時候,咱們編程時說的安全並非指怕被黑客破壞掉,而是預防本身由於逗比而讓程序崩了.react
純函數(Pure Function)是這樣一種函數——輸入輸出數據流全是顯式(Explicit)的。
顯式(Explicit)的意思是,函數與外界交換數據只有一個惟一渠道——參數和返回值;函數從函數外部接受的全部輸入信息都經過參數傳遞到該函數內部;函數輸出到函數外部的全部信息都經過返回值傳遞到該函數外部。git
若是一個函數經過隱式(Implicit)方式,從外界獲取數據,或者向外部輸出數據,那麼,該函數就不是純函數,叫做非純函數(Impure Function)。
隱式(Implicit)的意思是,函數經過參數和返回值之外的渠道,和外界進行數據交換。好比,讀取全局變量,修改全局變量,都叫做以隱式的方式和外界進行數據交換;好比,利用I/O API(輸入輸出系統函數庫)讀取配置文件,或者輸出到文件,打印到屏幕,都叫作隱式的方式和外界進行數據交換。程序員
//一些例子 //純函數 def add(a:Int,b:Int) = a + b //非純函數 var a = 1 def addA(b:Int) = a + b def add(a:Int,b:Int) = { println(s"a:$a b:$b") a + b } def randInt() = Random.nextInt()
單元測試什麼的,趕忙去 http://www.scalatest.org 試試吧es6
維基百科中惰性求值的解釋
惰性求值(Lazy Evaluation),又稱惰性計算、懶惰求值,是一個計算機編程中的一個概念,它的目的是要最小化計算機要作的工做。它有兩個相關而又有區別的含意,能夠表示爲「延遲求值」和「最小化求值」,本條目專一前者,後者請參見最小化計算條目。除能夠獲得性能的提高外,惰性計算的最重要的好處是它能夠構造一個無限的數據類型。
惰性求值的相反是及早求值,這是一個大多數編程語言所擁有的普通計算方式。
import scala.io.Source.fromFile val iter: Iterator[String] = fromFile("sampleFile") .getLines()
文件迭代器就用到了惰性求值.
用戶能夠徹底像操做內存中的數據同樣操做文件,然而文件只有一小部分傳入了內存中.github
lazy val firstLazy = { println("first lazy") 1 } lazy val secondLazy = { println("second lazy") 2 } def add(a:Int,b:Int) = { a+b }
//在 scala repl 中的結果 scala> add(secondLazy,firstLazy) second lazy first lazy res0: Int = 3 res0: Int = 3
second lazy 先於 first lazy輸出了sql
def firstLazy = { println("first lazy") 1 } def secondLazy = { println("second lazy") 2 } def chooseOne(first: Boolean, a: Int, b: Int) = { if (first) a else b } def chooseOneLazy(first: Boolean, a: => Int, b: => Int) = { if (first) a else b }
chooseOne(first = true, secondLazy, firstLazy) //second lazy //first lazy //res0: Int = 2 chooseOneLazy(first = true, secondLazy, firstLazy) //second lazy //res1: Int = 2
對於非純函數,惰性求值會產生和當即求值產生不同的結果.
//須要查詢mysql等,可能來自於一個第三方jar包 def itemIdToShopId: Int => Int var cache = Map.empty[Int, Int] def cachedItemIdToShopId(itemId: Int):Int = { cache.get(itemId) match { case Some(shopId) => shopId case None => val shopId = itemIdToShopId(itemId) cache += itemId -> shopId shopId } }
//用你的本地mock來測試程序 def mockItemIdToSHopId: Int => Int def cachedItemIdToShopId(itemId: Int): Int ={ cache.get(itemId) match { case Some(shopId) => shopId case None => val shopId = mockItemIdToSHopId(itemId) cache += itemId -> shopId shopId } }
//將遠程請求的結果做爲函數的一個參數 def cachedItemIdToShopId(itemId: Int, remoteShopId: Int): Int = { cache.get(itemId) match { case Some(shopId) => shopId case None => val shopId = remoteShopId cache += itemId -> shopId shopId } } //調用這個函數 cachedItemIdToShopId(itemId,itemIdToShopId(itemId))
沒錯,cache根本沒有起應有的做用,函數每次執行的時候都調用了itemIdToShopId從遠程取數據
//改爲call by name就沒有這個問題啦 def cachedItemIdToShopId(itemId: Int, remoteShopId: =>Int): Int = { cache.get(itemId) match { case Some(shopId) => shopId case None => val shopId = remoteShopId cache += itemId -> shopId shopId } } //調用這個函數 cachedItemIdToShopId(itemId,itemIdToShopId(itemId))
val (one, two) = (1, 2) one //res0: Int = 1 two //res1: Int = 2 def sellerAndItemId(orderId: Int): (Int, Int) = orderId match { case 0 => (1, 2) } val (sellerId, itemId) = sellerAndItemId(0) sellerId // sellerId: Int = 1 itemId // itemId: Int = 2 val sellerItem = sellerAndItemId(0) sellerItem._1 //res4: Int = 1 sellerItem._2 //res5: Int = 2
val sampleList = List((1, 2, 3), (4, 5, 6), (7, 8, 9)) sampleList.map(x => s"${x._1}_${x._2}_${x._3}") //res0: List[String] = List(1_2_3, 4_5_6, 7_8_9) sampleList.map { case (orderId, shopId, itemId) => s"${orderId}_${shopId}_$itemId" } //res1: List[String] = List(1_2_3, 4_5_6, 7_8_9)
上下兩個map作了一樣的事情,但下一個map爲tuple中的三個值都給了名字,增長了代碼的可讀性.
//case若是是常量,就在值相等時匹配. //若是是變量,就匹配任何值. def describe(x: Any) = x match { case 5 => "five" case true => "truth" case "hello" => "hi!" case Nil => "the empty list" case somethingElse => "something else " + somethingElse }
case class,tuple以及列表均可以在匹配的同時捕獲內部的內容.
case class Sample(a:String,b:String,c:String,d:String,e:String) def showContent(x: Any) = x match { case Sample(a,b,c,d,e) => s"Sample $a.$b.$c.$d.$e" case (a,b,c,d,e) => s"tuple $a,$b,$c,$d,$e" case head::second::rest => s"list head:$head second:$second rest:$rest" }
Map<String, String> map = ??? String valFor2014 = map.get(「1024」); // null if (valFor1024 == null) abadon(); else doSomething();
Some[A](x: A)
,size爲0的時候就是一個None
def get(key: A): Option[B] def getOrElse[B1 >: B](key: A, default: => B1): B1 = get(key) match { case Some(v) => v case None => default }
getOrElse
這個方法並放在一個叫作MapUtils的類裏.(寫完這段忽然發現java8開始包含getOrDefault了)
確實可以區分Map是無值仍是值爲null了.
可是if(爲null) 則 abandon 要寫一百遍.case Some(v) => v
case None => default
彷佛也得寫一百遍.
不,不是這樣的
不要忘了option是個容器
http://www.scala-lang.org/api...
val a: Option[String] = Some("1024") val b: Option[String] = None a.map(_.toInt) //res0: Option[Int] = Some(1024) b.map(_.toInt) //res1: Option[Int] = None,不會甩exception a.filter(_ == "2048") //res2: Option[String] = None b.filter(_ == "2048") //res3: Option[String] = None a.getOrElse("2048") //res4: String = 1024 b.getOrElse("2048") //res5: String = 2048 a.map(_.toInt) .map(_ + 1) .map(_ / 5) .map(_ / 2 == 0) //res6: Option[Boolean] = Some(false) //若是是null,恐怕要一連check abandon四遍了
val a: Seq[String] = Seq("1", "2", "3", null, "4") val b: Seq[Option[String]] = Seq(Some("1"), Some("2"), Some("3"), None, Some("4")) a.filter(_ != null).map(_.toInt) //res0: Seq[Int] = List(1, 2, 3, 4) //若是你忘了檢查,編譯器是看不出來的,只能在跑崩的時候拋異常 b.flatMap(_.map(_.toInt)) //res1: Seq[Int] = List(1, 2, 3, 4)
scala原生容器類都對option有良好支持
Seq(1,2,3).headOption //res0: Option[Int] = Some(1) Seq(1,2,3).find(_ == 5) //res1: Option[Int] = None Seq(1,2,3).lastOption //res2: Option[Int] = Some(3) Vector(1,2,3).reduceLeft(_ + _) //res3: Int = 6 Vector(1,2,3).reduceLeftOption(_ + _) //res4: Option[Int] = Some(6) //在vector爲空的時候也能用 Seq("a", "b", "c", null, "d").map(Option(_)) //res0: Seq[Option[String]] = // List(Some(a), Some(b), Some(c), None, Some(d)) //原始數據轉換成option也很方便
try { 1024 / 0 } catch { case e: Throwable => e.printStackTrace() }
用try-catch的模式,異常必須在拋出的時候立刻處理.
然而在分佈式計算中,咱們極可能但願將異常集中到一塊兒處理,來避免須要到每臺機器上單獨看錯誤日誌的窘態.
val seq = Seq(0, 1, 2, 3, 4) //seq: Seq[Int] = List(0, 1, 2, 3, 4) val seqTry = seq.map(x => Try { 20 / x }) //seqTry: Seq[scala.util.Try[Int]] = List(Failure(java.lang.ArithmeticException: devide by zero),Success(20), Success(10), Success(6), Success(5)) val succSeq = seqTry.flatMap(_.toOption) //succSeq: Seq[Int] = List(20, 10, 6, 5) Try能夠轉換成Option val succSeq2 = seqTry.collect { case Success(x) => x } //succSeq2: Seq[Int] = List(20, 10, 6, 5) 和上一個是同樣的 val failSeq: Seq[Throwable] = seqTry.collect { case Failure(e) => e } //failSeq: Seq[Throwable] = List(java.lang.ArithmeticException: devide by zero)
Try實例能夠序列化,而且在機器間傳送.
一個java版本
List<String> params = new LinkedList<>(); List<Integer> nums = new LinkedList<>(); List<String> marks = new LinkedList<>(); public JavaRangeMatcher(List<String> params) { this.params = params; for (String param : params) { String[] markNum = param.split(" "); marks.add(markNum[0]); nums.add(Integer.parseInt(markNum[1])); } } public boolean check(int input) { for (int i = 0; i < marks.size(); i++) { int num = nums.get(i); String mark = marks.get(i); if (mark.equals(">") && input <= num) return false; if (mark.equals(">=") && input < num) return false; if (mark.equals("<") && input >= num) return false; if (mark.equals("<=") && input > num) return false; } return true; } List<String> paramsList = new LinkedList<String>() {{ add(「>= 3」); add(「< 7」); }}; JavaRangeMatcher matcher = new JavaRangeMatcher(paramsList); int[] inputs = new int[]{1, 3, 5, 7, 9}; for (int input : inputs) { System.out.println(matcher.check(input)); } //給本身有限的時間,想一想又沒有性能優化的餘地 //咱們一塊兒來跑跑看
一個 scala 版本
def exprToInt(expr: String): Int => Boolean = { val Array(mark, num, _*) = expr.split(" ") val numInt = num.toInt mark match { case "<" => numInt.> case ">" => numInt.< case ">=" => numInt.<= case "<=" => numInt.>= } //返回函數的函數 } case class RangeMatcher(range: Seq[String]) { val rangeFunc: Seq[(Int) => Boolean] = range.map(exprToInt) def check(input: Int) = rangeFunc.forall(_(input)) } def main(args: Array[String]) { val requirements = Seq(">= 3", "< 7") val rangeMatcher = RangeMatcher(requirements) val results = Seq(1, 3, 5, 7, 9).map(rangeMatcher.check) println(results.mkString(",")) //false,true,true,false,false }
這裏有一個性能測試網站
我對於網站測試的結果,我總結的狀況就是兩點.
不過也有反例,咱們以前那個程序就是.
//java版本 public static void main(String[] args) { List<String> paramsList = new LinkedList<String>() {{ add(">= 3"); add("< 7"); }}; JavaRangeMatcher matcher = new JavaRangeMatcher(paramsList); Random random = new Random(); long timeBegin = System.currentTimeMillis(); for (int i = 0; i < 100000000; i++) { int input = random.nextInt() % 10; matcher.check(input); } long timeEnd = System.currentTimeMillis(); System.out.println("java 消耗時間: " + (timeEnd - timeBegin) + " 毫秒"); //java 消耗時間: 3263 毫秒 }
//scala版本 def main(args: Array[String]) { val requirements = Seq(">= 3", "< 7") val rangeMatcher = RangeMatcher(requirements) val timeBegin = System.currentTimeMillis() 0 until 100000000 foreach { case _ => rangeMatcher.check(Random.nextInt(10)) } val timeEnd = System.currentTimeMillis() println(s"scala 消耗時間 ${timeEnd - timeBegin} 毫秒") //scala 消耗時間 2617 毫秒 }
想一想這是爲何?