Scala学习笔记:Scala编程进阶
目录
Scala编程进阶
1.Scala函数式编程
(1)Scala的函数
函数是Scala的头等公民
值函数:可以在变量中存放函数
(2)Scala的匿名函数
匿名函数就是没有名字的函数
举例:定义一个匿名函数,作用是对某个数本身乘以3
定义一个数组Array(1,2,3) ,然后使用Array的函数map,作用是通过对这个数组的所有元素应用一个函数来构建一个新集合Array(3,6,9)
(3)Scala的闭包和柯里化
闭包:就是函数的嵌套,即在定义一个函数的时候,包含了另外一个函数的定义,在内函数中,可以访问外函数的变量
到目前为止,所有函数的例子仅参考了传入的参数,例如(x:Int) => x>0里,函数体x>0用到的唯一变量x被定义为函数参数。然而,当函数定义为(x:Int)=> x+more时,函数把“more”加入参考,但什么是more呢?从这个函数来看,more是个*变量,因为函数字面量没有给出其含义。相对的,变量x是一个绑定变量,因为它在函数的上下文中有明确意义,被定义为函数的唯一参数。如果尝试独立使用这个函数字面量,而范围内并没有任何more的定义,则编译器会报错。
另一方面,只要有一个叫做more的某种东西,同样的函数字面量将工作正常。依靠这个函数字面量在运行时创建的函数值(对象)被称为闭包。名称源自于通过“捕获”*变量的绑定,从而对函数字面量执行的“关闭”行动。
上面的例子,创建了一个函数makeIncreaser,x是这个函数的绑定变量,而more是个*变量,more的值取决于外界传入的值,从而成为一个闭包。当定义inc1函数时,每次调用makeIncreaser都会创建一个新闭包,每个闭包都会访问闭包创建时活跃的more变量,在这个例子里,more=1,当more被定下来之后,inc1的值也可以被定下来了。
下划线表示List里的任一数字,因此sum成为了一个闭包,因为累加的值并不固定
柯里化(Currying)
使函数柯里化的意思是,把具有多个参数的函数转换成一条函数链,在每个节点上都是单一的函数参数
首先我们定义一个函数:def add(x:Int,y:Int) = x+y
等价于这种柯里化写法:def add(x:Int)(y:Int) = x+y
那么是怎么实现的呢
add(x)(y)实际上是依次调用两个普通函数,第一次调用使用一个参数x,返回一个函数类型的值,第二次使用参数y调用这个函数类型的值,实质上最先演变成这样一个方法:def add(x:Int) = (y:Int) => x+y,闭包right?y是add函数的绑定变量,x是*变量,当定义一个val add1=add(1)的时候,调用add(1),捕获值1作为x的绑定的闭包被创建并返回,因此add1(2)=3
(4)Scala高阶函数
高阶函数即以函数作为参数的函数
第一个是普通函数,而第二个是高阶函数,someAction函数的参数是函数f,f属于匿名函数,也就是说,f(x)函数的参数是Double,返回值是Double,而这里的x=10,someAction所要返回的值,就是f(10)
举一个具体的例子
可以看到定义了一个普通函数mytest,传入两个Int,返回值也是Int,作用是将两个数相乘再加上100
myfunc1是属于模块化编程,调用mytest来计算传入的参数a和b,返回值同样设为Int
myfunc属于高阶函数,定义了一个匿名函数f(Int,Int),f的返回值是Int,而a和b是myfunc的参数,myfunc的返回值类型是Int,返回值是f(a,b)。用一个比较直白的例子就是sqrt(sqr(x)),对x求了乘方然后再开方,对于函数sqrt而言,他的参数就是函数sqr(x)。高阶函数可以使我们开发出来的程序非常简洁。
Scala常用高阶函数
定义一个集合:val numbers = List(1,2,3,4,5,6,7,8,9,10)
(1)map:对集合中的每个元素执行一个函数运算,返回一个包含相同数目的新的集合
举例:集合中所有乘以2
(2)foreach: 类似map,对集合中的每个元素执行一个函数运算,跟map的区别是没有返回值
(3)filter: 过滤,相当于SQL中where
举例:选择能够被2整除的元素
(4)zip: 把两个列表的元素合并到一个由元素对组成的新的列表中
(5)partition:分区,根据条件(断言)的返回值对列表进行分区
(6)find: 查找第一个满足条件的元素
(7) flatten:把一个嵌套的结构展开
(8) flatMap: 相当于flatten + map
2.Scala中的集合
集合分为可变集合和不可变集合
不可变集合必须要用val进行定义,否则里面的元素还是可以被修改的,当使用var进行对不可变集合的定义时,实际上是返回了一个新的集合,而老的集合并没有发生改变。
定义不可变集合需要导入scala.collection.immutable._,定义可变集合需要导入scala.collection.mutable._
对于immutable对象,它是线程安全的,在多线程下安全,没有竞态条件,而且由于不需要支持可变性, 可以尽量节省空间和时间的开销。所有的不可变集合实现都比可变集合更加有效的利用内存。
下面的图表显示scala.collection.immutable中的所有集合类。
下面的图表显示scala.collection.mutable中的所有集合类。
根据图以及源码可以很清晰地看出scala中的集合类可以分为三大类:
1.Seq,是一组有序的元素。
2.Set,是一组没有重复元素的集合。
3.Map,是一组k-v对。
(1)Map
Map在前面有提到过,就是<key,value>的键值对
package practice
object mydemo1 {
def main(args: Array[String]): Unit = {
//定义不可变集合(Map)
val math = scala.collection.immutable.Map("Tom"->80,"Mary"->90)
//可变集合
val chinese = scala.collection.mutable.Map("Tom"->80,"Mary"->90)
//集合的操作
//1.获取集合中的元素
println(chinese("Mary"))
//2.获取一个不存在中的元素
//println(chinese("Jone"))
//会抛出异常Exception in thread "main" java.util.NoSuchElementException: key not found: Jone
//所以在获取元素前应该要做一个条件判断
if (chinese.contains("Jone")){
println(chinese("Jone"))
}else{
println(-1)
}
//简写
chinese.getOrElse("Jone", -1)
//3.更新集合中的元素 该集合必须是一个可变集合或者用var定义的集合
chinese("Mary")=95
println(chinese("Mary"))
//4.添加新的元素
chinese +="Jone"->88
chinese.foreach(println)
//5.移除元素
chinese-="Mary"
chinese.foreach(println)
}
}
(2)List
List同样分immutable和mutable,具体的方法可以查阅API,这里只写了部分
List属于seq,所以是有序的,可重复的
immutable List的定义和一些基本操作
package practice
object mydemo02 {
//不可变列表
//定义一个字符串列表
val nameList = List("Tom","Mary","Mike")
//整数列表
val intList = List(1,2,3,4,5)
//定义空列表
//那么List()呢?一个空的List,他是什么类型呢?肯定不是List[Int],因为里面并没有Int,也不是List[Any],因为里面啥都没有,没有理由推导出任何一个其他类型,于是这个List()只能是List[Nothing]。
val nullList = List()
//另一个写法就是泛型了
val nullList1:List[Nothing] = List()
//二维列表
val dim:List[List[Int]] = List(List(1,2,3),List(4,5,6))
//或者使用List.tabulate()方法来创建,,第一个参数是行和列的长度,第二个参数是创建List所使用的函数
val mul = List.tabulate(4, 5)(_*_)
/**
* 这里使用的_*_是一个语法糖,等价于下面的写法
* val mul = List.tabulate(4, 5)((row: Int, col: Int) => row * col)
* 计算过程row->0-3 col ->0-4
* 0x0 0x1 0x2 0x3 0x4
* 1x0 1x1 1x2 1x3 1x4
...
*/
def main(args: Array[String]): Unit = {
//常用操作
//输出第一个元素
println(nameList.head)
//输出除去第一个元素外的剩下的List
println(nameList.tail)
//判断List是否为空
println(nameList.isEmpty)
}
}
mutable的定义和一些基本操作
package practice
object mydemo3 {
def main(args: Array[String]): Unit = {
//可变列表,注意在2.11.8版本下LinkedList已经被废弃了
val myList = scala.collection.mutable.LinkedList(1,2,3,4,5)
var list1 = List(1,2,3,4)
var list2 = List(1,2,3,4)
val listA: List[String] = List("A", "B", "C", "D");
val listB= List("A", "B", "C", "D");
//实现:将每个元素乘以2
//定义一个指针
var cur = myList
while(cur !=Nil){
cur.elem*= 2
//指针移到下一个元素
cur=cur.next
}
println(myList)
//把一个新元素组合到list的最前面
list1 =5 :: list1
println(list1)
//如果将两个list用中缀操作符连接,输出结果如下,因为是将list1这个整体看成一个元素,然后组合到list2的最前面了
var list3=list1 :: list2
println(list3)
//List(List(5, 1, 2, 3, 4), 1, 2, 3, 4)
//所以如果要连接两个list的元素,要用这个:::操作符,或者++
var list4=list1 ::: list2
println(list4)
//List(5, 1, 2, 3, 4, 1, 2, 3, 4)
}
}
(3)序列
序列分两种:Vector和Range
Vector:支持快速的查找和更新
Range:表示是一个整数序列,一段整数的范围
两者均属于seq,即有序也可重复
package practice
object demox {
//定义Vector
val v = Vector(1,2,3,4,5,6)
//定义Range,表示[0,5),左闭右开区间
var r1 = Range(0,5)
//使用until来定义Range,左闭右开区间
var r2 = (0 until 5)
//使用to来定义Range,闭区间
var r3 = (0 to 4)
//指定步长的方式定义Range,Range里的是1-10里的单数
var r4 = Range(1,10,2)
def main(args: Array[String]): Unit = {
//找出符合要求的第一个元素
println(v.find(_>3)) //输出Some(4)
//将第三个元素变成100,注意,这里用了val,而Vector又是不可变集合,所以直接输出v结果是不会变化的
v.updated(2,100)
println(v.updated(2,100))
//输出r1,r2
println(r1)
println(r2)
println(r3)
println(r4)
//Range相加:两个Range按顺序首尾相接
println((0 to 9) ++ ('A' to 'Z'))
}
}
(4)Set
Set是不重复元素的集合,本身是无序的,常用的有HashSet和TreeSet。
HashSet有以下特点:
使用元素的hashCode值来决定元素的存储位置
不能保证元素的排列顺序,顺序有可能发生变化
不是Synchronized的,因此线程不安全
集合元素可以是null,但只能放入一个null
TreeSet有以下特点:
TreeSet是SortedSet接口的唯一实现类,TreeSet可以确保集合元素处于排序状态。
TreeSet支持两种排序方式,自然排序 和定制排序,其中自然排序为默认的排序方式。
LinkedHashSet有以下特点:
LinkedHashSet集合同样是根据元素的hashCode值来决定元素的存储位置,但是它同时使用链表维护元素的次序。
这样使得元素看起 来像是以插入顺 序保存的,也就是说,当遍历该集合时候,
LinkedHashSet将会以元素的添加顺序访问集合的元素。
LinkedHashSet在迭代访问Set中的全部元素时,性能比HashSet好,但是插入时性能稍微逊色于HashSet。
package practice
import scala.collection.mutable.SortedSet
import scala.collection.mutable.TreeSet
import scala.collection.mutable.Set
object DemoSet {
var s1 = Set(2,0,1)
//指定为Int类型的空Set
var sx:Set[Int] =Set()
//用empty来定义一个空集合
var st = Set.empty()
def main(args: Array[String]): Unit = {
println(s1)
//添加一个重复的元素,Set不会有任何改版
s1 + 1
//添加一个不重复的元素
s1 + 100
//空集合赋值需要这么写
sx = sx + 1
//创建可以排序的set,SortedSet其实是一个接口,Treeset是其唯一的实现类
var s2 = SortedSet(1,2,3,10,4,5)
var s3 = TreeSet(2,1,3,10,4,5)
var set1=Set(1,2,3,4,5,6)
var set2=Set(5,6,7,8,9,10)
//并集,当然集合操作得出的结果也是无序的
println(set1 union set2)
//交集
println(set1 intersect set2)
//差集
println(set1 diff set2)
//判断一个set是否为另一个set的子集
println(set1.subsetOf(set2))
}
}
(5)Scala中的模式匹配
给定一个子串,要求在某个字符串中找出与该子串相同的所有子串,这就是模式匹配。
Scala有一个强大的模式匹配机制,可以应用在switch语句,类型检查等
Scala还提供了样本类(case class),对模式匹配进行了优化
package practice
object demomp {
def main(args: Array[String]): Unit = {
//普通模式匹配,类似switch
var sign = 0
var ch1 = '-'
ch1 match{
case '+' => sign = 1
case '-' => sign = -1
case _ => sign = 0
}
println(sign)
//Scala的守卫:匹配某种类型的所有值
var ch2='1'
var digit:Int = -1
ch2 match{
case '+' => println("这是一个加号")
case '-' => println("这是一个减号")
//使用if守卫,如果ch2是数字字符,就给digit返回ch2数字字符的数值
case _ if Character.isDigit(ch2) => digit = Character.digit(ch2, 10)
case '1' | '2' =>println("1 or 2")
case _ => println("其他")
}
println("digit:"+digit);
//模式匹配中使用变量
var str3="Hello World"
var ch='o'
str3(7) match{
case '+' => println("这是一个加号")
case '-' => println("这是一个减号")
case ch => println("这个字符是:"+ch)
}
//类型匹配,其中变量x,s的值就是v4的值
var v4:Any = 100
v4 match{
case x:Int => println("Int")
case s:String => println("String")
case _ => println("others")
}
//数组匹配
var myArray = Array(1,2,3)
myArray match{
case Array() => println("这是一个空数组")
case Array(0) => println("这个数组是Array(0)")
case Array(x,y) => println("这个数组包含两个元素,和是"+(x+y))
case Array(x,y,z) => println("这个数组包含三个元素,和是"+(x+y+z))
case Array(x,_*) => println("这是一个数组")
}
//List匹配
var myList = List(1,2,3)
myList match{
case List(0) => println("这个List是List(0)")
case List(x,y) => println("这个List包含两个元素,和是"+(x+y))
case List(x,y,z) => println("这个List包含三个元素,和是"+(x+y+z))
case List(x,_*) => println("这是一个List")
}
}
}
(6)样本类(CaseClass)
简单的来说,Scala的样本类激素在普通的类定义前加case这个关键字,然后你可以对这些类来模式匹配
CaseClass带来的最大的好处是它们支持模式识别
package practice
object demoCaseClass {
class Fruit
class Apple(name:String) extends Fruit
class Banana(name:String) extends Fruit
class Vehicle
case class Car(name:String) extends Vehicle
case class Bike(name:String) extends Vehicle
def main(args: Array[String]): Unit = {
var aApple:Fruit = new Apple("apple")
var bBanana:Fruit = new Banana("banana")
//使用isInstanceOf来判断某个对象是否是某一个类的对象
println(aApple.isInstanceOf[Fruit])
println(aApple.isInstanceOf[Apple])
println(bBanana.isInstanceOf[Apple])
var aCar:Vehicle = new Car("这是一辆汽车")
aCar match{
case Car(name) => println("这是一个汽车")
case Bike(name) => println("这是一个自行车")
case _ => println("不太确定")
}
}
}
3.Scala高级特性
(1)泛型类
和Java相似,类和特质可以带类型参数,在Scala中,使用方括号来定义类型参数
这里定义了一个普通的类,但是content只能是Int类型,如果希望保存任意的类型,则需要使用泛型类
这里的T就是泛型类,可以代表任意类型。下划线表示content可以是任意值
测试泛型类,可以看到这里能传入Int跟String类型
(2)泛型函数
函数和方法也可以带类型参数。和泛型类一样,我们需要把类型参数放在方法名之后
用例子说明
创建一个函数,用以创建一个Int类型的数组
创建一个函数,用以创建一个String类型的数组
创建一个泛型函数,使其能创建任意类型的数组,在创建泛型函数的时候,我们需要导入import.reflect.ClassTag,ClassTag会帮我们存储T的信息,Java中的泛型基本上都是在编译器这个层次来实现的。在生成的Java字节码中是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数,会在编译器在编译的时候去掉。这个过程就称为类型擦除。泛型擦除是为了兼容jdk1.5之前的jvm,在这之前是不支持泛型的。
进行测试
(3)泛型的上界和下界
类型的上界(Upper Bounds)与下界(Lower Bounds),是用来定义类型变量的范围。
类型上界:S <:T,也就是S必须是类型T本身或它的子类
类型下界:S >:T,也就是S必须是类型T本身或它的父类
用一些例子说明
基本类型:Int x:100<=x<=200,这里x的下界就是100,上界就是200
类型变量:A ->B->C->D,依次是后面的父类,定义变量D<: y<: B,这个时候变量y的类型只能是B、C、D
一个复杂一点的例子(上界)
(4)视图界定(View bounds)
视界比上界使用的范围更广,除了所有的子类型,还允许隐式转换的类型,用<%表示。
尽量使用视界来取代泛型的上界,因为使用的范围更加广泛。
示例
定义一个函数,将两个字符串连接起来。
可以发现传入Int参数的时候,就会报错,因为类型不对,这里设定的上界是String
然后我们使用视界,在这个例子里,可以接收String和String的子类,以及可以转换为String类型的类型
但是实际运行的时候还是会报错
根据错误信息可以知道,没有定义隐式转换的规则
所以我们定义一下隐式转换规则,这里用的是Int类型的toString方法
定义好规则后,Scala会自动调用
再次运行,成功
(5)协变和逆变
Scala的类或特征的泛型定义中,如果在类型参数前面加入+符号,就可以使类或特征变成协变了
协变是指泛型变量的值可以是其本身的类或者其子类的类型
逆变就是协变反过来了,是指泛型变量的值可以是其本身或者其父类
在类或特征的定义中,在类型参数之前加上一个-符号,就可以定义逆变泛型类和特征了
(6)隐式转换函数
隐式转换函数指的是以implicit关键字声明的带有单个参数的函数
前面讲视界的时候,定义转换规则用的就是隐式转换函数
再举一个例子
将Fruit对象转换成Monkey对象
(7)隐式参数
使用implicit声明的函数参数叫作隐式参数,我们也可以使用隐式参数实现隐式的转换
(8)隐式类
隐式类就是对类增加implicit修饰符,其作用主要是对类的功能加强
隐式类执行的过程:
Step1:当执行1.add(2)时,scala的编译器不会马上报错,它会在当前域中查找有没有implicit修饰的,同时是将Int作为参数的构造器,并且具有add方法的类,通过查找,找到了Calc类
Step2:利用隐式类Calc来执行add方法