Scala笔记
1、伴生对象
形如:
有一个类
class Test{
}
一个object和该类同名
object Test{
}
object Test的作用类似于静态类(工具类),其中的所有成员都是静态的,在object Test中可以直接访问class Test的成员;反之,class Test中要通过object Test来引用其成员例如使用Test.的方式
2、apply方法
class中的apply:是一个比较特殊的方法,通过这个class new 出来的对象,可以直接通过对象(),这样的方式来调用apply方法
object中的apply:比较常用,例如,通常使用数组时是下面这样的代码:
val arr = new Array[Int](3)
arr(0) = 1
arr(1) = 2
arr(2) = 3
但是,在scala中可以通过伴生对象的apply方法,我们可以很方便的构建类的对象,而不必知道和操作这个过程,如下:
val arr = Array(1,2,3)
通过伴生对象的apply方法,我们可以很方便的构建类的对象,而不必知道和操作这个过程,在apply方法中其实也是new出一个Array,并在其中做好了初始化操作
源代码如下:
/** Creates an array of `Int` objects */
// Subject to a compiler optimization in Cleanup, see above.
def apply(x: Int, xs: Int*): Array[Int] = {
val array = new Array[Int](xs.length + 1)
array(0) = x
var i = 1
for (x <- xs.iterator) { array(i) = x; i += 1 }
array
}
3、继承
没啥好说的,概念和其他语言一样,只是语法有点区别:
class Father(name:String){
...
}
//构造子类的时候会先构造父类,所以要将父类构造函数需要的参数给它
class Son(name:String,age:Int) extends Father(name){
...
}
4、trait特质
trait可以当接口来用,但是和其他语言的接口有些不同,trait里面竟然还可以有方法的定义!
那么这样和抽象类不是一样的了,干嘛还要trait?
Scala中也是单继承,也就是说一个类只能继承一个父类,需要有很多子类的特性的时候就可以通过继承多个trait来实现(可以把抽象类看成是一个统一的模板,trait则是其他七七八八的装饰,可加可减灵活性高)
在Scala中如果一个class,或者一个trait直接继承了另外一个trait,那么语法是一样的:
trait Test{
...
}
class Test1 extends Test{
...
}
当多重继承时,trait要使用with关键字,构造顺序从左往右,且不重复构造:
class Human{...}
trait ITeacher extends Human{...}
trait IBasketballPlayer extends Human{...}
class BasketballTeacher extends Human with ITeacher with IBasketballPlayer{...}
由于ITeacher和IBasketballPlayer都继承了Human,理论上在构造他们的时候会去构造他们的父类,也就是Human,但是由于Human之间在构造BasketballTeacher 的时候已经构造过了,所以这里不再重复构造
上面代码演示的是在定义class的时候混入trait
实际上也可以在new object的时候使用,这样可以在具体的场景中定义具体处理的对象(和定义class的时候直接混入的区别是,前者new出来的所有object都带有trait特质的,后者只作用在一个单一的object):
class Human{...}
trait ITeacher extends Human{...}
trait IBasketballPlayer extends Human{...}
val t1 = new Human with ITeacher with IBasketballPlayer{
//如果有抽象方法,在此重写
}
需要注意的是,混入的trait必须都继承自同一个父类
trait的AOP实现:
trait DoSomething {
def work
}
class Do extends DoSomething{
override def work: Unit = {
println("working!")
}
}
trait BeforeAndAfter extends DoSomething {
abstract override def work: Unit = {
println("init...")
super.work
println("destroy...")
}
}
object test {
def main(args: Array[String]) {
val worker = new Do with BeforeAndAfter
worker.work
}
}
上面的代码中,使用trait实现了一个简单的AOP编程实例
首先定义了一个trait DoSomething,里面有一个抽象方法work
class Do继承自DoSomething,并实现了具体的work方法
这时new一个Do的object之后调用work应该打印出一行记录
之后定义了另外一个trait BeforeAndAfter,也继承了DoSomething并重写work
在其重写的方法中将父trait的work方法放在初始化和销毁的操作之间,由于父trait的work方法是抽象的,此时又调用了这个抽象方法,所以这个重写的work仍然是抽象的,要加上abstract关键字
在new Do的object的时候混入这个trait就可以实现AOP,代码执行过程应该是这样的:
1、调用worker的work方法,由于混入了trait,所以实际调用的是这个trait里面的work
2、这个trait里面的work先进行了初始化操作,然后调用父trait的work,而这个方法的具体实现是在Do类中完成的,所以又调用了Do类中的具体work实现
3、work调用完成之后,进行销毁操作
5、包对象
包对象的定义及作用如下:
//这是一个包对象
package obejct Person{...}
//这是一个包的定义
package Person{
//此时,在这个包的作用于范围之内,可以直接访问包对象的成员
}
//使用这种语法的import意思是将scala包中的StringBuilder隐藏起来不使用(使用别的包的StringBuilder)
import scala.{StringBuilder => _}
6、文件操作
//读取本地文件
val localFile = Source.fromFile("file path")
val lines = localFile.getLines
//读取网络文件
val networkFile = Source.fromURL("file url")
//创建一个写入器
val writer = new PrintWriter(new File("file path"))
writer.println("something to write")
writer.close
//控制台读取
Console.readLine
7、正则表达式
//可以直接使用字符串.r的方式返回一个Regex对象
val regex1 = "[0-9]+".r
//三个引号表示表达式中的符号都是原意,而不是转义符(如\)
val regex2 = """\s+[0-9]+\s+""".r
//Regex对象可以直接调用findAllIn/findFirstIn等方法
regex1.findAllIn("1234 dqd qwdq")//该方法会返回全部匹配项
正则表达式和模式匹配相结合
val regex = """([0-9]+) ([a-z]+)""".r
val line = "123 abc"
line match {
//line如果符合regex1规则,会将其自动匹配成(num,str)格式
case regex(num, str) => println(num + ":" + str)
case _ => println("~~~~~~~~~`")
}
8、内部函数的定义
开发过程中,经常将各个子功能定义为一个个函数,在通过一个统一入口函数在调用这个子函数
但是这样存在一个问题,入口函数和子函数通常是定义在一起的,既然入口函数可以被外部调用,那么子函数同样也可以
这就不是我们想要的效果了,我们需要实现的是外部只能调用入口函数,而对于其他的子功能都是不可见的
这也就是高内聚低耦合的思想
在Scala中,函数是一等公民,意味着函数可以当做变量成员来使用
那么,在函数中可以定义变量,也就可以定义函数
这就是内部函数
def portal{
//内部函数定义
def action1{
}
def action2{
}
...
调用内部函数
action1
action2
}
9、闭包
简单的说,闭包就是指在一个函数中,能够访问另外一个函数的变量(必须要访问这个变量才能完成函数的工作),读取这个变量之后,这个函数就关闭执行,成为闭包
一个简单的闭包例子:
//这个函数中,要完成功能必须要知道more的值
def add(more:Int) = (x:Int) => x + more
//传入more的值为1,返回值其实还是一个函数x+1
val a = add(9)
val b = add(90)
//调用这个返回的函数,传入x=1
a(1)
b(10)
10、高阶函数
高阶函数简单的说就是 参数是函数 的函数
例如map、reduce等需要传入匿名函数的函数
具体操作就不详细说明了,因为高阶函数太多了= =使用方法都是差不多的
11、SAM转换
即Simple Abstract Method
在例如Java等语言中,代码通常是这样子写的:
jButton.addActionListener(new ActionListener{
override def actionPerformed(event:ActionEvent){
counter += 1
}
})
jButton的addActionListener需要一个格式为(event:ActionEvent)=>Unit的方法,上面的代码中直接new出了一个ActionListener并重写其方法传入
这叫做样本代码,即符合这个样本格式的方法才能使用
而很多时候,addActionListener这类的方法并不需要知道这么多信息,它只需要我们给它一个方法就行了,而不是一大堆的重新new对象,重写方法
在Scala中是这么解决问题的:
//这是一个隐式转换,其实起到的作用就是样本代码,名字任意,只要参数和返回值是符合固定格式的即可
implicit def makeAction(action:(ActionEvent) => Unit) = {
new ActionListener{
override def actionPerformed(event:ActionEvent){
action(event)
}
}
//有了上面的隐式转换,我们就可以很简洁的直接将方法当做参数传入
jButton.addActionListener((event:ActionEvent) => counter += 1)
此时,在这个界面的所有地方,都可以传入(ActionEvent) => Unit类型的函数给需要的函数了
总结SAM转换:
借助隐式转换,将样本代码省去
传入一个方法,自动扫描当前区域的隐式转换(定义了样本代码),如果转换成功就可以调用
12、Currying函数柯里化
函数的柯里化即将原来接受两个参数的函数变成新的接受一个参数的函数的过程。
简单的例子:
//多参数的函数
def add(x:Int,y:Int) = x + y
add(1,2)
//接受一个参数的函数
def add(x:Int) = (y:Int) => x + y
add(1)(2)
对于只接受一个参数的函数格式是不是挺熟悉的?在之前的闭包中使用的就是这种格式的函数
Scala中支持定义简介的柯里化函数
def add(x:Int)(y:Int) = x + y
柯里化的作用有很多,其中一个就是用来做类型推断
在一些场合,Scala编译器可以通过第一个参数的类型推荐第二个参数的类型以便进行一些操作
13、模式匹配
Scala的模式匹配类似于switch,但是更加灵活,格式如下:
val line = ...
line match{
case ... => ...
...
case _ => ...
}
每个case都有返回值(没有特殊指定的话)
case分支不用break
case后可以用常亮,变量,表达式,方法等
模式匹配还有很多其他的特殊的用法
例如:匹配type,array、list、tuple格式
def match_type(t:Any) = t match{
case i:Int => println("Int")
case s:String => println("String")
case m:Map[_,_] => m.foreach(println)
}
def match_array(arr:Any) = arr match{
//数组中只有一个0的匹配成功
case Array(0) => ...
//数组中有任意的两个数匹配成功
case Array(x,y) => ...
//数组中第一个为0,还有任意多个元素的匹配成功
case Array(0,_*) => ...
}
def match_list(lst:Any) = lst match{
//List中有一个0的匹配成功(Nil表示一个空的List,::运算符是从右至左运算的,表示空List加上一个0元素,即为只有一个0的List集合)
case 0 :: Nil => ...
//List中有任意的两个数匹配成功
case x :: y :: Nil => ...
//集合中第一个为0,还有任意多个元素的匹配成功(tail表示List中除了第一个元素外的所有元素)
case 0 :: tail => ...
}
14、样例类和样例对象
通常在系统开发中,我们需要自己定义一些数据结构来进行统一的消息传递,例如经常使用的Model
在Scala中,使用样例类和样例对象来完成这个功能
定义的格式如下:
case class Person(...)
case object Person1(...)
15、嵌套的样例类和模式匹配结合
abstract class Item{...}
case class Book(name:String,price:Int) extends Item
//BookList中嵌套了一个Book的样例类
case class BookList(num:Int,books:Book*) extends Item
def match_caseclass(item:Item) = item match{
//匹配BookList,用占位符_代替的属性都不关心,只要Book的name属性
case BookList(_,Book(name,_),_*) => ...
//可以使用别名@Book的方式进行模式匹配,后面的方法体中可以使用别名直接操作
case BookList(_,b1 @ Book(_,_),other @ _*) => ...
}
未完待续…