写在前面:
多学、多记、多写代码,最后做一个优秀码农,因为每一个码农都是一个资深美食家,哈哈 ~~~我是一个matlab出家的码农,java不会,c++不熟,c皮毛,python基本不会,为了做数据分析,用spark开发,开始学习scala,本blog记录我的学习过程,不知两年后能否写出优秀的代码。
部分简称的全称:
FP:Functional Programming 函数式程序设计
OO:Object Oriented 面向对象的
1.scala的优点和简介
Scala虽然是一门彻头彻底的静态语言,但又具备了现代动态语言的很多方便和灵活:一、不需要冗余的类型声明
二、map替代各种for循环,简化代码。
快捷键使用(mac): command+空格:能看一个函数的原代码; option+enter:将鼠标放在一个变量或者常量A上,能够看到这个A的类型。 |
2.基本语法部分
2.0 scala简单语法
(假装你已经安装好scala,打开终端,输入 $:scala 回车,进入scala编译环境)
scala> List(1,31,4,53,4,3,234)
res0: List[Int] = List(1, 31, 4, 53, 4, 3, 234)
第一行代码,怎么少的了hello world!
scala> println("Hello World!")
Hello World!
语句尾部分号问题:scala中一行代码结束,可以不写分号";",如果一行写多个语句,需要在句末尾加上分号。
一行语句不能写两行,scala默认将一行代码作为一个语句,除非遇见语句分隔号——分号。
举个例子:将1+2分为两行写,就是两个语句,输出两结果:
scala> 1
res3: Int = 1
scala> +2
res4: Int = 2
scala> 1+2
res5: Int = 3
这种情况解决办法:
一、用括号包含分行代码
scala> (1
| +2)
res6: Int = 3
2.1 scala基本类型
类型(第一个单词是大写) | 范围 |
Byte | 8bit |
Short | 16bit |
Int | 32bit |
Long | 64bit |
Char | 16bit |
String | a sequence of chars |
Float | 32bit |
Double | 64bit |
Booleam | true of false |
2.2变量定义
一、val:不可变变量,又名常量。建议如果这个变量的数值,在后续使用中不变,那么定义的时候定义为val,便于追踪bug。
scala> val temp =1
temp: Int = 1
scala> temp =2
<console>:12: error: reassignment to val
temp =2
^
二、var:可变变量。后续可以改变这个变量的值。
scala> var temp = 1
temp: Int = 1
scala> temp =2
temp: Int = 2
2.3函数定义
可以简写为, 返回值类型不需要写, 可以推断出, 只有一条语句, 所以{}可以省略:
scala> def max2(x:Int,y:Int)=if(x>y) x else y
max2: (x: Int, y: Int)Int
简单的funciton, 返回值为Unit, 类似Void(区别在于void为无返回值, 而scala都有返回值, 只是返回的为Unit,()):
scala> def goodluck()=println("hello world!")
goodluck: ()Unit
那么我们尝试判断一下goodluck的类型是不是unit呢,unit这里就是():看到下面的代码,肯定了goodluck的返回值是()类型。
scala> goodluck() == ()
hello world!
res8: Boolean = true
2.4函数形式(function literal)
cala FP的基础, function作为first class, 以function literal的形式作为参数被传递
scala> List(1,2,3,4)
res9: List[Int] = List(1, 2, 3, 4)
scala> res9.foreach((arg:Int)=>println(arg))
1
2
3
4
scala> res9.foreach(arg=>println(arg)) //省略类型
1
2
3
4
scala> res9.foreach(println) //其实连参赛列表也可以省略
1
2
3
4
可以看到scala在省略代码量上可以说下足功夫, 只要能推断出来的你都可以不写, 这也是对于静态类型系统的一种形式的弥补
对于oo程序员, 可能比较难理解, 其实等于:
for (arg <-args)
println(arg)
2.5控制结构
由于scala是偏向于FP的, 所以所有控制结构都有返回值, 这样便于FP编程。
val filename =
if (!args.isEmpty) args(0)
else "default.txt"
While, 在FP里面不推荐使用循环, 应该用递归,尽量避免
For, 没有python和clojure的好用或简洁
for (
file <- filesHere //generator,用于遍历,每次file都会被从新初始化
if file.isFile; //过滤条件, 多个条件需要用;竟然没有and操作,,这个后续再检查更新,这个不是很确定。
if file.getName.endsWith(".scala"); //第二个过滤
line <- fileLines(file) //嵌套for
trimmed = line.trim //Mid-stream variable bindings, val类型,类似clojure let
if trimmed.matches(pattern)
) println(file +": "+ trimmed)
//for默认不会产生新的集合, 必须使用yield
def scalaFiles =
for {
file <- filesHere
if file.getName.endsWith(".scala")
} yield file //yield产生新的集合,类似python
2.5数据结构
2.5.1数组
可变的同类对象序列, 适用于OO场景。
scala> val g = new Array[String](3)
g: Array[String] = Array(null, null, null)
scala> g(0)="hello"
scala> g(1)=","
scala> g(2)="world!"
scala> g.foreach(println)
hello
,
world!
Scala 操作符等价于方法, 所以任意方法都可以以操作符的形式使用1 + 2 //(1).+(2), 在只有一个参数的情况下, 可以省略.和()0 to 2 //(0).to(2)greetStrings(0) //greetStrings.apply(0),这也是为什么scala使用(), 而非[]
greetStrings(0) = "Hello" //greetStrings.update(0, "Hello")
简化的array初始化:
scala> val num=Array("one","two","threee")
num: Array[String] = Array(one, two, threee)
scala> val num =Array.apply("one","two","three")
num: Array[String] = Array(one, two, three)
2.5.2List
相对于array, List为不可变对象序列, 适用于FP场景.
对于List最常用的操作符为:
::——cons, 把新的elem放到list最前端
:::——两个list的合并scala> val oneTwo = List(1, 2)
oneTwo: List[Int] = List(1, 2)
scala> val threeFour = List(3, 4)
threeFour: List[Int] = List(3, 4)
scala> val zeroOneTwo = 0 :: oneTwo
zeroOneTwo: List[Int] = List(0, 1, 2)
scala> val oneTwoThreeFour = oneTwo ::: threeFour
oneTwoThreeFour: List[Int] = List(1, 2, 3, 4)
一、右操作数, ::
普通情况下, 都是左操作数, 比如, a * b => a.*(b)
但是当方法名为:结尾时, 为右操作数
1 :: twoThree => twoThree.::(1)
二、不支持append
原因是, 这个操作的耗时会随着list的长度变长而线性增长, 所以不支持, 只支持前端cons, 实在需要append可以考虑ListBuffer。
三、方法:拉链操作zip
scala> List(1,2,3,4)
res20: List[Int] = List(1, 2, 3, 4)
scala> List("a","b","c","d")
res22: List[String] = List(a, b, c, d)
scala> res20.zip(res22)
res26: List[(Int, String)] = List((1,a), (2,b), (3,c), (4,d))
四、方法:drop(n)
返回去掉前n个元素的列表
scala> res20.drop(3)
res28: List[Int] = List(4)
五、方法:exists()
判断列表中是否包含某个元素。返回值类型为布尔类型。
scala> res20.exists(s=>s==3)
res29: Boolean = true
scala> res20.exists(s=>s==5)
res30: Boolean = false
六、方法filter()
过滤作用,根据需求写语句。
七、map()
对列表中的每一个元素都进行同样的操作。
scala> res20.map(s => s*2)
res34: List[Int] = List(2, 4, 6, 8)
2.5.3 Queues 队列
两种形式:可变队列和不可变队列。
import scala.collection.immutable.Queue //不可变Queue
val empty = new Queue[Int]
val has1 = empty.enqueue(1) //添加单个元素
val has123 = has1.enqueue(List(2, 3)) //添加多个元素
val (element, has23) = has123.dequeue //取出头元素,返回两个值, 头元素和剩下的queue
element: Int = 1
has23: scala.collection.immutable.Queue[Int] = Queue(2,3)
import scala.collection.mutable.Queue //可变Queue
val queue = new Queue[String]
queue += "a" //添加单个
queue ++= List("b", "c") //添加多个
queue.dequeue //取出头元素, 只返回一个值
res22: String = a
scala> queue
res23: scala.collection.mutable.Queue[String] = Queue(b, c)
2.5.4 Stack 堆
import scala.collection.mutable.Stack
val stack = new Stack[Int]
stack.push(1)
stack.push(2)
scala> stack.top
res8: Int = 2
scala> stack.pop
res10: Int = 2
scala> stack
res11: scala.collection.mutable.Stack[Int] = Stack(1)
2.5.5 Tuple 元组
tuple和list一样是不可变的, 不同是, list中的elem必须是同一种类型, 但tuple中可以包含不同类型的elem。
scala> val pair =(1,"pearl") //自动推断出类型为,Tuple2[Int, String]
pair: (Int, String) = (1,pearl)
scala> println(pair._1) //从1开始,而不是0,依照Haskell and ML的传统
1
scala> println(pair._2) //elem访问方式不同于list, 由于元组中elem类型不同
pearl
2.5.6 set 和map
scala> var jetSet = Set("Boeing", "Airbus")
jetSet: scala.collection.immutable.Set[String] = Set(Boeing, Airbus)
scala> jetSet += "Lear"
scala> println(jetSet.contains("Cessna"))
false
2.6 面向对象-OO
2.6.1类和对象
相对于Java定义比较简单, 默认public
class ChecksumAccumulator {
private var sum = 0
def add(b: Byte): Unit = {
sum += b
}
def checksum(): Int = {
return ~(sum & 0xFF) + 1
}
}
进一步简化, 去掉{ }和return, 默认将最后一次计算的值返回,还有赋值不能作为返回值。想返回哪个参数,可以在最后一句写上该变量的名字。
不写return是推荐的方式, 因为函数尽量不要有多个出口,
class ChecksumAccumulator {
private var sum = 0
def add(b: Byte): Unit = sum += b
def checksum(): Int = ~(sum & 0xFF) + 1
}
其实对于Unit(void), 即没有返回值, 对于FP而言, 就是该function只会产生side effect, 还有另外一种简写的方式
class ChecksumAccumulator {
private var sum = 0
def add(b: Byte) { sum += b } //对于Unit返回的, 另一种简写, 用{}来表示无返回, 所以前面的就不用写了
def checksum(): Int = ~(sum & 0xFF) + 1
}
实例化:
val acc = new ChecksumAccumulator
val csa = new ChecksumAccumulator
acc.sum = 3
初始化的时候,各个小函数都是0,当赋值或者操作的时候才会改变对应小函数值。
可变对象
可变Objects本身没啥好说的, 说说Scala getter和setter规则
every var that is a non-private member of some object implicitly defines a getter and a setter method with it.
The getter of a var x is just named “x”, while its setter is named “x_=”.
每个非私有的var都会隐含的自动定义getter和setter, 如下面的例子
class Time {
var hour = 12
var minute = 0
}
//等同于
class Time {
private[this] var h = 12
private[this] var m = 0
def hour: Int = h
def hour_=(x: Int) { h = x }
def minute: Int = m
def minute_=(x: Int) { m = x }
}
所以在Scala中比较有趣的是, 其实你可以不真正定义这个成员, 而只需要定义getter和setter就可以
如下面的例子, 并没有真正定义华氏温度, 而只是定义了getter和setter, 更简洁
class Thermometer {
var celsius: Float = _
def fahrenheit = celsius * 9 / 5 + 32
def fahrenheit_= (f: Float) {
celsius = (f 32)
* 5 / 9
}
override def toString = fahrenheit +"F/"+ celsius +"C"
}
优秀笔记:
http://qiujj.com/static/Scala-Handbook.htm (这个写的有点高深了)
http://www.cnblogs.com/fxjwind/p/3338829.html (这个我个人认为还较为基础)