Scala 基础语法_第二阶段
1. 类和对象
1.1 类
1)、简单类和无参方法
如下定义Scala类最简单形式:
class Counter {
private var value = 0 // 必须初始换字段
def increment () { // 方法默认是公有的
value += 1
}
def current () = value
}
在Scala中,类并不是声明为public,且一个Scala源文件可以包含多个类,这些类都具有公有可见性。使用该类的方式是:
val myCounter = new Counter // 或 new Counter()
myCounter.increment()
println(myCounter.current) // 调用无参方法时可以省略()
2)、带getter和setter的属性
Scala对每个字段都提供getter和setter方法,在任何时候都可以自己重新定义getter和setter方法:
class Counter{
var count = 0 // 默认自带的getter和setter方法分别叫做count和count_=
//*******************自定义getter和setter方法**********************
private var v= 0 //私有变量,外界无法直接访问
def value = v // 定义一个方法,方法名为我们想要的字段的名称,代替getter
def value_= (newValue: Int) { //注意 value_= 是方法名字
if (newValue > 0) v = newValue // 只有数字为正数才允许修改
}
}
object Counter {
def main(args: Array[String]): Unit = {
val myCounter = new Counter
println(myCounter.value) //调用的是value方法
myCounter.value = 3 //赋予新值
println(myCounter.value)
}
}
3)、只带getter的属性
有时候只需要一个只读属性,有getter没有setter,如果属性值在对象构建玩之后就不再改变,可以使用val字段。
但是,有时候需要某个属性,客户端不能随意修改,但可以通过某种方式修改,这样就不能用val字段修饰,而是提供一个私有属性和该属性的getter方法:
class Counter {
private var value = 0
def increment () { value += 1}
def current = value
}
4)、对象私有字段
在Scala中,方法可以访问该类的所有对象的私有字段:
class Counter {
private var value = 0
def increment () { value += 1 }
// 可以访问另一个对象的私有字段
def isLess (other: Counter) = value < other.value
}
之所以可以访问other.value,是因为other也同样是Counter对象。
Scala允许我们定义更加严格的访问限制,通过private[this]修饰符实现:
private[this] var value = 0 // Counter类的方法只能访问当前对象的value字段
5)、Bean 属性
Scala 也存在类似 JavaBeans规范,可以将Scala的字段标注为@BeanProperty:
import scala.beans.BeanProperty
class Counter{
@BeanProperty
var name : String = _
}
object Counter {
def main(args: Array[String]): Unit = {
println(myCounter.getName)
myCounter.setName("zhangsan")
println(myCounter.name)
myCounter.name = "lisi"
println(myCounter.getName)
}
}
将会生成四个方法:
- name:string
- name_=(newValue:String):Unit
- getName():String
- setName(newValue:String):Unit
6)、主构造器
在Scala中,每个类都有主构造器。主构造器并不是以this方法定义,而是与类定义交织在一起:
-
主构造器的参数直接放置在类名之后:
class Person (val naem : String,val age : Int) {//()中的内容就是主构造器的参数
//...
}
这样Person类设置了name和age字段,且默认提供getter和setter方法。
-
主构造器会执行类定义中的所有语句:
class Person (val naem : String,val age : Int) {
println("class Person loading...")
private val props = new Peoperties
props.load(new FileReader("myprop.properties"))
// 上述语句都是主构造器的一部分
}
7)、辅助构造器
Scala类除了有一个比较重要的主构造器之外,还可以有任意多个的辅助构造器。具体特点为:
- 辅助构造器的名称为this。
- 每一个辅助构造器都必须以一个对先前定义的其他辅助构造器或主构造器的调用开始。
class Person (val name : String ,val age : Int) {
private var sex = "male"
private var addr = ""
def this(name:String,age:Int,sex:String){ //第一个辅助构造器
this(name,age) //调用主构造器,注意参数列表
this.sex = sex
}
def this(name:String,age:Int,sex:String,addr:String){ //另一个辅助构造器
this(name:String,age:Int,sex:String) //调用前一个构造器
this.addr = addr
}
override def toString() = { // 重写toString方法
"["+name+" "+age+" "+sex+" "+addr+" "+"]"
}
}
object Person {
def main(args: Array[String]): Unit = {
val p1 = new Person("zhangsan",19)
println(p1)
val p2 = new Person("lisi",22,"female")
println(p2)
val p3 = new Person("wangwu",33,"male","shanghai")
println(p3)
}
}
[zhangsan 19 male ]
[lisi 22 female ]
[wangwu 33 male shanghai ]
1.2 对象
1.2.1 单例对象
Scala 没有静态方法或静态字段,你可以用object这个语法结构来达到同样目的。用object定义的对象,构造器在第一次使用时被调用,若从未使用该对象,其构造器也不会被执行。这个对象中的成员属性或方法可用对象名.属性或方法直接调用:
object Accounts {
private var lastNumber = 0
def newUniqueNumber() = {lastNumber += 1 ; lastNumber}
// object对象中可以定义main方法
def main(args: Array[String]): Unit = {
val num = Accounts.newUniqueNumber()
}
}
对象本质上可用拥有类的所有特性——它甚至可用扩展其他类或特质(Java 8 中的接口),但是有一个例外:你不能提供构造器参数。
1.2.2 伴生对象
在Scala中,你可以通过类和与类同名的“伴生”对象来定义普通实例方法和静态方法:
class Accounts {
// 调用伴生对象中的方法必须以对象名.方法名()调用
val id = Accouts.newUniqueNumber()
...
}
object Accounts { // 伴生对象
private var lastNumber = 0
def newUniqueNumber() = {lastNumber += 1 ; lastNumber}
}
类和它的伴生对象可以互相访问私有特性。它们必须存放在同一个源文件中。
1.2.3 apply 方法
我们通常会定义和使用对象的apply方法,当出现如下形式的表达式时,apply方法就会被调用:
ObjectName(参数1,...,参数N)
通常,这样一个apply方法返回的是伴生类的对象。apply方法区别于构造器的调用,可以省去new关键字会方便很多。
1.2.4 应用程序对象
每个Scala程序大多是以一个对象的main方法开始,这个方法的类型为Array[String]=>Unit:
object Hello {
def main (args:Array[String]){
println("Hello,World!")
}
}
除了每次都提供自己的main方法外,你也可以扩展App特质,然后将程序代码放入构造器方法体内:
object Hello extends App {
println("Hello,World!")
}
如果你需要命令行参数,则可以通过args属性得到:
object Hello extends App {
if(args.length > 0)
println("Hello:"+args(0))
else
println("Hello,World")
}
1.2.5 枚举
Scala中并没有枚举类型,不过,标准类提供了一个Enumeration助手类,可以用于产出枚举。定义一个扩展Enumeration类的对象并以Value方法调用初始化枚举中的所有可选值:
object Colors extends Enumeration {
val Red,Yellow,Green = Value
}
枚举中的每个枚举值的ID默认从0开始依次加1,定义完成后,可以用Colors.Red、Colors.Yellow等来引用枚举值。注意:这些枚举值的类型是Colors.Value而不是Colors,后者只是握有这些枚举值的对象,可以为Value增加一个类型别名:
object Colors extends Enumeration {
type Colors = Values
val Red,Yellow,Green = Value
}
枚举值的ID可通过id方法返回,名称通过toString方法返回,对Colors.values的调用输出所有枚举的值集:
object Colors extends Enumeration {
type Colors = Value
val Red,Yellow,Green = Value
def main(args: Array[String]): Unit = {
for(color <- Colors.values)
println(color.id+":"+color)
/**
0:Red
1:Yellow
2:Green
*/
println(Colors(0)) //输出Red
println(Colors.withName("Red")) //输出Red
}
}
2. 包和引入
2.1 包
Scala也可以管理大型项目的名称,可在任意处语句中增加条目到包中,如:
package com {
package horstman {
package impatient {
class Employee
...
}
}
}
这样一来类名Employee就可以在任意位置以com.horstman.impatient.Employee访问到。
2.2 作用域规则
在Scala中,包的作用域比起 Java 来更加前后一致。Scala的包和其他作用域一样支持嵌套,可以访问上层作用域中的名称。
2.3 包对象
每个包都可以有一个包对象,需要在父包中定义它,且名称与子包名一样。
2.4 包可见性
在Scala中,可以达到在Java中无修饰符(public、private、protected)修饰的类成员在包含该类的包中可见的效果:
package com.scala.people
class Person{
private[perple] def description = "A person with name " + name //在people包可见
...
}
2.4 引入
引入语句让你可以使用更短的名称而不是原来较长的名称,通常的写法和Java中一样,不过当引入某个包的全体成员时:
import java.awt._ //而不是用通配符*
//而且访问java.awt下的成员时可以省略java.awt
3. 继承
3.1 扩展类
Scala扩展类的方式和Java一样——使用extends关键字。
3.2 重写方法
在Scala中重写一个非抽象方法必须使用override修饰符。
3.3 类型检查和转换
要测试某个对象是否属于某个给定的类,可以用isInstanceOf方法。如果测试成功,你就可以用asInstanceOf方法将引用转换为子类的引用:
if (p.isInstanceOf[Employee]) {
val s = p.asInstanceOf[Employee] // s的类型为Employee
...
}
如果p是null,则p.isInstanceOf[Employee]将返回false,且p.asInstanceOf[Employee]将返回null。
如果p不是一个Employee,则p.asInstanceOf[Employee]将抛出异常。
如果你想要测试p指向的是一个Employee对象但又不是其他子类的话,可以用:
if (p.getClass == classOf[Employee])
classOf方法定义在scala.Predef对象中,因此会被自动引入。
3.4 受保护字段和方法
和Java一样,也可以将字段或方法声明为protected,使其可以被任何子类访问,但不能从其他位置看到。
于Java的区别是,protected的成员对于所属的包而言,是不可见的。
Scala还提供了一个protected[this]的变体,将访问权限定在当前对象。
3.5 超类的构造
只有主构造器可以调用超类的构造器,而辅助构造器不能。类似如下调用:
class Employee(name:String , age:Int , Val salary:Double) extends
Person(name,age)
3.6 重写字段
前面学到Scala的字段由一个私有字段和取值器/改值器方法构成。重写字段有如下规则:
- def只能重写另一个def;
- val只能重写另一个val或者不带参数的def;
- var只能重写另一个抽象的var。
3.7 匿名子类
和Java一样,你可以通过包含带有定义或重写的代码块的方式创建一个匿名的子类。
3.8 抽象类
用abstract关键字标记的不能被实例化的类,该类中的抽象方法没有方法体,在子类重写这些抽象方法时不需要使用override关键字。
抽象类中的抽象字段没有被初始化。
4. 特质
Scala Trait(特征) 相当于 Java 的接口,实际上它比接口还功能强大。与接口不同的是,它还可以定义属性和方法的实现。一般情况下 Scala 的类可以继承多个 Trait,从结果来看就是实现了多重继承。Trait(特征) 定义的方式与类类似,但它使用的关键字是 trait。
- Scala的特质大部分情况下会被当做Java中的接口使用;
- 特质中可以带有具体的实现;
- 继承类之后再使用特质要用关键字:with,只是使用特质时第一个要用extends;
5. 集合
5.1 数组
详见之前小节
5.2 list
-
创建 list
val list = List(1,2,3,4)
——Nil 长度为 0 的 list
-
list 遍历
foreach ,for
- list 方法举例
filter:过滤元素
count:计算符合条件的元素个数
map:对元素操作
flatmap :压扁扁平,先 map 再 flat
5.3 set
- 创建 set
注意:set 集合会自动去重 - set 遍历
foreach,for - set 方法举例
交集:intersect ,&
差集: diff ,&~
子集:subsetOf
最大:max
最小:min
转成数组,toList
转成字符串:mkString(“~”)
5.4 map
- map 创建
- 获取 map 的值
- map.get(“1”).get
- map.get(100).getOrElse(“no value”):如果 map 中没有对应项,赋值为 getOrElse 传的值。
//获取值
println(map.get("1").get)
val result = map.get(8).getOrElse("no value")
println(result)
- 遍历 map
//map遍历
for(x <- map){
println("====key:"+x._1+",value:"+x._2)
}
map.foreach(f => {
println("key:"+ f._1+" ,value:"+f._2)
})
- 遍历 key
//遍历key
val keyIterable = map.keys
keyIterable.foreach { key => {
println("key:"+key+", value:"+map.get(key).get)
} }
println("---------")
- 遍历 value
//遍历value
val valueIterable = map.values
valueIterable.foreach { value => {
println("value: "+ value)
}
}
- 合并 map
- ++ 例:map1.++(map2)
注意:合并 map 会将 map 中的相同 key 的 value 替换
//合并map
val map1 = Map(
(1,"a"),
(2,"b"),
(3,"c")
)
val map2 = Map(
(1,"aa"),
(2,"bb"),
(2,90),
(4,22),
(4,"dd")
)
map1.++(map2).foreach(println)
- map 中的方法举例
- filter:过滤,留下符合条件的记录
- count:统计符合条件的记录数
- contains:map 中是否包含某个 key
- exist:符合条件的记录存在不存在
6. Actor
在Scala 2.10 版本及之前,Scala类库提供了一个actor模型的简单实现,这也是本节介绍的内容。在Scala 2.10版本之后,actor被整合到akka之中,详情后续介绍。通过使用Actor模型我们提升了抽象级别,为构建可扩展的、有弹性的响应式并发应用提供了一个更好的平台。
6.1 创建和启动Actor
actor是扩展自Actor特质的类,该特质带有一个抽象方法act,我们可以重写该方法来指定该actor的行为。通常,act方法带有一个消息循环:
import scala.actors.Actor
class HelloActor extends Actor{
override def act(): Unit = {
while (true){
receive {
case "hello" => println("您好!")
case _ => println("excuse me ?")
}
}
}
}
act方法跟Java中的Runnable接口的run方法相似。要启动一个actor,需要构造该actor实例,并调用start方法:
val actor1 = new HelloActor
actor1.start()
6.2 发送消息
要发送消息,我们可以用为actor定义的!操作符:
actor1 ! "hello" // !后面可以发送对象在内的多种类型
6.3 接收消息
发送到actor 的消息被存放在一个名叫receive方法的“邮箱”中,receive方法能获取传给它的任意类型的参数,该参数是一个偏函数,如:
receive {
case "hello" => println("您好!")
case _ => println("excuse me ?")
}
以上结合代码:
import scala.actors.Actor
class HelloActor extends Actor{
override def act(): Unit = {
while (true){
receive {
case "hello" => println("您好!")
case _ => println("excuse me ?")
}
}
}
}
object HelloActor extends App {
val actor1 = new HelloActor
actor1.start()
actor1 ! "hello"
}
运行结果:
您好!
6.4 消息通道
消息通道有两个好处:
- 消息通道是类型安全的——你只能发送或接收某个特定类型的消息;
- 你不会不小心同过消息通道调用到某个actor的方法。
import scala.actors.{Actor, Channel, OutputChannel}
case class ActorCase (input: Seq[Int] ,result: OutputChannel[Int])
class ActorTest01 extends Actor{
override def act(): Unit = {
while (true) {
receive{
case ActorCase (input,out) => { // 处理消息并回复
println("收到:"+input)
val answer = 6
out ! answer}
}
}
}
}
object ActorTest01 extends App {
val channel = new Channel [Int]
val actorTest = new ActorTest01
val input : Seq[Int] = Seq(1,2,3) // Seq特质,用来创建列表
actorTest.start()
actorTest ! ActorCase(input,channel) // 发送消息
channel.receive{ //接收回复
case x : Int => println("回复:"+ x)
}
}
6.5 同步消息和Future
actor可以发送一个消息并等待回复,用 !? 操作符即可,例如:
case class ActorCase2 (request : String )
class ActorTest02 extends Actor{
override def act(): Unit = {
receive {
case ActorCase2(request) => sender ! "fine , thank you"
}
}
}
object ActorCase2 extends App {
val actorTest02= new ActorTest02
actorTest02.start()
println("how are you") ;
val reply = actorTest02 !? ActorCase2("how are you")
reply match {
case response : String => println(response)
}
}
发送方会阻塞,知道它接收到回复。
如果不想对一个回复永远等待下去,可以使用receiveWithin方法来指定等待毫秒数:
receiveWithin(10*1000){
case ActorCase2(request) => sender ! "fine , thank you"
}
此外,同步消息还可以用Future实现:
val replyFuture = actorTest02 !! ActorCase2("how are you")
val reply = replyFuture()
reply match {
case response:String => println(response)
}
6.6 共享线程
要想在同一个线程中运行多个actor,可以使用react方法。react方法接收一个偏函数,并将它添加到邮箱,然后退出,如我们有两个嵌套的react语句:
react { // 偏函数f1
case CaseType1(param) =>
react { //偏函数f2
case CaseType2() =>
println("caseType2")
}
}
第一个react的调用将f1于actor的邮箱关联起来,然后退出,当CaseType1消息抵达时,f1被调用;偏函数f1调用另一个react,此时f2会与actor邮箱关联起来,然后退出。第二个react的退出需要抛出异常。
react方法不能放在while循环当中,而是通过无穷递归实现:
override def act() {
react {
case CaseType1 (param) => {
println(param)
act()
}
}
}
注:这个递归并不会占用很大的栈空间,每次对react的调用都会抛出异常,从而清栈。
6.7 Actor的生命周期
actor的act方法在start方法被调用时开始执行,actor会在如下情形之一终止执行:
- act方法返回;
- act方法由于异常被终止;
- actor调用exit方法。
注:当actor因一个异常终止时,其原因是UncaughtException样例类的一个实例,通过重写exceptionHandler方法处理非受检异常:
override def exceptionHandler = {
case e : RuntimeException => log(e)
}
6.8 Actor 与 Actor 之间通信
import scala.actors.Actor
case class Message(actor:Actor,msg:Any)
class Actor1 extends Actor{
def act(){
while(true){
receive{
case msg :Message => {
println("i sava msg! = "+ msg.msg)
msg.actor!"i love you too !"
}
case msg :String => println(msg)
case _ => println("default msg!")
}
}
}
}
class Actor2(actor :Actor) extends Actor{
actor ! Message(this,"i love you !")
def act(){
while(true){
receive{
case msg :String => {
if(msg.equals("i love you too !")){
println(msg)
actor! "could we have a date !"
}
}
case _ => println("default msg!")
}
}
}
}
object Lesson_Actor2 {
def main(args: Array[String]): Unit = {
val actor1 = new Actor1()
actor1.start()
val actor2 = new Actor2(actor1)
actor2.start()
}
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)