add scala

pull/1/head
wanglei 2020-08-09 09:14:02 +08:00
parent 5542a81030
commit e579248efa
11 changed files with 1192 additions and 0 deletions

View File

@ -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 的类型是 StringValue 的类型是 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 做 getScala 一样会爆给你看。
```
例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

View File

@ -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部分源码

View File

@ -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
```

View File

@ -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方法

View File

@ -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")
}
}
```
如果要非铆定,可以使用上面的用法。

View File

@ -0,0 +1,108 @@
## 1.scala中如何定义类
scala中定义类的方式很简单
```
class Point(xc:Int,yc:Int)
```
上面这行代码就定义了一个类
1.首先是关键字class
2.其后是类名 Point
3.类名之后的括号中是构造函数的参数列表这个例子中是类的两个变量xcyc且均为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
```

View File

@ -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种意思
1scala中函数的小括号可以用花括号来表示即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类型的参数都是可以这样。这种类似的参数在变长参数方法中使用最多。

View File

@ -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))
}
}
```
是不是经过这么包装以后,代码的可读性更强,封装得也更好,使用起来也更为方便呢?

View File

@ -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
```

View File

@ -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。这也是ADTalgebraic data type之所以得名。注意它与OO中的ADT抽象数据类型是风马牛不相及的两个概念。
原文链接: http://zhangyi.farbox.com/post/designthinking/design-patterns-with-scala-syntax-sugar

View File

@ -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的系数,最后再将所有的值相加。接近于自然语言的表达方式,一目了然。