本文内容适用于有java基础的开发人员,重点讲解scala与java的不同之处,未提到的用法和java语言基本相同。
1.Scala简介
scala是一门多范式的编程语言
1.1 Scla特征
面向对象特性
Scala是一种纯面向对象的语言,每个值都是对象。对象的数据类型以及行为由类和特质描述
函数式编程
Scala也是一种函数式语言,其函数也能当成值来使用。Scala提供了轻量级的语法用以定义匿名函数,支持高阶函数,允许嵌套多层函数,并支持柯里化。Scala的case class及其内置的模式匹配相当于函数式编程语言中常用的代数类型。
静态类型
Scala具备类型系统,支持以下类型:
泛型类、协变和逆变、标注、类型参数的上下限约束、把类别和抽象类型作为对象成员、复合类型、引用自己时显式指定类型、视图、多态方法
扩展性
Scala的设计秉承一项事实,即在实践中,某个领域特定的应用程序开发往往需要特定于该领域的语言扩展。Scala提供了许多独特的语言机制,可以以库的形式轻易无缝添加新的语言结构:
任何方法可用作前缀或后缀操作符
可以根据预期类型自动构造闭包。
并发性
Scala使用Actor作为其并发模型,Actor是类似线程的实体,通过邮箱发收消息。Actor可以复用线程,因此可以在程序中可以使用数百万个Actor,而线程只能创建数千个。在2.10之后的版本中,使用Akka作为其默认Actor实现。
1.2 Scala如何工作
编译成 Java 字节码
可在任何标准 JVM 上运行
- 甚至是一些不规范的JVM上,如 Dalvik
- Scala 编译器是 Java 编译器的作者写的
java编译命令:javac xxx.java 运行命令:java xxx
scala编译命令:scalac xxx.scala 运行命令:scala xxx
1.3 安装参考:http://www.runoob.com/scala/scala-install.html
注:版本问题
Scala 2.12 需要Java 8,Scala 2.11.x可以用Java6或Java7,具体参考:[2.12-roadmap]
(https://www.scala-lang.org/news/2.12-roadmap/)
Sbt 1.x 需要Java8或以上,Sbt 0.13需要Java6或以上
2.基本语法
本章重点讲解scala与java的不同之处,没提到的基本和Java相同
2.1基本类型
数据类型 | 描述 |
---|---|
Byte | 8位有符号补码整数。数值区间为 -128 到 127 |
Short | 16位有符号补码整数。数值区间为 -32768 到 32767 |
Int | 32位有符号补码整数。数值区间为 -2147483648 到 2147483647 |
Long | 64位有符号补码整数。数值区间为 -9223372036854775808 到 9223372036854775807 |
Float | 32位IEEE754单精度浮点数 |
Double | 64位IEEE754单精度浮点数 |
Char | 16位无符号Unicode字符, 区间值为 U+0000 到 U+FFFF |
String | 字符序列 |
Boolean | true或false |
Unit | 表示无值,和其他语言中void等同。用作不返回任何结果的方法的结果类型。Unit只有一个实例值,写成()。 |
Null | null 或空引用 |
Nothing | Nothing类型在Scala的类层级的最低端;它是任何其他类型的子类型 |
Any | Any是所有其他类的超类 |
AnyRef | AnyRef类是Scala里所有引用类(reference class)的基类 |
2.2 多行字符串的表示方法
多行字符串用三个双引号来表示分隔符,格式为:”“” … “”“。
实例如下:
val foo = """菜鸟教程
www.runoob.com
www.w3cschool.cc
www.runnoob.com
以上三个地址都能访问"""
2.3 Null值
空值是 scala.Null 类型。
Scala.Null和scala.Nothing是用统一的方式处理Scala面向对象类型系统的某些”边界情况”的特殊类型。
Null类是null引用对象的类型,它是每个引用类(继承自AnyRef的类)的子类。Null不兼容值类型。
2.4 元组类型
元组定义:val tuple =(“Hello”,”china”,1)
访问时通过变量名._N的形式,如: tuple._1, tuple._2, tuple._3
2.5 变量声明
- 变量: 在程序运行过程中其值可能发生改变的量叫做变量。如:时间,年龄。
常量 在程序运行过程中其值不会发生变化的量叫做常量。如:数值 3,字符’A’。
在 Scala 中,使用关键词 “var” 声明变量,使用关键词 “val” 声明常量
声明变量实例如下:
var myVar : String = "Foo" var myVar : String = "Too"
声明常量实例如下:
val myVal : String = "Foo"
变量声明一定需要初始值,否则会报错,String类型如果用占位符“_”初始化变量为null,数值类型被初始化为0,Char类型被初始化为?。
lazy修饰的变量在定义时不被赋值,在使用此变量时才会赋值。
lazy val v1 = "test"
2.6 访问修饰符
Scala 访问修饰符基本和Java的一样,分别有:private,protected,public。
如果没有指定访问修饰符符,默认情况下,Scala 对象的访问级别都是 public。
Scala 中的 private 限定符,比 Java 更严格,在嵌套类情况下,外层类甚至不能访问被嵌套类的私有成员
作用域保护
Scala中,访问修饰符可以通过使用限定词强调。格式为:
private[x] 或 protected[x]
实例如下:
class People{
private[People] var name = "张三"
def this(name:String){
this()
this.name = name
}
}
object People{
val people = new People()
people.name = "李四"
val name = people.name
}
def main(args: Array[String]) {
println(People.name)
}
但如果private[x]为this的话,只能在People伴生类中访问,伴生对象中不能访问。
2.7 运算符
Scala的内置运算符:算术运算符、关系运算符、逻辑运算符、位运算符、赋值运算符
与Java没区别,不再描述。
2.8 程序控制结构
包括 if…else、while、do…while与Java没区别。
注意一下几点
1. if(表达语句)中的表达语句写法:
for(i <- 表达式){
//执行循环语句
}
实例如下:
for(i <- 1 to 5){
println(i)
}
其中<-被称为生成器(generator),1 to 5 相当于1.to(5),此时Int类型隐式转换为scala.runtime.RichInt类型,然后调用RichInt中的to方法。
2. Scala语言中没有continue和break关键字
可用通过两种途径实现continue或break的功能:一是定义Boolean类型的变量,在for或while循环中进行条件判断;二是在程序中引入scala.util.control.Breaks类。
break实例如下:
object BreakableTest extends App{
val list1 = List(1,2,3,4,5)
//breakable方法与break方法组合使用实现break功能
//将整个循环放置于breakable方法中,然后需要跳出循环的时候则使用break方法,则跳出整个循环
breakable{
println("break功能展示:——————————————")
for(i <- list1){
if(i==4) break else println(i)
}
}
}
continue实例如下:
object BreakableTest extends App{
val list2 = List(11,22,33,44,55)
//这里与上面的区别是将if-else语句放置在breakable方法内部,而没有将整个循环结构放置在
//方法内部
//这样做可以实现结束本次执行而不是整个循环结束,从而实现continue功能
for(j <- list2){
breakable{
if(j==44) break else println(j)
}
}
}
3. 有过滤条件的for循环
for(x <- 表达式 if条件判断1;if条件判断2){
......
}
4. yield关键字
yield缓存每次循环生成的值,最后一起生成集合返回。
var x = for(i<- 1 to 5) yield i/2
输出:Vector(0,1,1,2,2)
3.集合
Scala 集合分为可变的和不可变的集合
可变集合可以在适当的地方被更新或扩展。这意味着你可以修改,添加,移除一个集合的元素。包名:scala.collection.mutable
而不可变集合类,相比之下,永远不会改变。不过,你仍然可以模拟添加,移除或更新操作。但是这些操作将在每一种情况下都返回一个新的集合,同时使原来的集合不发生改变。包名:scala.collection.immutable
3.1 Array(数组)
数组声明格式:
var arr:Array[String] = new Array[String](3)
或
var arr = new Array[String](3)
以上语法中,z 声明一个字符串类型的数组,数组长度为 3 ,可存储 3 个元素。我们可以为每个元素设置值,并通过索引来访问每个元素,如下所示:
arr(0) = "Runoob"; arr(1) = "Baidu"; arr(4/2) = "Google"
多维数组:
var myMatrix = ofDim[Int](3,3)
合并数组:
使用 concat() 方法来合并两个数组,concat() 方法中接受多个数组参数。
var myList1 = Array(1.9, 2.9, 3.4, 3.5)
var myList2 = Array(8.9, 7.9, 0.4, 1.5)
var myList3 = concat( myList1, myList2)
创建区间数组:
使用range()方法,可接收2个或3个参数,range() 方法最后一个参数为步长,默认为 1。
var myList1 = range(10, 20, 2)
var myList2 = range(10,20)
myList1: 10 12 14 16 18
myList2: 10 11 12 13 14 15 16 17 18 19
3.1 List(列表)
List的特征是其元素以线性方式存储,集合中可以存放重复对象。
常用实例参考:http://www.runoob.com/scala/scala-lists.html
3.2 Set(集合)
Set是最简单的一种集合。集合中的对象不按特定的方式排序,并且没有重复对象。
常用实例参考:http://www.runoob.com/scala/scala-sets.html
3.3 Map(映射)
Map 是一种把键对象和值对象映射的集合,它的每一个元素都包含一对键对象和值对象。
常见实例参考:http://www.runoob.com/scala/scala-maps.html
3.4 Option
Option[T] 表示有可能包含值的容器,也可能不包含值。
常见实例参考:http://www.runoob.com/scala/scala-options.html
3.5 Iterator(迭代器)
迭代器不是一个容器,更确切的说是逐一访问容器内元素的方法。
常见实例参考:http://www.runoob.com/scala/scala-iterators.html
4.函数
4.1 标准函数定义
def gcd(x:Int,y:Int):Int={
if(x%y==0)
y
else
gcd(y,x%y)
}
1.def:关键字声明函数
2.gcd:函数名
3.(x:Int,y:Int):函数参数,并指明参数类型
4.参数括号后面的类型Int:函数返回值类型
5.{……}:函数体,函数体中最后一条直线语句为函数返回值,可省略return关键字
注:scala具有类型推导功能,4中的函数返回类型可省略,但有两个限制:一是如果需要return关键字指定返回值,则必须显示地指定函数返回值类型。二是如果函数中存在递归调用,则必须显示地指定函数返回值类型。
4.2 值函数
在Scala中,函数也是对象,可以像变量一样赋值,把这种函数称为函数字面量(function literal)或值函数。
val sum=(x:Int,y:Int)=>{
println(x+y)
x+y
}
1.sum:变量名
2.(x:Int,y:Int):函数参数
3.=>函数映射符
4.{……}:函数体,最后一条语句为函数返回值。
函数体多行放在{}中,如果只有一行,可以省略,如:val sum=(x:Int,y:Int)=>x+y
4.3 值函数的简化
值函数最常用的场景是作为高阶函数的输入,定义如下整数数组:
val arrInt=Array(1,2,3,4)
现在希望所有元素加1,可以使用Array提供的map方法
def map[B](f:(A) => B):Array[B]
B:返回数据元素的类型
f:应用于每个元素的方法
returns:将方法应用于数组的每个元素,并返回新的数组
定义map的参数function:
val increment = (x:Int) => x+1
将increment变量作为函数的参数使用
arrInt.map(increment)
如果值函数只使用一次,则常常直接作为函数参数
arrInt.map((x:Int) => x+1)
值函数省略参数类型,参数类型通过类型推到得到:
arrInt.map((x) => x+1)
值函数参数只有一个时,可以省略括号:
arrInt.map(x => x+1)
值函数参数在=>只出现一次是,可以用占位符“_”对函数进行简化
arrInt.map(_+1)
4.4 高级函数
4.4.1 高级函数定义
像上面的map函数,map函数的参数类型为函数类型,则map为高级函数。
如:
该函数输入类型为函数类型(Double)=>Double
def higherOrderFunction(f:(Double)=>Double)=f(100)
def sqrt(x:Double)=Math.sqrt(x)
higherOrderFunction(sqrt)
高级函数除能将函数作为函数参数外,还能将函数作为返回值
def higherOrderFunction(factor:Int):Double=>Double={
(x:Double)=>factor*x
}
该函数输入参数为Int类型,返回值为函数类型Double=>Double,返回的函数类型输入参数为Double,返回类型为Double,代码(x:Double)=>factor*x 为该函数的返回值。
根据函数类型推导,返回函数类型为Double=>Double
def higherOrderFunction(factor:Int)=(x:Double)=>factor*x
生成新的函数
val multiply=higherOrderFunction(100)
multipy(10)
返回:1000.0
4.4.2 高级函数的应用
map函数
定义:def map[B](f:(A) => B):Array[B]
用途:将函数f应用于数组的所有元素,并返回一个新的数组Array[B]。函数f输入类型为A,返回值类型为B。
flatMap函数
定义:def flatMap[B](f:(A) => GenTraversableOnce[B]):Array[B]
用途:将函数f作用于集合中的所有元素,各元素得到相应的集合GenTraversableOnce[B],然后再将其扁平化返回生成新的集合。
map和flatMap区别:
val ListInt = List(1,2,3) listInt.map(x => x match{ case 1 => List(1) case _ => List(x*2,x*3,x*4) }) 返回:List(List(1),List(2,6,8),List(6,9,12)) listInt.flatMap(x => x match{ case 1 => List(1) case _ => List(x*2,x*3,x*4) }) 返回:List(1,2,6,8,6,9,12)
filter函数
定义:def filter(p:(T) => Boolean):Array[T]
用途:返回所有满足条件p的元素集合。
reduce函数
定义:def reduce[A1 >: A](op:(A1,A1) => A1):A1
用途:使用函数op作用于集合至上,返回的结果类型为A1。op为特定的联合二元算子,A1为A的超类。
Array(1,2,4,3,5).reduce((x:Int,y:Int)=>{println(x,y);x+y}) (1,2) (3,4) (7,3) (10,5) Int = 15 //简化的写法 Array(1,2,4,3,5).reduce(_+_) Int = 15
fold函数
定义:def fold[A1 >: A](z:A1)(op:(A1,A1) => A1):A1
用途:使用联合二元操作算子对集合进行fold操作,z为给定的初始值。
Array(1,2,4,3,5).fold(0)((x:Int,y:Int) => {println(x,y);x+y}) (0,1) (1,2) (3,4) (7,3) (10,5) Int = 15
4.5 闭包
闭包是一个函数,返回值依赖于声明在函数外部的一个或多个变量。
object Test {
def main(args: Array[String]) {
println( "muliplier(1) value = " + multiplier(1) )
println( "muliplier(2) value = " + multiplier(2) )
}
var factor = 3
val multiplier = (i:Int) => i * factor
}
自由变量factor在运行过程中会不断变化,处于一种开放状态,而在函数执行过程中,变量factor被确定下来,可以认为在运行时它暂时褚宜封闭状态,这种存在从开放到封闭过程的函数被称为闭包。
4.6 函数柯里化
柯里化(Currying)指的是将原来接受两个参数的函数变成新的接受一个参数的函数的过程。新的函数返回一个以原有第二个参数为参数的函数。
首先我们定义一个函数:
def add(x:Int,y:Int)=x+y
那么我们应用的时候,应该是这样用:add(1,2)
现在我们把这个函数变一下形:
def add(x:Int)(y:Int) = x + y
那么我们应用的时候,应该是这样用:add(1)(2),最后结果都一样是3,这种方式(过程)就叫柯里化。
4.7 部分应用函数(偏应用函数)
上面的柯里化函数不能只调用第一个参数返回val part1 = add(1)一个函数对象,然后再调用part1(2)。如果想通过这种方式调用,可以使用部分应用函数。
val part = add(1) _
part(2)
add(1)_生成一个部分应用函数,函数类型为Int => Int,即返回的函数要求输入参数类型为Int,返回类型为Int。
两个参数都不指定对应的部分应用函数,生成的函数类型 Int=>(Int => Int)
val part2 = add _
part2(1)(2)
不知柯里化函数有部分应用函数,普通函数也可以
def product(x:Int,y:Int,z:Int) = x+y+z
一个参数的部分应用函数
def product1 = product(_:Int,2,3)
product1(2)
Int = 7
两个参数的部分应用函数
def product2 = product(_:Int,_:Int,3)
product2(2,2)
Int = 7
三个参数的部分应用函数
def product3 = product(_:Int,_:Int,_:Int)
product3(2,2,3)
Int = 7
三个参数的部分应用函数,等价于product(_:Int,_:Int,_:Int)
def product3 = product_
product3(2,2,3)
Int = 7
4.8 偏函数
偏函数是只对函数定义域的一个子集进行定义的函数。
对比Function和Partial Function,更学术味的解释如下:
对给定的输入参数类型,函数可接受该类型的任何值。换句话说,一个(Int) => String 的函数可以接收任意Int值,并返回一个字符串。
对给定的输入参数类型,偏函数只能接受该类型的某些特定的值。一个定义为(Int) => String 的偏函数可能不能接受所有Int值为输入。
scala中偏函数的定义:trait PartialFunction[-A, +B] extends (A => B) ,泛型参数A为偏函数的输入参数类型,泛型B为偏函数的返回结果类型,由于偏函数可以只处理参数定义域的子集,对于子集之外的参数会抛出异常,这一特点使得偏函数与Scala模式匹配能够完美结合。
偏函数内部有一些方法,比如isDefinedAt、OrElse、 andThen、applyOrElse等等
isDefinedAt: 这个函数的作用是判断传入来的参数是否在这个偏函数所处理的范围内。
scala> val pf:PartialFunction[Int,String] = {
| case 1=>"One"
| case 2=>"Two"
| case 3=>"Three"
| case _=>"Other"
| }
pf: PartialFunction[Int,String] = <function1>
scala> pf(1)
res0: String = One
scala> pf(2)
res1: String = Two
scala> pf(3)
res2: String = Three
scala> pf(4)
res3: String = Other
scala> pf.isDefinedAt(1)
res4: Boolean = true
scala> pf.isDefinedAt(2)
res5: Boolean = true
scala> pf.isDefinedAt("1")
<console>:13: error: type mismatch;
found : String("1")
required: Int
pf.isDefinedAt("1")
^
scala> pf.isDefinedAt(100)
res7: Boolean = true
再如:
scala> val anotherPF:PartialFunction[Int,String] = {
| case 1=>"One"
| case 2=>"Two"
| case 3=>"Three"
| }
anotherPF: PartialFunction[Int,String] = <function1>
scala> anotherPF.isDefinedAt(1)
res8: Boolean = true
scala> anotherPF.isDefinedAt(2)
res9: Boolean = true
scala> anotherPF.isDefinedAt(3)
res10: Boolean = true
scala> anotherPF.isDefinedAt(4)
res11: Boolean = false
orElse: 将多个偏函数组合起来使用,效果类似case语句。
scala> val onePF:PartialFunction[Int,String] = {
| case 1=>"One"
| }
onePF: PartialFunction[Int,String] = <function1>
scala> val twoPF:PartialFunction[Int,String] = {
| case 2=>"Two"
| }
twoPF: PartialFunction[Int,String] = <function1>
scala> val threePF:PartialFunction[Int,String] = {
| case 3=>"Three"
| }
threePF: PartialFunction[Int,String] = <function1>
scala> val otherPF:PartialFunction[Int,String] = {
| case _=>"Other"
| }
otherPF: PartialFunction[Int,String] = <function1>
scala> val newPF = onePF orElse twoPF orElse threePF orElse otherPF
newPF: PartialFunction[Int,String] = <function1>
scala> newPF(1)
res0: String = One
scala> newPF(2)
res1: String = Two
scala> newPF(3)
res2: String = Three
scala> newPF(4)
res3: String = Other
andThen: 相当于方法的连续调用,比如g(f(x))。
scala> val pf1:PartialFunction[Int,String] = {
| case i if i == 1 => "One"
| }
pf1: PartialFunction[Int,String] = <function1>
scala> val pf2:PartialFunction[String,String] = {
| case str if str eq "One" => "The num is 1"
| }
pf2: PartialFunction[String,String] = <function1>
scala> val num = pf1 andThen pf2
num: PartialFunction[Int,String] = <function1>
scala> num(1)
res4: String = The num is 1
pf1的结果返回类型必须和pf2的参数传入类型必须一致,否则会报错。
applyOrElse: 它接收2个参数,第一个是调用的参数,第二个是个回调函数。如果第一个调用的参数匹配,返回匹配的值,否则调用回调函数。
scala> onePF.applyOrElse(1,{num:Int=>"two"})
res5: String = One
scala> onePF.applyOrElse(2,{num:Int=>"two"})
res6: String = two
在这个例子中,第一次onePF匹配了1成功则返回的是”One”字符串。第二次onePF匹配2失败则触发回调函数,返回的是”Two”字符串。
5 面向对象编程
5.1 类
类的定义:
class Person{
//声明一个成员变量必须初始化
var name:String = null
}
创建对象:
val p = new Person()
构造函数无参数,可以省略()
val p1 = new Person
成员变量访问
setter方法
p.name_ = ("John")
p.name= "John"
实际上调用的是p.name_ = ("John"),因为name是私有方法
getter方法
p.name
如果把成员变量name声明成val之后,则此类只能生成public的getter方法,没有setter方法
如果想生成与Java风格相同的getter方法和setter方法,则可以在变量前面加上@BeanProperty注解
import scala.beans.BeanProperty
class Person{
@BeanProperty var name:String = null
}
5.2 单例对象
在 Scala 中,是没有 static 这个东西的,但是它也为我们提供了单例模式的实现方法,那就是使用关键字 object。
Scala 中使用单例模式时,除了定义的类之外,还要定义一个同名的 object 对象,它和类的区别是,object对象不能带参数。
当单例对象与某个类共享同一个名称时,他被称作是这个类的伴生对象:companion object。你必须在同一个源文件里定义类和它的伴生对象。类被称为是这个单例对象的伴生类:companion class。类和它的伴生对象可以互相访问其私有成员。
单例对象:
import java.io._
class Point(val xc: Int, val yc: Int) {
var x: Int = xc
var y: Int = yc
def move(dx: Int, dy: Int) {
x = x + dx
y = y + dy
}
}
object Test {
def main(args: Array[String]) {
val point = new Point(10, 20)
printPoint
def printPoint{
println ("x 的坐标点 : " + point.x);
println ("y 的坐标点 : " + point.y);
}
}
}
伴生类和伴生对象:
// 私有构造方法
class Marker private(val color:String) {
println("创建" + this)
override def toString(): String = "颜色标记:"+ color
}
// 伴生对象,与类共享名字,可以访问类的私有属性和方法
object Marker{
private val markers: Map[String, Marker] = Map(
"red" -> new Marker("red"),
"blue" -> new Marker("blue"),
"green" -> new Marker("green")
)
def apply(color:String) = {
if(markers.contains(color)) markers(color) else null
}
def getMarker(color:String) = {
if(markers.contains(color)) markers(color) else null
}
def main(args: Array[String]) {
println(Marker("red"))
// 单例函数调用,省略了.(点)符号
println(Marker getMarker "blue")
}
}
5.3 构造函数
主构造函数
与java没区别
默认参数构造函数
class Person(var name:String="",var age:Int=18){
}
//不指定参宿,使用默认值
val p = new Person
//指定name,不指定age
val p1 = new Person("John")
//指定部分参数的默认参数
class Person(var name:String,var age:Int=18){
}
- 私有主构造函数
只能在类内部使用,外部不能使用
class Person private(var name:String,var age:Int)
5.4 继承与多态
与java基本相同
5.5 trait
Scala Trait(特征) 相当于 Java 的接口,实际上它比接口还功能强大。
与接口不同的是,它还可以定义属性和方法的实现。
一般情况下Scala的类只能够继承单一父类,但是如果是 Trait(特征) 的话就可以继承多个,从结果来看就是实现了多重继承。
Trait(特征) 定义的方式与类类似,但它使用的关键字是 trait,如下所示:
trait Closable{
def close():Unit
}
class File(var name:String) extends Closable{
def close():Unit=println(s"File $name has been closed")
}
new File("config.txt").close()
如果类继承其他类,又混入若干特征,则需要用with关键字
class File(var name:String) extends java.io.File(name) with Closable with Cloneable{
def close()=println(s"File $name has been closed")
}
对类或trait进行扩展至时,混入第一个trait用extends,其他的trait用with关键字
trait Processable extends Closable with Cloneable
trait与抽象类类似,在trait中可以有抽象成员、成员方法、具体成员和具体方法。
trait不能有主构造函数。
abstract class Logger(val msg:String)
trait Logger(val msg:String) //错误
5.6 自身类型
特质可以要求混入它的类扩展自另一个类型,但是当使用自身类型(self type)的声明来定义特质时(this: ClassName =>),这样的特质只能被混入给定类型的子类当中。
如果尝试将该特质混入不符合自身类型所要求的类时,就会报错。
trait A{
val name:String = "张三"
}
trait B {
this:A=>
this.name
}
//必须混入特质A
class C extends B with A{
val age = 18
println("name:"+this.name)
println("age:"+this.age)
}
5.7 包
普通package与java的相同之处不再描述。
以下有不同之处:
* 包对象
scala语言中一切皆对象,包也布列外。包对象主要用于对常量和工具函数的封装,使用时直接通过包名引用
package object Math{
val PI=3.141529
val THETA=2.0
val SIGMA=1.9
}
class Computation{
def computeArea(r:Double)=Math.PI*r*r
}
包的隐式引入
如果不引人任何包,Scala会默认引入java.lang._和scala.Predef对象中的所有类、成员变量和方法。
引入重命名
Scala中允许对引入的类和方法进行重命名,例如需要在程序中同时使用java.util.HashMap及scala.collection.mutable.HashMap时,可以利用引入重命名消除命名冲突问题(也可以采用包名前缀的方式)。
import java.util.{ HashMap => JavaHashMap}
import scala.collection.mutable.HashMap
后面就可以使用JavaHashMap类代表java.util.HashMap了
- 类隐藏
若不希望引入某个包的某个类,可使用类隐藏机制,同样解决上面的包冲突问题。
import java.util.{HashMap=>_,_}
import scala.collection.mutable.HashMap
引入java.util包中所有类的同时,将HashMap类隐藏
6 模式匹配
6.1模式匹配的7大类型
相当于java中的switch…case…语句,Java中很容易忽略break语句,Scala中可以有效解决这一问题,避免switch语句以为陷入其他分支,只要发现有一个匹配的case,剩下的case不会继续匹配。
6.1.1 常量模式匹配
常量模式匹配是指case语句后面接的全部为常量
for(i < 1 to 5)
i match {
case 1=> println(1)
case 2=> println(2)
case 3=> println(3)
}
6.1.2 变量模式
变量模式指case语句后面是变量
for(i < -1 to 6)
i match {
case 1=> println(1)
case x if(x%2==0)=>println(s"$x 能被2整除")
case _=>
}
6.1.3 构造函数模式
使用了case关键字的类定义就是就是样例类(case classes),样例类是种特殊的类,经过优化以用于模式匹配。
object Test {
def main(args: Array[String]) {
val alice = new Person("Alice", 25)
val bob = new Person("Bob", 32)
val charlie = new Person("Charlie", 32)
for (person <- List(alice, bob, charlie)) {
person match {
case Person("Alice", 25) => println("Hi Alice!")
case Person("Bob", 32) => println("Hi Bob!")
case Person(name, age) =>
println("Age: " + age + " year, name: " + name + "?")
}
}
}
// 样例类
case class Person(name: String, age: Int)
}
使用构造函数时,如果析取部分成员域,可以使用通配符
case class Dog(val name:String,val age:Int)
val dog = Dog("Pet",2)
def patternMatching(x:AnyRef)=x match {
case Dog(_,age) => println(s"Dog age=$age")
case _ =>
}
patternMatching(dog)
在声明样例类时,下面的过程自动发生了:
- 构造器的每个参数都成为val,除非显式被声明为var,但是并不推荐这么做;
- 在伴生对象中提供了apply方法,所以可以不使用new关键字就可构建对象;
- 提供unapply方法使模式匹配可以工作;
- 生成toString、equals、hashCode和copy方法,除非显示给出这些方法的定义。
6.1.4 序列匹配
序列匹配用于匹配Seq[+A]类及子类集合的内容
val arrInt = Array(1,2,3,4)
def patternMatching(x:AnyRef)=x match {
case Array(first,second) => println(s"序列中第一个元素=$first,第二个元素=$second")
case Array(first,_,three,_*) => println(s"序列中第一个元素=$first,第三个元素=$three")
case _=>
}
patternMatching(arrInt)
注:_*必须用在模式的最后
6.1.5 元组匹配
与序列匹配类似不能使用_*
val tupleInt = (1,2,3,4)
def patternMatching(x:AnyRef)=x match {
case (first,second) => println(s"序列中第一个元素=$first,第二个元素=$second")
case (first,_,three,_) => println(s"序列中第一个元素=$first,第三个元素=$three")
case _=>
}
patternMatching(tupleInt)
6.1.6 类型模式
类型匹配用于匹配变量的类型,Scala是一种静态类型语言,任何变量在定义时都是有类型的,在不给定的情况下,Scala会使用类型推导确定变量的类型。类型模式用于判断变量的具体类型。
class A
class B extends A
class C extends A
val b = new B
val c = new C
def patternMatching(x:AnyRef) = x match {
case x:String => println("字符串类型")
case x:B => println("对象类型为B")
case x:A => println("对象类型为A")
case _ => println("其他类型")
}
patternMatching("Scala")
patternMatching(b)
patternMatching(c)
结果:
字符串类型
对象类型为B
对象类型为A
6.1.7 变量绑定模式
构造函数模式可用于析取成员域,而如果想返回匹配该模式的对象,需要使用变量绑定模式
case class Dog(val:name:String,val age:Int)
val dog = Dog("Pet",2)
def patternMatching(x:AnyRef)=x match {
case d@Dog(_,_)=>println("变量绑定模式返回变量值为:"+d)
case _=>
}
patternMatching(dog)
结果:
变量绑定模式返回变量值为:Dog(Pet,2)
6.2 正则表达式和模式匹配
Scala语言与Java语言有着良好的相互操作性,Scala完全可以使用Java语言提供的API进行正则表达式处理。
import java.util.regex.Pattern
val line="Hadoop has been the most popular big data "+
"processing tool since 2005-11-21"
val regx = "(\\d\\d\\d\\d)-(\\d\\d)-(\\d\\d)"
val pattern = Pattern.compile(regx)
val m = pattern.matcher(line)
if(m.find()){
println(m.group(0))
println(m.group(1))
println(m.group(2))
println(m.group(3))
}
结果:
2005-11-21
2005
11
21
Scala自身提供的API,两种方式:
第一种是通过r方法直接将字符串转换为正则表达式对象,对应类为scala.util.matching.Regex
val dataP1 = """(\d\d\d\d)-(\d\d)-(\d\d)""".r
三个引号避免Java语言中直接使用转义字符"(\\d\\d\\d\\d)-(\\d\\d)-(\\d\\d)"所带来的阅读性差的问题。
第二种创建正则表达式对象的方式显示调用scala.util.matching.Regex构造函数创建
val dataP2 = new scala.util.matching.Regex("""(\d\d\d\d)-(\d\d)-(\d\d)""")
关于Regex几个重要的方法,可参考相应的API
7 隐式转换
Scala 的隐式转换系统定义了一套定义良好的查找机制,让编译器能够调整代码。Scala 编译器可以推导下面两种情况:
- 缺少参数的方法调用或构造器调用;
- 缺少了的从一种类型到另一种类型的转换。(是指调用某个对象上不存在的方法时,隐式转换自动把对象转换成有该方法的对象)
implicit 关键字可通过两种方式使用:1、方法或变量定义;2、方法参数列表。如果关键字用在变量或方法上,等于告诉编译器在隐式解析(implicit resolution)的过程中可以使用这些方法和变量。
隐式解析是指编译器发现代码里的缺少部分信息,而去查找缺少信息的过程。
7.1 隐式参数转换
implicit 关键字用在方法参数列表的开头,是告诉编译器应当通过隐式解析来确定所有的参数值。
scala> def findAnInt(implicit x: Int) = x
findAnInt: (implicit x: Int)Int
scala> findAnInt
<console>:9: error: could not find implicit value for parameter x: Int
findAnInt
^
scala> implicit val test = 5
test: Int = 5
scala> findAnInt
res1: Int = 5
scala> def findTwoInt(implicit a: Int, b: Int) = a + b
findTwoInt: (implicit a: Int, implicit b: Int)Int
scala> findTwoInt // implicit 作用于整个参数列表
res2: Int = 10
scala> implicit val test2 = 5
test2: Int = 5
scala> findTwoInt
<console>:11: error: ambiguous implicit values:
both value test of type => Int
and value test2 of type => Int
match expected type Int
findTwoInt
^
scala> findTwoInt(2, 5) // 隐式的方法参数仍然能够显式给出
res4: Int = 7
7.2 隐式类型转换
scala编译器在对语言做类型检查时,发现参与运行的表达式类型A不符合类型语义要求,在抛出错误之前,会在当前作用域中查找是否存在A 到B类型的转换,
scala> val list1 = List(1,2)
list1: List[Int] = List(1, 2)
scala> val i = 3
i: Int = 3
scala> list1 ::: i
<console>:28: error: value ::: is not a member of Int
list1 ::: i
^
原因在于list1:::i被转换成list1.:::(i)的方法调用,这个方法要求参数必须是集合类型,而i是Int型。一种解决办法是像这样list1:::List(i)在代码中显式的将 i 转换成List。另外一种方式就是像下面这样:
object IntWrapper{
implicit def intToList(i : Int): List[Int] = {
List(i)
}
def main(args : Array[String]) :Unit = {
val l1 = List(1,2);
val l2 = l1 ::: 3;
print(l2)
//输出List(1, 2, 3)
}
}
定义隐式转换方法intToList(必须使用implicit关键字),接收Int类型参数,转换成List[Int]。 上面代码编译器再碰到l1:::3时,发现类型不匹配,会在当前作用域寻找一种隐式转换使得 3 转换成l1.:::()调用能够接收的类型。
还有一种会编译器会尝试将源类型隐式转换成目标类型的地方就是:在源类型上调用方法,可是该方法却不存在于源类型的class定义中,比如:
class IntWrapper(i : Int) {
private[IntWrapper] var l = List[Int](i)
def printSelf(): Unit ={
println("In IntWraper: " + l)
}
}
object IntWrapper{
implicit def intToIntWraper(i : Int):IntWrapper = {
new IntWrapper(i)
}
def main(args : Array[String]) :Unit = {
val i = 11;
i.printSelf()
//输出 In IntWraper: List(11)
}
}
main方法里定义了Int类型 i ,Int类型不存在方法printSelf,此时编译器会尝试寻找Int上的类型转换,使的转换后的目标的类型有方法printSelf,因此会使用implicit def intToIntWraper作为转换函数。
7.3 隐式参数使用常见问题
(1)当函数没有柯里化是,implicit关键字会作用于函数参数列表中的所有参数。
def product(implicit x:Double,y:Double) = x*y
implicit val d = 2.55
product
结果:
Double = 6.5024999999999995
(2)隐式参数使用时要么全部知道,要么全不指定,不能只指定部分
def product(implicit x:Double,y:Double) = x*y
implicit val d = 2.55
//全部不指定
product
//全部指定
product(3,3)
//报错
product(3.0)
(3)同类型的隐式值在当前作用域内只能出现一次
def product(implicit x:Double,y:Double) = x*y
implicit val d = 2.55
//指定后会报错
implicit val e = 2.6
product
(4)在定义隐式参数时,implicit只能定义在参数开头
//不合法
def product(x:Double,implicit y:Double) = x*y
(5)如果想达到函数中def product(x:Double,implicit y:Double)只将参数y定义为隐式参数的目的,则需要函数进行柯里化
def product(x:Double)(implicit y:Double)=x*y
implict val d = 4.0
product(3)
结果:12.0
(6)柯里化的函数implict关键字只能作用域最后一个参数
//不合法
def product(implicit x:Double)(y:Double)=x*y
(7)implict关键字在隐式参数中只能出现一次,对柯里化的函数也不例外
//合法
def product(implicit x:Double,y:Double) = x*y
//不合法
def product(implicit x:Double,implicit y:Double) = x*y
//不合法
def product(implicit x:Double)(implicit y:Double)
(8)匿名函数不能使用隐式函数
val product=(x:Double,y:Double)=>x*y
//不能使用隐式参数
val product=(implict x:Double,y:Double)=>x*y
(9)柯里化的函数如果有隐式参数,则不能使用其偏应用函数
def product(x:Double)(y:Double)=x*y
//两个参数的偏应用函数
val p1=product_
p1(3.0)(4.0)
//一个参数的偏应用函数
val p2=product(3.0)_
p2(4.0)
//将柯里化函数参数y声明为隐式参数
def product(x:Double)(implict y:Double)=x*y
//定义隐式参数后,便不能使用其偏应用函数
val p=product_ //不合法
8 异常处理
Scala的异常处理类似许多其他语言(如Java)。它不是以正常方式返回值,方法可以通过抛出异常来终止。 但是,Scala实际上并没有检查异常。
当您想要处理异常时,要像Java一样使用try {…} catch {…}块,除了catch块使用匹配来识别和处理异常。
Scala允许在单个块中try/catch任何异常,然后使用case块对其进行模式匹配。
import java.io.FileReader
import java.io.FileNotFoundException
import java.io.IOException
object Demo {
def main(args: Array[String]) {
try {
val f = new FileReader("input.txt")
} catch {
case ex: FileNotFoundException => {
println("Missing file exception")
}
case ex: IOException => {
println("IO Exception")
}
} finally {
println("Exiting finally...")
}
}
}
可以在代码中明确地抛出异常。Scala提供throw关键字来抛出异常。 throw关键字主要用于抛出自定义异常。
class ExceptionExample2{
def validate(age:Int)={
if(age<18)
throw new ArithmeticException("You are not eligible")
else println("You are eligible")
}
}
object MainObject{
def main(args:Array[String]){
var e = new ExceptionExample2()
e.validate(10)
}
}
9 文件IO
Scala 进行文件写操作,直接用的都是 java中 的 I/O 类 (java.io.File):
import java.io._
object Test {
def main(args: Array[String]) {
val writer = new PrintWriter(new File("test.txt" ))
writer.write("Hello world!")
writer.close()
}
}
有时候我们需要接收用户在屏幕输入的指令来处理程序:
object Demo {
def main(args: Array[String]) {
print("Please enter your input : " )
val line = Console.readLine
println("Thanks, you just typed: " + line)
}
}
从文件读取内容非常简单。我们可以使用 Scala 的 Source 类及伴生对象来读取文件:
import scala.io.Source
object Test {
def main(args: Array[String]) {
println("文件内容为:" )
Source.fromFile("test.txt" ).foreach{
print
}
}
}
以上内容属于Scala的全部基本用法,Scala的高级部分用法,还包括Scala的类型参数、Scala的并发编程等,本文不再过多描述,感兴趣的同学可自行学习。
备注
扩展性:例如想扩展一个String的功能
javascript的实现方式:
var wordCount = function(words){
return words.split(" ").length;
};
扩展后:
String.prototype.wordCount = function(){
return this.split(" ").length;
};
Java的实现方式,可以自己写个类,如StringUtils工具类,然后通过import这个类实现。
C#实现方式:
namespace StringExtensionMethods
{
public static class WordCountExtensions
{
public static int WordCount(this String str)
{
return str.Split(new char[] { ' ' },
StringSplitOptions.RemoveEmptyEntries).Length;
}
}
}
必须 using 才可以使用这个扩展方法(扩展方法最终会转变成静态方法调用,所以跟 java 相比没有任何性能损失)
using StringExtensionMethods;
...
"Are you OK?".WordCount();
Scala的实现方式:
object StringExtension {
implicit class StringWordCount(words: String) {
def wordCount: Int = {
words.split(" ").length
}
}
}
扩展后:
import StringExtension.StringWordCount
"Are you OK?".wordCount
单单在实现这个功能上讲, C#的方式和 scala 没有太大的差别,C#的方式简单粗暴,是标准的静态语言套路.但是 scala 实现方式的可扩展性要强得多,因为implicit可以用在更多的其他地方,比如 隐式参数和Type Class Pattern