add scala
parent
5542a81030
commit
e579248efa
|
@ -0,0 +1,267 @@
|
|||
## 1.java里的Null Pointer Exception
|
||||
写过一阵子的Java后, 应该会对NullPointerException (NPE)这种东西很熟悉,基本上会碰到这种异常,就是你有一个变量是 null,但你却调用了它的方法,或是取某个的值。
|
||||
|
||||
举例而言,下面的 Java 代码就会抛出NPE异常:
|
||||
|
||||
```
|
||||
例1:
|
||||
String s1 = null;
|
||||
System.out.println("length:" + s1.length());
|
||||
```
|
||||
|
||||
当然,一般来说,我们很少会写出这么明显的错误代码。
|
||||
|
||||
但另一方,在 Java 的使用习惯说,我们常常以「返回 null」这件事,来代表一个函数的返回值是不是有意义。
|
||||
|
||||
```
|
||||
例2:
|
||||
//就是在 Java 里 HashMap 的 get() 方法,如果找不到对应的 key 值,就会反回 null:
|
||||
HashMap<String, String> myMap = new HashMap<String, String>();
|
||||
myMap.put("key1", "value1");
|
||||
String value1 = myMap.get("key1"); // 返回 "value1"
|
||||
String value2 = myMap.get("key2"); // 返回 null
|
||||
|
||||
System.out.println(value1.length()); // 没问题,答案是 6
|
||||
System.out.println(value2.length()); // 抛 NullPointerException
|
||||
```
|
||||
|
||||
在上面的例子中,myMap 里没没有对应的key值,那么get()会传回null。
|
||||
如果你像上面一样没有做检查,那很可能就会抛出 NullPointerException,所以我们要像下面一样,先判断得到的是不是 null 才可以调用算字符串长度的方法。
|
||||
|
||||
```
|
||||
例3:
|
||||
HashMap<String, String> myMap = new HashMap<String, String>();
|
||||
|
||||
myMap.put("key1", "value1");
|
||||
|
||||
String value1 = myMap.get("key1"); // 返回 "value1"
|
||||
String value2 = myMap.get("key2"); // 返回 null
|
||||
|
||||
if (value1 != null) {
|
||||
System.out.println(value1.length()); // 没问题,答案是 6
|
||||
}
|
||||
|
||||
if (value2 != null) {
|
||||
System.out.println(value2.length()); // 没问题,如果 value2 是 null,不会被执行到
|
||||
}
|
||||
```
|
||||
|
||||
那我们要怎么知道一个 Java 里某个函数会不会返回null 呢?
|
||||
|
||||
答案是你只能依靠 JavaDoc 上的说明、去查看那个函式的源码来看,再不然就是靠黑盒测试(如果你手上根本没有源码),又或者直接等他哪天爆掉再来处理。
|
||||
|
||||
## 2.Scala 里的 Option[T] 的概念
|
||||
相较之下,如果你去翻 Scala 的 Map 这个类别,会发现他的回传值类型是个 Option[T],但这个有什么意义呢?
|
||||
|
||||
我们还是直接来看代码吧:
|
||||
|
||||
```
|
||||
例4:
|
||||
// 虽然 Scala 可以不定义变量的类型,不过为了清楚些,我还是
|
||||
// 把他显示的定义上了
|
||||
|
||||
val myMap: Map[String, String] = Map("key1" -> "value")
|
||||
val value1: Option[String] = myMap.get("key1")
|
||||
val value2: Option[String] = myMap.get("key2")
|
||||
|
||||
println(value1) // Some("value1")
|
||||
println(value2) // None
|
||||
```
|
||||
|
||||
在上面的代码中,myMap 一个是一个 Key 的类型是 String,Value 的类型是 String 的 hash map,但不一样的是他的 get() 返回的是一个叫 Option[String] 的类别。
|
||||
|
||||
但在各个Option 类别代表了什么意思呢?答案是他在告诉你:我很可能没办法回传一个有意义的东西给你喔!
|
||||
|
||||
像上面的例子里,由于 myMap 里并没有 key2 这笔数据,get() 自然要想办法告诉你他找不到这笔数据,在 Java 里他只告诉你他会回传一个 String,而在 Scala 里他则是用 Option[String] 来告诉你:「我会想办法回传一个 String,但也可能没有 String 给你」。
|
||||
|
||||
至于这是怎么做到的呢?很简单,Option 有两个子类别,一个是 Some,一个是 None,当他回传 Some 的时候,代表这个函式成功地给了你一个 String,而你可以透过 get() 这个函式拿到那个 String,如果他返回的是 None,则代表没有字符串可以给你。
|
||||
|
||||
当然,在返回 None,也就是没有 String 给你的时候,如果你还硬要调用 get() 来取得 String 的话,Scala 一样是会报告一个 Exception 给你的。
|
||||
|
||||
至于怎么判断是 Some 还是 None 呢?我们可以用 isDefined 这个函式来判别,所以如果要和 Java 版的一样,打印 value 的字符串长度的话,可以这样写:
|
||||
|
||||
```
|
||||
例5:
|
||||
// 虽然 Scala 可以不定义变量的类型,不过为了清楚些,我还是
|
||||
// 把他显示的定义上了
|
||||
|
||||
val myMap: Map[String, String] = Map("key1" -> "value")
|
||||
val value1: Option[String] = myMap.get("key1")
|
||||
val value2: Option[String] = myMap.get("key2")
|
||||
|
||||
if (value1.isDefined) {
|
||||
println("length:" + value1.get.length)
|
||||
}
|
||||
|
||||
if (value2.isDefined) {
|
||||
println("length:" + value2.get.length)
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
还是改用 Pattern Matching 好了
|
||||
我知道你要翻桌了,这和我们直接来判断反回值是不是 null 还不是一样?!如果没检查到一样会出问题啊,而且这还要多做一个 get 的动作,反而更麻烦咧!
|
||||
|
||||
不过就像我之前说过的,Scala 比较像是工具箱,他给你各式的工具,让你自己选择适合的来用。
|
||||
|
||||
所以既然上面那个工具和原本的 Java 版本比起来没有太大的优势,那我们就换下一个 Scala 提供给我们的工具吧!
|
||||
|
||||
Scala 提供了 Pattern Matching,也就是类似 Java 的 switch-case 加强版,所以我们上面的程序也可以改写成像下面这样:
|
||||
|
||||
```
|
||||
例6:
|
||||
// 虽然 Scala 可以不定义变量的类型,不过为了清楚些,我还是
|
||||
// 把他显示的定义上了
|
||||
val myMap: Map[String, String] = Map("key1" -> "value")
|
||||
val value1: Option[String] = myMap.get("key1")
|
||||
val value2: Option[String] = myMap.get("key2")
|
||||
|
||||
value1 match {
|
||||
case Some(content) => println("length:" + content.length)
|
||||
case None => // 啥都不做
|
||||
}
|
||||
|
||||
value2 match {
|
||||
case Some(content) => println("length:" + content.length)
|
||||
case None => // 啥都不做
|
||||
}
|
||||
```
|
||||
|
||||
上面是另一个使用 Option 的方式,你用 Pattern Matching 来检查 value1 和 value2 是不是 Some,如果是的话就把 Some 里面的值抽成一个叫 content 的变量,然后再来看你要做啥。
|
||||
|
||||
在大多数的情况下,比起上面的方法,我会更喜欢这个做法,因为我觉得 Pattern Matching 在视觉上比 if 来得更容易理解整个程序的流程。
|
||||
|
||||
但话说回来,其实这还是在测试返回值是不是 None,所以充其量只能算是 if / else 的整齐版而已
|
||||
|
||||
## 3.Option[T] 是个容器,所以可以用 for 循环
|
||||
之前有稍微提到,在 Scala 里 Option[T] 实际上是一个容器,就像数组或是 List 一样,你可以把他看成是一个可能有零到一个元素的 List。
|
||||
|
||||
当你的 Option 里面有东西的时候,这个 List 的长度是一(也就是 Some),而当你的 Option 里没有东西的时候,他的长度是零(也就是 None)。
|
||||
|
||||
这就造成了一个很有趣的现象--如果我们把他当成一般的 List 来用,并且用一个 for 循环来走访这个 Option 的时候,如果 Option 是 None,那这个 for 循环里的程序代码自然不会执行,
|
||||
于是我们就达到了「不用检查 Option 是否为 None」这件事。
|
||||
|
||||
于是下面的程序代码可以就达成和我们上面用 if 以及 Pattern Matching 的程序代码相同的效果:
|
||||
|
||||
```
|
||||
例7:
|
||||
// 虽然 Scala 可以不定义变量的类型,不过为了清楚些,我还是
|
||||
// 把他显示的定义上了
|
||||
|
||||
val myMap: Map[String, String] = Map("key1" -> "value")
|
||||
val value1: Option[String] = myMap.get("key1")
|
||||
val value2: Option[String] = myMap.get("key2")
|
||||
|
||||
for (content <- value1) {
|
||||
println("length:" + content.length)
|
||||
}
|
||||
|
||||
for (content <- value2) {
|
||||
println("length:" + content.length)
|
||||
}
|
||||
```
|
||||
|
||||
我们可以换个想法解决问题
|
||||
|
||||
话说上面的几个程序,我们都是从「怎么做」的角度来看,一步步的告诉计算机,如果当下的情况符合某些条件,就去做某些事情。
|
||||
|
||||
但之前也说过,Scala 提供了不同的工具来达成相同的功能,这次我们就来换个角度来解决问题--我们不再问「怎么做」,而是问「我们要什么」。
|
||||
|
||||
我们要的结果很简单,就是在取出的 value 有东西的时候,印出「length: XX」这样的字样,而 XX 这个数字是从容器中的字符串算出来的。
|
||||
|
||||
在 Functional Programming 中有一个核心的概念之一是「转换」,所以大部份支持 Functional Programming 的程序语言,都支持一种叫 map()
|
||||
的动作,这个动作是可以帮你把某个容器的内容,套上一些动作之后,变成另一个新的容器。
|
||||
|
||||
举例而言,在 Scala 里面,如果有们有一个 List[String],我们希望把这个 List 里的字符串,全都加上" World" 这个字符串的话,可以像下面这样做:
|
||||
|
||||
```
|
||||
例8:
|
||||
scala> val xs = List("Hello", "Goodbye", "Oh My")
|
||||
xs: List[String] = List(Hello, Goodbye, Oh My)
|
||||
scala> xs.map(_ + " World!")
|
||||
res0: List[String] = List(Hello World!, Goodbye World!, Oh My World!)
|
||||
```
|
||||
|
||||
你可以看到,我们可以用 map() 来替 List 内的每个元素做转换,产生新的东西。
|
||||
|
||||
所以我们现在可以开始思考,在我们要达成的 length: XX 中,是怎么转换的:
|
||||
|
||||
先算出 Option 容器内字符串的长度
|
||||
然后在长度前面加上 "length:" 字样
|
||||
最后把容器走访一次,印出容器内的东西
|
||||
有了上面的想法,我们就可以写出像下面的程序:
|
||||
|
||||
```
|
||||
例9:
|
||||
// 虽然 Scala 可以不定义变量的类型,不过为了清楚些,我还是
|
||||
// 把他显示的定义上了
|
||||
|
||||
val myMap: Map[String, String] = Map("key1" -> "value")
|
||||
val value1: Option[String] = myMap.get("key1")
|
||||
val value2: Option[String] = myMap.get("key2")
|
||||
|
||||
// map 两次,一次算字数,一次加上讯息
|
||||
value1.map(_.length).map("length:" + _).foreach(println _)
|
||||
|
||||
// 把算字数和加讯息全部放在一起
|
||||
value2.map("length:" + _.length).foreach(pritlnt _)
|
||||
```
|
||||
|
||||
透过这样「转换」的方法,我们一样可以达成想要的效果,而且同样不用去做「是否为 None」的判断。
|
||||
|
||||
再稍微强大一点的 for 循环组合
|
||||
上面的都是只有单一一个 Option[T] 操作的场合,不过有的时候你会需要「当两个值都是有意义的时候才去做某些事情」的状况,这个时候 Scala 的 for 循环配上 Option[T] 就非常好用。
|
||||
|
||||
同样直接看程序代码:
|
||||
|
||||
```
|
||||
例10:
|
||||
val option1: Option[String] = Some("AA")
|
||||
val option2: Option[String] = Some("BB");
|
||||
|
||||
for (value1 <- option1; value2 <- option2) {
|
||||
println("Value1:" + value1)
|
||||
println("Value2:" + value2)
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
在上面的程序代码中,只有当 option1 和 option2 都有值的时候,才会印出来。如果其中有任何一个是 None,那 for 循环里的程序代码就不会被执行。
|
||||
|
||||
当然,这样的使用结构不只限于两个 Option 的时候,如果你有更多个 Option 变量,也只要把他们放到 for 循环里去,就可以让 for 循环只有在所有 Option 都有值的时候才能执行。
|
||||
|
||||
但我其实想要默认值耶……
|
||||
有的时候,我们会希望当函数没办法返回正确的结果时,可以有个默认值来做事,而不是什么都不错。
|
||||
|
||||
就算是这样也没问题!
|
||||
|
||||
因为 Option[T] 除了 get() 之外,也提供了另一个叫 getOrElse() 的函式,这个函式正如其名--如果 Option 里有东西就拿出来,不然就给个默认值。
|
||||
|
||||
举例来讲,如果我用 Option[Int] 存两个可有可无的整数,当 Option[Int] 里没东西的时候,我要当做 0 的话,那我可以这样写:
|
||||
|
||||
```
|
||||
例11:
|
||||
val option1: Option[Int] = Some(123)
|
||||
val option2: Option[Int] = None
|
||||
|
||||
val value1 = option1.getOrElse(0) // 这个 value1 = 123
|
||||
val value2 = option2.getOrElse(0) // 这个 value2 = 0
|
||||
```
|
||||
|
||||
所以 Option[T] 万无一失吗?
|
||||
当然不是!由于 Scala 要和 Java 兼容,所以还是让 null 这个东西继续存在,所以你一样可以产生 NullPointerException,而且如果你不注意,对一个空的 Option 做 get,Scala 一样会爆给你看。
|
||||
|
||||
```
|
||||
例12:
|
||||
val option1: Option[Int] = null
|
||||
val option2: Option[Int] = None
|
||||
|
||||
option1.foreach(println _) // 爆掉,因为你的 option1 本来就是 null 啊
|
||||
option2.get() // 爆掉,对一个 None 做 get 是一定会炸的
|
||||
```
|
||||
|
||||
我自己是觉得 Option[T] 比较像是一种保险装置,而且这个保险需要一些时间来学习,也需要在有正确使用方式(例如在大部份的情况下,你都不应该用 Option.get() 这个东西),才会显出他的好处来。
|
||||
|
||||
只是当习惯了之后,就会发现 Option[T] 真的可以替你避掉很多错误,至少当你一看到某个 Scala API 的回传值的型态是 Option[T] 的时候,你会很清楚的知道自己要小心。
|
||||
|
||||
原文链接: https://my.oschina.net/u/200745/blog/69845
|
|
@ -0,0 +1,189 @@
|
|||
scala的各种源码里,有大量的implicit关键字。老见到他晃来晃去又不知道为什么,本博主憋得慌,于是有了这篇小文章。
|
||||
|
||||
## 1.最常见的隐式转换函数
|
||||
我们定义了一个方法test,接受的参数类型是String。当我们输出的参数为"101"的时候,显然是OK的。但是当输入的参数为101这个int时,显然就会有问题:
|
||||
|
||||
```
|
||||
scala> def printMsg(msg:String) = println(msg)
|
||||
printMsg: (msg: String)Unit
|
||||
|
||||
scala> printMsg("101")
|
||||
101
|
||||
|
||||
scala> printMsg(101)
|
||||
<console>:13: error: type mismatch;
|
||||
found : Int(101)
|
||||
required: String
|
||||
printMsg(101)
|
||||
^
|
||||
```
|
||||
|
||||
我们给来个隐式转换函数,将int转成string:
|
||||
|
||||
```
|
||||
scala> implicit def turnIntToString(num:Int) = num.toString
|
||||
warning: there was one feature warning; re-run with -feature for details
|
||||
turnIntToString: (num: Int)String
|
||||
|
||||
scala> printMsg(101)
|
||||
101
|
||||
```
|
||||
|
||||
隐式函数是在一个scop下面,给定一种输入参数类型,自动转换为返回值类型的函数,和函数名,参数名无关。
|
||||
|
||||
## 2.再来个常见的例子
|
||||
讲到隐式转换,大部门的资料里都会提到这个例子:在Predef中使用了方法调用的隐式转换
|
||||
|
||||
```
|
||||
Map(1 -> 11, 2 -> 22)
|
||||
```
|
||||
|
||||
上面这段Map中的参数是个二元元组。注意Int是没有 -> 方法。 但是在Predef中定义了:
|
||||
|
||||
```
|
||||
final class ArrowAssoc[A](val __leftOfArrow: A) extends AnyVal {
|
||||
// `__leftOfArrow` must be a public val to allow inlining. The val
|
||||
// used to be called `x`, but now goes by `__leftOfArrow`, as that
|
||||
// reduces the chances of a user's writing `foo.__leftOfArrow` and
|
||||
// being confused why they get an ambiguous implicit conversion
|
||||
// error. (`foo.x` used to produce this error since both
|
||||
// any2Ensuring and any2ArrowAssoc pimped an `x` onto everything)
|
||||
@deprecated("Use `__leftOfArrow` instead", "2.10.0")
|
||||
def x = __leftOfArrow
|
||||
|
||||
@inline def -> [B](y: B): Tuple2[A, B] = Tuple2(__leftOfArrow, y)
|
||||
def →[B](y: B): Tuple2[A, B] = ->(y)
|
||||
}
|
||||
@inline implicit def any2ArrowAssoc[A](x: A): ArrowAssoc[A] = new ArrowAssoc(x)
|
||||
```
|
||||
|
||||
1->11其实就是1.->(11)的偷懒写法。
|
||||
这里怎么能让整数类型1能有->方法呢?其实就是any2ArrowAssoc起的作用了,将整型的1 implicit转换为 ArrowAssoc(1),然后再调用->方法。
|
||||
|
||||
|
||||
## 3.方法调用的隐式转换
|
||||
先上代码
|
||||
|
||||
```
|
||||
object Test {
|
||||
|
||||
case class Person (name:String,age:Int) {
|
||||
def +(num:Int) = age + num
|
||||
def +(p:Person) = age + p.age
|
||||
}
|
||||
|
||||
def main(args: Array[String]): Unit = {
|
||||
val person = Person("Micheal",18)
|
||||
println(person + 1) //result is 19
|
||||
//println(1 + person) 会报错,因为int的+方法没有重载Person参数
|
||||
|
||||
implicit def intAddPerson(num:Int) = Person("default",num)
|
||||
println(1 + person) //result is 19
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
有了隐式转换方法之后,编译器检查 1 + person 表达式,发现Int的+方法没有有Person参数的重载方法。在放弃之前查看是否有将Int类型的对象转换成以Person为参数的+方法的隐式转换函数,于是找到了,然后就进行了隐式转换。
|
||||
|
||||
|
||||
## 4.隐式参数
|
||||
函数或者方法可以带有一个标记为implicit的参数列表。在这种情况下,编译器会查找缺省值,提供给该函数或方法。
|
||||
|
||||
```
|
||||
object Test {
|
||||
|
||||
case class Delimiters(left:String,right:String)
|
||||
|
||||
def transferStr(input:String)(implicit delims:Delimiters) =
|
||||
delims.left + input + delims.right
|
||||
|
||||
implicit val delimiters = Delimiters("(",")")
|
||||
|
||||
def main(args: Array[String]): Unit = {
|
||||
val res = transferStr("hello world")
|
||||
println(res)
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
输出结果为:
|
||||
|
||||
```
|
||||
(hello world)
|
||||
```
|
||||
|
||||
## 5.利用隐式参数进行隐式转换
|
||||
|
||||
我们提供一个泛型函数来得到相对小的值:
|
||||
|
||||
```
|
||||
def smaller[T](a: T, b: T) = if (a < b) a else b
|
||||
```
|
||||
|
||||
这里由于我们并不知道a和b的类型是否有<操作符,所以编译器不会通过。
|
||||
解决办法是添加一个隐式参数order来指代一个转换函数
|
||||
|
||||
```
|
||||
def smaller[T](a:T,b:T)(implicit order:T => Ordered[T])
|
||||
= if(order(a) < b) a else b
|
||||
```
|
||||
|
||||
由于Ordered[T]特质中有一个接受T作为参数的<方法,所以编译器将在编译时知道T,并且从而判决是否T => Ordered[T]类型的隐式定义存在于作用域中。
|
||||
|
||||
这样,才可以调用smaller(40, 2)或者smaller("AA", "BB")。
|
||||
|
||||
注意,order是一个带有单个参数的函数,被打上了implicit标签,所以它不仅是一个隐式参数,也是一个隐式转换。那么,我们可以在函数体重省略order的显示调用。
|
||||
|
||||
|
||||
```
|
||||
def smaller[T](a: T, b: T)(implicit order: T => Ordered[T])
|
||||
= if (a < b) a else b
|
||||
```
|
||||
|
||||
因为a没有带<的方法,那么会调用order(a)进行转换。
|
||||
|
||||
## 6.隐式类
|
||||
1.隐式类必须有一个带一个参数的主构造函数
|
||||
2.必须定义在另一个class/object/trait里面(不能独立定义)
|
||||
3.隐式类构造器只能带一个不是implicit修饰的参数
|
||||
4.作用域中不能有与隐式类类型相同的成员变量,函数以及object名称
|
||||
|
||||
```
|
||||
class B {
|
||||
def add(x:Int,y:Int) = {
|
||||
x + y
|
||||
}
|
||||
}
|
||||
|
||||
object ImpClass {
|
||||
implicit class mulB(arg:B) {
|
||||
def multiply(x:Int,y:Int) =
|
||||
x * y
|
||||
}
|
||||
}
|
||||
|
||||
object client {
|
||||
def main(args: Array[String]): Unit = {
|
||||
import ImpClass._
|
||||
val b = new B()
|
||||
val res = b.multiply(2,3)
|
||||
println("res is: " + res)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
结果:
|
||||
|
||||
```
|
||||
res is: 6
|
||||
|
||||
```
|
||||
|
||||
## 参考资料:
|
||||
1.http://blog.csdn.net/oopsoom/article/details/24643869
|
||||
2.http://fangjian0423.github.io/2015/12/20/scala-implicit/
|
||||
3.http://blog.jasonding.top/2016/02/21/Scala/【Scala类型系统】隐式转换与隐式参数/
|
||||
4.http://bit1129.iteye.com/blog/2186533
|
||||
5.scala Predef部分源码
|
|
@ -0,0 +1,41 @@
|
|||
## 1.匿名函数初探
|
||||
Scala 中定义匿名函数的语法很简单,箭头左边是参数列表,右边是函数体,参数的类型是可省略的,Scala 的类型推测系统会推测出参数的类型。使用匿名函数后,我们的代码变得更简洁了。
|
||||
|
||||
|
||||
```
|
||||
val numIncOne = (x:Int) => x + 1
|
||||
```
|
||||
|
||||
上面的表达式就定义了一个接受一个Int类型输入参数的匿名函数,使用起来也很简单
|
||||
|
||||
```
|
||||
numIncOne(2)
|
||||
```
|
||||
|
||||
## 2.输入为空的匿名函数
|
||||
|
||||
```
|
||||
val printFun = () => println("It's print function!")
|
||||
```
|
||||
|
||||
调用:
|
||||
|
||||
```
|
||||
printFun()
|
||||
```
|
||||
|
||||
上面的匿名函数没有接受任何参数,就是打印了一行字符串而已。
|
||||
需要注意的是,匿名函数的返回值的数据类型依赖于函数体的最后一行表达式的值,这个由程序自己判断,匿名函数的返回值类型不能手工指定!
|
||||
|
||||
|
||||
## 3.匿名函数的使用场景
|
||||
1.函数变量(常量)
|
||||
像我们前面举的例子里,都把匿名函数保存为了一个常量。
|
||||
|
||||
2.在将一个匿名函数作为参数进行传递。这个在我们平时编码过程中使用极多,因为scala是函数式编程语言,函数是头等公民,函数经常需要作为参数传递。如果给每个函数,尤其是那种一次性使用的函数起个合适的名字,那简直要头疼死了。在这种情况下,匿名函数就是最佳选择。来看几个特别简单的例子:
|
||||
|
||||
```
|
||||
val rawList = List(1,2,3,4,5,6)
|
||||
println(rawList.filter(_>3)) //取大于3的数
|
||||
println(rawList.map(x => x*2)) //将所有数乘2
|
||||
```
|
|
@ -0,0 +1,68 @@
|
|||
## 1.单例对象
|
||||
Scala中没有静态方法或静态字段,但可以使用object这个语法结构来实现相同的功能。Object与class在语法层面上很相似,除了不能提供构造器参数外,object可以拥有class的所有特性。
|
||||
|
||||
废话不多说,直接上代码
|
||||
|
||||
```
|
||||
object Singleton {
|
||||
|
||||
var count = 0
|
||||
|
||||
def addCount:Long = {
|
||||
count += 1
|
||||
count
|
||||
}
|
||||
}
|
||||
|
||||
object Client {
|
||||
def main(args: Array[String]): Unit = {
|
||||
println(Singleton.addCount)
|
||||
println(Singleton.addCount)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
让代码run起来
|
||||
|
||||
```
|
||||
1
|
||||
2
|
||||
```
|
||||
|
||||
创建单例对象需要用Object关键字,而非Class。因为单例对象无法初始化,所以不能给它的主构造函数传递参数
|
||||
单例一旦定义完毕,它的名字就表示了这个单例对象的唯一一个实例。单例可以传给函数,就像通常传递实例一样
|
||||
|
||||
## 2.伴生类与伴生对象
|
||||
单例对象可以和类具有相同的名称,此时该对象也被称为“伴生对象”。
|
||||
先看代码
|
||||
|
||||
```
|
||||
class Person private(val name:String){
|
||||
private def getSkill() =
|
||||
name + "'s skill is: " + Person.skill
|
||||
}
|
||||
|
||||
object Person {
|
||||
private val skill = "basketball"
|
||||
private val person = new Person("Tracy")
|
||||
def printSkill =
|
||||
println(person.getSkill())
|
||||
|
||||
def main(args: Array[String]): Unit = {
|
||||
Person.printSkill
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
将代码run起来
|
||||
|
||||
```
|
||||
Tracy's skill is: basketball
|
||||
```
|
||||
|
||||
Tricks:
|
||||
1.伴生类Person的构造函数定义为private,虽然这不是必须的,却可以有效防止外部实例化Person类,使得Person类只能供对应伴生对象使用;
|
||||
2.每个类都可以有伴生对象,伴生类与伴生对象写在同一个文件中;
|
||||
3.在伴生类中,可以访问伴生对象的private字段Person.skill;
|
||||
4.而在伴生对象中,也可以访问伴生类的private方法 Person.getSkill();
|
||||
5.在外部不用实例化,直接通过伴生对象访问Person.printSkill()方法
|
|
@ -0,0 +1,85 @@
|
|||
正则表达式是所有攻城狮尤其是算法,数据相关攻城狮必备的技能。日常工作中免不了处理各种字符串与字符串操作,写好正则表达式能大幅度提高工作效率,提升工作愉悦度。现在就简单总结一下scala中常见的正则表达式用法。
|
||||
|
||||
如果有对正则表达式不熟悉的同志们,可以查看[正则表达式30分钟入门教程](https://deerchao.net/tutorials/regex/regex.htm)。
|
||||
|
||||
## 1.匹配电话号码
|
||||
匹配电话号码是我们常见的需求之一。代码如下:
|
||||
|
||||
```
|
||||
def reg() = {
|
||||
val PHONE_PATTERN = """1(([3,5,8]\d{9})|(4[5,7]\d{8})|(7[0,6-8]\d{8}))""".r
|
||||
val num = "13566513245"
|
||||
val res = PHONE_PATTERN.findFirstIn(num).get
|
||||
println(res)
|
||||
}
|
||||
```
|
||||
|
||||
在这里,"""表示raw string,三引号括起来的字符串表示不需要转义。因为正则表达式本身比较长,可读性也比较差,所以用三引号可以降低阅读难度,并且缩短字符串长度。
|
||||
|
||||
## 2.匹配小写字母
|
||||
|
||||
```
|
||||
def reg2() = {
|
||||
val str = "abc"
|
||||
val pattern = new Regex("[a-z]+")
|
||||
println(pattern.findFirstIn(str).get)
|
||||
}
|
||||
```
|
||||
|
||||
## 3.匹配所有英文字母
|
||||
|
||||
```
|
||||
def reg3() = {
|
||||
val str = "abcDEFg"
|
||||
//val pattern = new Regex("(?i)[a-z]+")
|
||||
val pattern = new Regex("[a-zA-Z]")
|
||||
println(pattern.findAllIn(str).mkString(""))
|
||||
}
|
||||
```
|
||||
|
||||
注意pattern的两种写法都可以。
|
||||
|
||||
## 4.匹配email
|
||||
|
||||
```
|
||||
def reg4() = {
|
||||
val str = "email:leilei@mi.com.cn"
|
||||
val pattern = new Regex("""(?i)[a-z0-9._-]+@[a-z0-9._-]+(\.[a-z0-9._-]+)+""")
|
||||
println(pattern.findAllIn(str).mkString(""))
|
||||
}
|
||||
```
|
||||
|
||||
## 5.匹配日期,并提取年月日
|
||||
|
||||
```
|
||||
def reg5() = {
|
||||
val str = "2017-01-01"
|
||||
val datePattern = new Regex("""(\d{4})-(\d{2})-(\d{2})""")
|
||||
val datePattern(year, month, day) = str
|
||||
println(year, month, day)
|
||||
|
||||
str match {
|
||||
case datePattern(year, month, day) => println(s"year is $year.\n" +
|
||||
f"month is $month.\n" + raw"day is $day")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
正则表达式中,小括号表示分组的概念。从上面的例子可以看出,scala正则中提取分组可以采用两种方式,都很方便。模式匹配的方式可能更直观,更符合scala的习惯。
|
||||
|
||||
## 6.非铆定
|
||||
|
||||
```
|
||||
def reg7() = {
|
||||
val datePattern = new Regex("""(\d{4})-(\d{2})-(\d{2})""")
|
||||
val pattern = datePattern.unanchored
|
||||
val str = "Date: 2017-05-21 19:25:18"
|
||||
|
||||
str match {
|
||||
case pattern(year, month, day) => println(s"year is $year.\n" +
|
||||
f"month is $month.\n" + raw"day is $day")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
如果要非铆定,可以使用上面的用法。
|
|
@ -0,0 +1,108 @@
|
|||
## 1.scala中如何定义类
|
||||
scala中定义类的方式很简单
|
||||
|
||||
```
|
||||
class Point(xc:Int,yc:Int)
|
||||
```
|
||||
|
||||
上面这行代码就定义了一个类
|
||||
1.首先是关键字class
|
||||
2.其后是类名 Point
|
||||
3.类名之后的括号中是构造函数的参数列表,这个例子中是类的两个变量xc,yc,且均为Int类型的数据。
|
||||
|
||||
## 2.类的构造方法
|
||||
类的定义中可以有多个构造参数。与java中不同的是,scala类名称后直接加上构造函数的参数列表,这个构造函数就是主构造函数。另外,Scala中有只有一个主要构造函数,其他都是辅助构造函数。而且需要注意的是,辅助构造函数必须调用主构造函数或者通过this(...)之间相互调用。
|
||||
|
||||
另外与java中不同的一点是,在scala中重写方法,必须加上override关键字,否则编译器会报错!
|
||||
|
||||
```
|
||||
class Point(xc:Int,yc:Int) {
|
||||
|
||||
var x:Int = xc
|
||||
var y:Int = yc
|
||||
|
||||
val isOriginal:Boolean = {
|
||||
x == 0 && y == 0
|
||||
}
|
||||
|
||||
def this(xc:Int) {
|
||||
this(xc,0)
|
||||
println("hello,I'm another constructor!")
|
||||
}
|
||||
|
||||
def move(dx:Int,dy:Int): Unit = {
|
||||
x += dx
|
||||
y += dy
|
||||
}
|
||||
|
||||
override def toString(): String = "(" + x + ", " + y + ")"
|
||||
}
|
||||
```
|
||||
|
||||
再写个客户端代码调用这个类:
|
||||
|
||||
```
|
||||
object TestPoint {
|
||||
|
||||
def t1(): Unit = {
|
||||
val p1 = new Point(10,15)
|
||||
println(p1)
|
||||
val p2 = new Point(1)
|
||||
println(p2)
|
||||
}
|
||||
|
||||
def main(args: Array[String]): Unit = {
|
||||
t1()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
将客户端代码run起来
|
||||
|
||||
```
|
||||
(10, 15)
|
||||
hello,I'm another constructor!
|
||||
(1, 0)
|
||||
```
|
||||
|
||||
## 3.继承
|
||||
scala继承基类很简单,跟java一样会用extends关键字即可。
|
||||
|
||||
```
|
||||
class MyPoint(xc:Int,yc:Int) extends Point(xc,yc) {
|
||||
|
||||
def sayMyPoint(): Unit = {
|
||||
println("location: " + xc + "," + yc)
|
||||
}
|
||||
|
||||
override def move(dx: Int, dy: Int): Unit = {
|
||||
x += 2*dx
|
||||
y += 2*dy
|
||||
println("now x is: " + x + ",y is: " + y)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
调用继承类:
|
||||
|
||||
```
|
||||
object TestPoint {
|
||||
|
||||
def t2(): Unit = {
|
||||
val p1 = new MyPoint(10,15)
|
||||
p1.sayMyPoint()
|
||||
p1.move(5,5)
|
||||
}
|
||||
|
||||
def main(args: Array[String]): Unit = {
|
||||
t2()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
将代码run起来:
|
||||
|
||||
```
|
||||
location: 10,15
|
||||
now x is: 20,y is: 25
|
||||
```
|
|
@ -0,0 +1,58 @@
|
|||
## 1.花括号与小括号的区别
|
||||
以下代码的用法,非常常见:
|
||||
|
||||
```
|
||||
val raw = List(("a",1),("b",2),("c",3))
|
||||
val res = raw.map{ case (key,value) => value }.reduce(_ + _)
|
||||
println(res)
|
||||
```
|
||||
|
||||
注意这里如果将map方法的大括号换成小括号,代码会报错。
|
||||
方法中的花括号有2种意思:
|
||||
1)scala中函数的小括号,可以用花括号来表示,即foo{xx} 与 foo(xx)是一回事儿。
|
||||
2)对于只有一个参数的方法,其小括号是可以省略的,map(lambda)可写为 map lambda,即这块{case (key,value) => value} 连同花括号整体是一个lambda(函数字面量)。
|
||||
|
||||
|
||||
这里很明显花括弧啊的用法是第二种。
|
||||
|
||||
## 2.case偏函数
|
||||
以下代码的用法,也非常常见:
|
||||
|
||||
```
|
||||
val res2 = List(1,2,3).map{
|
||||
case 1 => "first"
|
||||
case 2 => "second"
|
||||
case _ => "other"
|
||||
}
|
||||
```
|
||||
|
||||
{case x => y} 叫做偏函数(必须用大括号“{}”,使用“()”会报错)。 与完全函数想对应,普通的方法都是完全函数,即f(i:Int) = xxx 是将所有Int类型作为参数的,是对整个Int集的映射;而偏函数则是对部分数据的映射。
|
||||
|
||||
## 3.变长参数
|
||||
有时候,函数需要一个可变长度的参数。在scala中是容易实现的:
|
||||
|
||||
```
|
||||
object T1 {
|
||||
|
||||
def sum(args:Int*) = {
|
||||
var result = 0
|
||||
for(arg <- args) result += arg
|
||||
result
|
||||
}
|
||||
|
||||
def main(args: Array[String]): Unit = {
|
||||
val s = sum(1 to 5:_*)
|
||||
println(s)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
注意调用的时候使用:_*,将序列或者集合的内容全部当做参数来传递。
|
||||
|
||||
## 4.asInstanceOf[AnyRef]
|
||||
|
||||
```
|
||||
val str = MessageFormat.format("The answer to {0} is {1}","everything",40.asInstanceOf[AnyRef])
|
||||
```
|
||||
|
||||
这种写法很常见,对于任意的Object类型的参数都是可以这样。这种类似的参数在变长参数方法中使用最多。
|
|
@ -0,0 +1,68 @@
|
|||
在函数式语言中,函数是一等公民,也是最基本的功能块。通常为了使代码更加清晰易读,我们往往会写许多辅助函数。但是大家都知道,给函数或者变量起个合适的名字,是coding中最难的任务,没有之一。函数多了以后,很容易名字就冲突,而且暴露给外部过多的函数也会带来各种问题。在java里,我们是通过private method这种方式来解决。当然,scala里也可以这么干。但是我们既然说scala是函数式语言,那我们就采用函数式语言的方式来解决这个问题:在函数内部再定义函数。就像局部变量一样,其作用 域仅限于外部函数内部。
|
||||
|
||||
在 http://blog.csdn.net/bitcarmanlee/article/details/52194255 一文中,我们提到了用牛顿法求解方根。现在我们以此为例,来看看scala中怎么实现。
|
||||
|
||||
## 1.常规方式,定义很多外部函数
|
||||
|
||||
```
|
||||
object Demo {
|
||||
|
||||
//满足终止条件
|
||||
def isGoodEnough(guess:Double,x:Double) =
|
||||
abs(guess * guess - x) < 0.0001
|
||||
//返回绝对值
|
||||
def abs(x:Double) =
|
||||
if (x<0) -x else x
|
||||
//迭代公式
|
||||
def sqrtIter(guess:Double,x:Double):Double =
|
||||
if (isGoodEnough(guess,x))
|
||||
guess
|
||||
else
|
||||
sqrtIter((guess + x/guess)/2,x)
|
||||
//默认从1开始迭代
|
||||
def sqrt(x:Double):Double =
|
||||
sqrtIter(1,x)
|
||||
|
||||
def main(args: Array[String]): Unit = {
|
||||
println(sqrt(2))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
将代码run起来:
|
||||
|
||||
```
|
||||
1.4142156862745097
|
||||
```
|
||||
|
||||
上面的代码,总共定义了4个函数。但是对于用户来说,用户只关心sqrt函数,而我们一下暴露了4个函数给用户。scala 里可以内部函数,我们可以将这些辅助函数定义为 sqrt的内部函数,更进一步,由于内部函数可以访问其函数体外部定义的变量,我们可以去掉这些辅助函数中的 x参数,代码将会变得更为简洁,内部函数访问包含该函数的参数是非常常见的一种嵌套函数的用法。
|
||||
|
||||
## 2.使用内部函数
|
||||
|
||||
```
|
||||
object Demo {
|
||||
def sqrtEncapsulation(x:Double):Double = {
|
||||
|
||||
def sqrtIter(guess:Double):Double = {
|
||||
if(isGoodEnough(guess))
|
||||
guess
|
||||
else
|
||||
sqrtIter((guess + x/guess) / 2)
|
||||
}
|
||||
def isGoodEnough(guess:Double) = {
|
||||
abs(guess * guess - x) < 0.0001
|
||||
}
|
||||
def abs(x:Double) = {
|
||||
if(x < 0) -x else x
|
||||
}
|
||||
|
||||
sqrtIter(1)
|
||||
}
|
||||
|
||||
def main(args: Array[String]): Unit = {
|
||||
println(sqrtEncapsulation(2))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
是不是经过这么包装以后,代码的可读性更强,封装得也更好,使用起来也更为方便呢?
|
|
@ -0,0 +1,51 @@
|
|||
在scala中,字符串可以带s,f,raw前缀。这几个前缀都可以用来进行变量替换。下面来简单分析实验一下。
|
||||
|
||||
## 1.s前缀
|
||||
s前缀的作用就是用来表示变量替换。
|
||||
|
||||
```
|
||||
def test() = {
|
||||
val word = "hello"
|
||||
println(s"$word, world")
|
||||
}
|
||||
```
|
||||
|
||||
函数运行的结果为:
|
||||
|
||||
```
|
||||
hello, world
|
||||
```
|
||||
|
||||
## 2.f前缀
|
||||
f前缀在表示变量替换的同时,还可以在变量名后面添加格式化参数。
|
||||
```
|
||||
def test() = {
|
||||
val year1 = 2017
|
||||
println(f"hello, $year1%.2f")
|
||||
val year2 = "2017"
|
||||
println(f"hello, $year2%.3s")
|
||||
}
|
||||
```
|
||||
|
||||
输出的结果为:
|
||||
|
||||
```
|
||||
hello, 2017.00
|
||||
hello, 201
|
||||
```
|
||||
|
||||
## 3.raw前缀
|
||||
raw前缀在表示变量替换的同事,不对特殊字符转义。
|
||||
|
||||
```
|
||||
def t6() = {
|
||||
val year = 2017
|
||||
println(raw"hello\t$year")
|
||||
}
|
||||
```
|
||||
|
||||
输出结果为:
|
||||
|
||||
```
|
||||
hello\t2017
|
||||
```
|
|
@ -0,0 +1,160 @@
|
|||
Scala算是一门博采众家之长的语言,兼具OO与FP的特性,若使用恰当,可以更好地将OO与FP的各自优势发挥到极致;然而问题也随之而来,倘若过分地夸大OO特性,Scala就变成了一门精简版的Java,写出的是没有Scala Style的拙劣代码;倘若过分追求FP的不变性等特性,因为Scala在类型系统以及Monad实现的繁琐性,又可能导致代码变得复杂,不易阅读,反而得不偿失。
|
||||
|
||||
看来,赋予程序员选择的自由,有时候未必是好事!
|
||||
|
||||
在OO世界里,设计模式曾经风靡全世界,你不懂设计模式,都不好意思说自己是程序员。现在呢?说你懂设计模式,倒显得你逼格低了,心里鄙视:“这年头谁还用设计模式,早过时了!”程序员心中的鄙视链开始加成,直接失血二十格。
|
||||
|
||||
其实什么事情都得辩证来看!设计模式对OO设计的推进作用不容忽视,更不容轻视。我只是反对那种为了“模式”而“模式”的僵化思想,如果没有明白设计模式的本质思想,了解根本的设计原理,设计模式无非就是花拳绣腿罢了。当然,在FP世界里,设计模式开始变味开始走形,但诸多模式的本质,例如封装、抽象,仍然贯穿其中,不过是表达形式迥然而已罢了。
|
||||
|
||||
在混合了OO与FP的Scala语言中,我们来观察设计模式的实现,会非常有趣。Pavel Fatin有篇博客 [Design Pattern in Scala](https://pavelfatin.com/design-patterns-in-scala/%20Design%20Patterns%20in%20Scala)将Java设计模式与Scala进行了对比,值得一读。我这里想借用他的案例,然后从另一个角度来俯瞰设计模式。
|
||||
|
||||
在Pavel Fatin比较的设计模式中,部分模式在Scala中不过是一种语法糖(Syntax Sugar),包括:
|
||||
|
||||
Factory Method
|
||||
Lazy Initialization
|
||||
Singleton
|
||||
Adapter
|
||||
Value Object
|
||||
|
||||
## 1.Factory Method
|
||||
文中给出的Factory Method模式,准确地说其实是静态工厂模式,它并不在GOF 23种模式之列,但作为对复杂创建逻辑的一种封装,常常被开发人员使用。站在OCP(开放封闭原则)的角度讲,该模式对扩展不是开放的,但对于修改而言,却是封闭的。如果创建逻辑发生了变化,可以保证仅修改该静态工厂方法一处。同时,该模式还可以极大地简化对象创建的API。
|
||||
|
||||
在Scala中,通过引入伴生对象(Companion Object)来简化静态工厂方法,语法更加干净,体现了Scala精简的设计哲学。即使不是要使用静态工厂,我们也常常建议为Scala类定义伴生对象,尤其是在DSL上下文中,更是如此,因为这样可以减少new关键字对代码的干扰。
|
||||
|
||||
## 2.Lazy Initialization
|
||||
lazy修饰符在Scala中有更深远的涵义,例如牵涉到所谓严格(Strictness)函数与非严格(Non-strictness)函数。在Scala中,若未明确声明,所有函数都是严格求值的,即函数会立即对它的参数进行求值。而如果对val变量添加lazy修饰符,则Scala会延迟对该变量求值,直到它第一次被引用时。如果要定义非严格函数,可以将函数设置为by name参数。
|
||||
|
||||
scala的lazy修饰符常常被用作定义一些消耗资源的变量。这些资源在初始化时并不需要,只有在调用某些方法时,才需要准备好这些资源。例如在Spark SQL的QeuryExecution类中,包括optimizedPlan、sparkPlan、executedPlan以及toRdd等,都被定义为lazy val:
|
||||
|
||||
```
|
||||
class QueryExecution(val sparkSession: SparkSession, val logical: LogicalPlan) {
|
||||
lazy val analyzed: LogicalPlan = {
|
||||
SparkSession.setActiveSession(sparkSession)
|
||||
sparkSession.sessionState.analyzer.execute(logical)
|
||||
}
|
||||
lazy val withCachedData: LogicalPlan = {
|
||||
assertAnalyzed()
|
||||
assertSupported()
|
||||
sparkSession.sharedState.cacheManager.useCachedData(analyzed)
|
||||
}
|
||||
lazy val optimizedPlan: LogicalPlan = sparkSession.sessionState.optimizer.execute(withCachedData)
|
||||
lazy val sparkPlan: SparkPlan = {
|
||||
SparkSession.setActiveSession(sparkSession)
|
||||
planner.plan(ReturnAnswer(optimizedPlan)).next()
|
||||
}
|
||||
lazy val executedPlan: SparkPlan = prepareForExecution(sparkPlan)
|
||||
lazy val toRdd: RDD[InternalRow] = executedPlan.execute()
|
||||
}
|
||||
```
|
||||
|
||||
这样设计有一个好处是,当程序在执行到这些步骤时,并不会被马上执行,从而使得初始化QueryExecution变得更快。只有在需要时,这些变量对应的代码才会执行。这也是延迟加载的涵义。
|
||||
|
||||
## 3.Singleton Pattern
|
||||
C#提供了静态类的概念,但Java没有,而Scala则通过引入Object弥补了Java的这一缺失,而且从语义上讲,似乎比静态类(Static Class)更容易让人理解。
|
||||
|
||||
Object可以派生自多个trait。例如派生自App trait,就可直接享有main函数的福利。
|
||||
|
||||
```
|
||||
trait App extends DelayedInit {
|
||||
def main(args: Array[String]) = {
|
||||
this._args = args
|
||||
for (proc <- initCode) proc()
|
||||
if (util.Properties.propIsSet("scala.time")) {
|
||||
val total = currentTime - executionStart
|
||||
Console.println("[total " + total + "ms]")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object Main extends App
|
||||
```
|
||||
|
||||
继承多个trait的好处是代码复用。我们可以将许多小粒度方法的实现定义在多个trait中。这些方法如果被类继承,则成为实例方法,如果被Object继承,则变成了线程安全的静态方法(因为继承trait的实现就是一个mixin)。多么奇妙!所以很多时候,我们会尽量保证Obejct的短小精悍,然后将许多逻辑放到trait中。当你看到如下代码时,其实不必惊讶:
|
||||
|
||||
```
|
||||
object Main extends App
|
||||
with InitHook
|
||||
with ShutdownHook
|
||||
with ActorSystemProvider
|
||||
with ScheduledTaskSupport
|
||||
```
|
||||
|
||||
这种小粒度的trait既可以保证代码的复用,也有助于职责分离,还有利于测试。真是再好不过了!
|
||||
|
||||
## 4.Adapter Pattern
|
||||
隐式转换当然可以用作Adapter。在Scala中,之所以可以更好地调用Java库,隐式转换功不可没。从语法上看,隐式转换比C#提供的扩展方法更强大,适用范围更广。
|
||||
|
||||
Pavel Fatin给出了日志转换的Adapter案例:
|
||||
|
||||
```
|
||||
trait Log {
|
||||
def warning(message: String)
|
||||
def error(message: String)
|
||||
}
|
||||
|
||||
final class Logger {
|
||||
def log(level: Level, message: String) { /* ... */ }
|
||||
}
|
||||
|
||||
implicit class LoggerToLogAdapter(logger: Logger) extends Log {
|
||||
def warning(message: String) { logger.log(WARNING, message) }
|
||||
def error(message: String) { logger.log(ERROR, message) }
|
||||
}
|
||||
|
||||
val log: Log = new Logger()
|
||||
```
|
||||
|
||||
这里的隐式类LoggerToLogAdapter可以将Logger适配为Log。与Java实现Adapter模式不同的是,我们不需要去创建LoggerToLogAdapter的实例。如上代码中,创建的是Logger实例。Logger自身与Log无关,但在创建该对象的上下文中,由于我们定义了隐式类,当Scala编译器遇到该隐式类时,就会为Logger添加通过隐式类定义的代码,包括隐式类中定义的对Log的继承,以及额外增加的warning与error方法。
|
||||
|
||||
在大多数场景,Adapter关注的是接口之间的适配。但是,当要适配的接口只有一个函数时,在支持高阶函数(甚至只要支持Lambda)的语言中,此时的Adapter模式就味如鸡肋了。假设Log与Logger接口只有一个log函数(不管它的函数名是什么),接收的参数为(Level, String),那么从抽象的角度来看,它们其实属于相同的一个抽象:
|
||||
|
||||
```
|
||||
f: (Level, String) => Unit
|
||||
```
|
||||
|
||||
任何一个符合该定义的函数,都是完全适配的,没有类型与函数名的约束。
|
||||
|
||||
如果再加上泛型,抽象会更加彻底。例如典型的Load Pattern实现:
|
||||
|
||||
```
|
||||
def using[A](r : Resource)(f : Resource => A) : A =
|
||||
try {
|
||||
f(r)
|
||||
} finally {
|
||||
r.dispose()
|
||||
}
|
||||
```
|
||||
|
||||
泛型A可以是任何类型,包括Unit类型。这里的f扩大了抽象范围,只要满足从Resource转换到A的语义,都可以传递给using函数。更而甚者可以完全抛开对Resource类型的依赖,只需要定义了close()方法,都可以作为参数传入:
|
||||
|
||||
```
|
||||
def using[A <: def close():Unit, B][resource: A](f: A => B): B =
|
||||
try {
|
||||
f(resource)
|
||||
} finally {
|
||||
resource.close()
|
||||
}
|
||||
|
||||
using(io.Source.fromFile("example.txt")) { source => {
|
||||
for (line <- source.getLines) {
|
||||
println(line)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
因为FileResource定义了close()函数,所以可以作为参数传给using()函数。
|
||||
|
||||
## 5.Value Object
|
||||
|
||||
Value Object来自DDD中的概念,通常指的是没有唯一标识的不变对象。Java没有Value Object的语法,然而因其在多数业务领域中被频繁使用,Scala为其提供了快捷语法Case Class。在几乎所有的Scala项目中,都可以看到Case Class的身影。除了在业务中表现Value Object之外,还可以用于消息传递(例如AKKA在Actor之间传递的消息)、序列化等场景。此外,Case Class又可以很好地支持模式匹配,或者作为典型的代数数据类型(ADT)。例如Scala中的List,可以被定义为:
|
||||
|
||||
```
|
||||
sealed trait List[+T]
|
||||
case object Nil extends List[Nothing]
|
||||
case class Cons[+T](h: T, t: List[T]) extends List[T]
|
||||
```
|
||||
|
||||
这里,case object是一个单例的值对象。而Nil与Cons又都同时继承自一个sealed trait。在消息定义时,我们常常采用这样的ADT定义。例如List定义中,Nil与Cons就是List ADT的sum或者union,而Cons构造器则被称之为是参数h(代表List的head)与t(代表List的tail)的product。这也是ADT(algebraic data type)之所以得名。注意它与OO中的ADT(抽象数据类型)是风马牛不相及的两个概念。
|
||||
|
||||
原文链接: http://zhangyi.farbox.com/post/designthinking/design-patterns-with-scala-syntax-sugar
|
|
@ -0,0 +1,97 @@
|
|||
## 1.函数式编程带来的好处
|
||||
函数式编程近些年异军突起,又重新回到了人们的视线,并得到蓬勃发展。总结起来,无外乎如下好处:
|
||||
1.减少了可变量(Immutable Variable)的声明,程序更为安全。
|
||||
2.相比命令式编程,少了非常多的状态变量的声明与维护,天然适合高并发多现成并行计算等任务,这也是函数是编程近年又大热的重要原因。
|
||||
3.代码更为简洁,可读性更强,对强迫症的同学来说是个重大福音。
|
||||
|
||||
## 2.函数式编程的本质
|
||||
函数式编程中的函数这个术语不是指计算机中的函数(实际上是Subroutine),而是指数学中的函数,即自变量的映射。也就是说一个函数的值仅决定于函数参数的值,不依赖其他状态。比如sqrt(x)函数计算x的平方根,只要x不变,不论什么时候调用,调用几次,值都是不变的。
|
||||
|
||||
引用知乎上的一段描述:
|
||||
纯函数式编程语言中的变量也不是命令式编程语言中的变量,即存储状态的单元,而是代数中的变量,即一个值的名称。变量的值是不可变的(immutable),也就是说不允许像命令式编程语言中那样多次给一个变量赋值。比如说在命令式编程语言我们写“x = x + 1”,这依赖可变状态的事实,拿给程序员看说是对的,但拿给数学家看,却被认为这个等式为假。
|
||||
|
||||
函数式语言的如条件语句,循环语句也不是命令式编程语言中的控制语句,而是函数的语法糖,比如在Scala语言中,if else不是语句而是三元运算符,是有返回值的。
|
||||
|
||||
## 3.实例
|
||||
### 1.集合中包含某一个元素
|
||||
经常有判断集合是否包含某一元素的需求。在命令式编程中,我们一般都会这么干:
|
||||
|
||||
```
|
||||
public class IfExists {
|
||||
|
||||
public static void traditional() {
|
||||
boolean flag = false;
|
||||
String[] citys = {"bj","sh","gz","sz"};
|
||||
for(String city:citys) {
|
||||
if(city.equals("bj")) {
|
||||
flag = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
System.out.println(flag);
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
traditional();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这段代码正常工作肯定是没有问题的。而且逻辑本身也很简单,大家都能写出来。但是在我看来,是不是太长了。。。
|
||||
|
||||
看看我们用scala实现一下
|
||||
|
||||
```
|
||||
object IfExists{
|
||||
|
||||
def main(args: Array[String]): Unit = {
|
||||
println(Array("bj","sh","gz","sz").contains("bj"))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
直接针对集合进行操作,省去了讨厌的循环判断等一系列对我们来说累赘得很的操作。瞬间感觉清爽好多有木有。
|
||||
|
||||
### 2.对一个集合做一系列处理
|
||||
现在有个价格的集合。如果价格大于20,先九折,然后相加。如果用java实现
|
||||
|
||||
```
|
||||
public class Price {
|
||||
|
||||
public final static List<BigDecimal> prices = Arrays.asList(
|
||||
new BigDecimal("10"), new BigDecimal("30"), new BigDecimal("17"),
|
||||
new BigDecimal("20"), new BigDecimal("15"), new BigDecimal("18"),
|
||||
new BigDecimal("45"), new BigDecimal("12"));
|
||||
|
||||
public static void sumOfPrices() {
|
||||
BigDecimal total = BigDecimal.ZERO;
|
||||
for(BigDecimal price:prices) {
|
||||
if(price.compareTo(BigDecimal.valueOf(20)) > 0) {
|
||||
total = total.add(price.multiply(BigDecimal.valueOf(0.9)));
|
||||
}
|
||||
}
|
||||
System.out.println("The total is: " + total);
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
sumOfPrices();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这代码也可以正常工作没有问题。问题跟前面一样,还是太累赘,不是那么优雅,可读性也不是很好。用scala写一下上面的逻辑:
|
||||
|
||||
```
|
||||
object Prices {
|
||||
|
||||
def main(args: Array[String]): Unit = {
|
||||
val prices = Array(10, 30, 17, 20, 15, 18, 45, 12)
|
||||
val total = prices.filter(x => x>20)
|
||||
.map(x => x*0.9)
|
||||
.reduce((x,y) => x+y)
|
||||
println("total is: " + total)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
简直就是强迫症患者的福音有木有!而且这段代码的可读性不知道高了多少:针对集合,先做filter操作,将大于20的元素选出来,然后做map操作,乘以0.9的系数,最后再将所有的值相加。接近于自然语言的表达方式,一目了然。
|
Loading…
Reference in New Issue