Scala类与对象

类简介

简介

类是对象的蓝图。一旦你定义了类,就能够用关键字new根据类的蓝图建立对象。在类的定义里,能够放置字段和方法,这些被笼统地称为成员。对于字段,不论是val仍是var定义的,都是指向对象的变量。对于方法,用def定义,包含了可执行代码。字段保留了对象的状态或数据,而方法使用这些数据执行对象的运算工做。当类被实例化的时候,运行时环境会预留一些内存来保留对象的状态映像——即变量的内容。html

示例

建立类示例:java

class SumAccumulator {
    var sum = 0
}

而后实例化两次:程序员

val acc: SumAccumulator = new SumAccumulator
val csa: SumAccumulator = new SumAccumulator 

这时内存里对象的状态映像以下:缓存

 

因为字段sum是var类型而非val,因此sum能够被赋予新的Int类型的值:例如安全

acc.sum = 3

此时映像会变成:网络

 

图中同时存在两个sum变量,一个在acc指向的对象里,另外一个在csa指向的对象里。字段的另外一种说法是实例变量(instance variable),由于每个实例都有本身的变量集。多线程

尽管acc和csa都是val类型,可是仍是能够修改acc指向的对象的值(上面的sum)。val类型对象对acc(或csa)的限制仅限于不能够把它们再次赋值给其余对象。例如,下面的尝试将会失败:app

//编译不过,由于acc是val
acc = new ChecksumAccumuulator

 

所以,咱们能够得出结论,acc将始终指向初始化的Checksuumaccumulator对象,可是对象包含的字段能够随时改动。ide

Scala类的重要特性

访问修饰符

  • Public是Scala默认的访问级别

为了保证对象的健壮性,能够把类中字段变为私有的(private)以阻止外界直接对它访问。由于私有字段只能被定义成在同一类里的方法访问,全部跟新字段的代码将锁定在类里。要声明字段是私有的,能够把访问修饰符private放在字段的前面。函数

代码示例:

class SumAccumulator {
    private var sum = 0
} 

使用private修饰后,任何从类外部对sum的访问都将失败

val acc: SumAccumulator = new SumAccumulator
acc.sum = 3 //编译不经过,由于sum是私有的

 

方法

1. Scala方法参数类型

Scala方法参数中的类型都是val而不是var类型

def add(a: Int, b: Int): Int = {
    a = a + b //编译错误:Reassignment to val (就是对val类型赋值的错误)
    a
}    

2. Scala方法中return

Scala方法中的return能够去掉,从而简化代码

代码示例:

def numSum(num:Int):Int ={
    val sum = num + 10
    sum //省略return关键字,简化代码
 
}

此时返回值就是sum

3. Scala方法中的“=”号

Scala方法中的“=”号很是重要:由于Scala编译器能够把任何类型转换为Unit若是定义的函数中”=”号忘记了添加,那么Scala会默认它返回值为Unit类型。若你原本想返回一个String类型的值,因为没有添加”=”,String类型返回值会被转换为Unit类型而丢弃掉。

代码示例:

def printStr() {
    "这是String类型的返回值"
}

返回结果为:   ()

正确代码示例:

def printStr(): String = {
    "这是String类型的返回值"
}

 返回结果为:    这是String类型的返回值

4. Scala方法表达式

假如某个方法仅计算单个结果的表达式,这能够去掉花括号,若是结果表达式很短,甚至能够把它放在def的同一行里。

代码示例:

def numSum(num:Int):Int = num + 10

5. Scala中分号推断

scala程序中,语句末尾的分号一般是可选的,若一行仅有一个语句也能够不加分号。不过,若是一行包含多条语句时,分号则是必须的。

不加分号:

if(x < 2)
println("too small")
else
println("ok")

必须加分号:

val x = 10; println(x)  //两个语句,必须加分号

Scala一般的风格是把操做符放在行尾而不是行头:

错误示例:

val x = 10;
val y = 3
val z = x //它会被编译器识别为z = x ; +y 两个语句
+y

打印结果:10

正确示例:

val x = 10;
val y = 3
val z = x +
y
println(z)

打印结果:13

Scala分号推断规则:

  1. 疑问行由一个不能合法做为语句结尾的字结束,如句点或中缀操做符。
  2. 下一行开始于不能做为语句开始的词。
  3. 行结束于括号(...)或方框[...]内部,由于这些符号不能容纳多个语句。

6. Scala无参方法

调用Scala类中无参方法时,能够写上括号,也能够不写。对于类中的取值器来讲,去掉()是不错的风格

代码示例:

object exam1 {
def main(args: Array[String]): Unit = {
val per: Person = new Person
per.talk() //ok
per.talk //一样ok
}
 
}
 
class Person {
def talk(): Unit = println("Talking")
}

若是你想强制使用这种风格,能够在声明方法时不带()

代码示例:

per.talk() //此时这种写法是错误的
per.talk //OK的
class Person {
def talk = println("Talking")
}

Singleton对象

Scala比Java更为面向对象的特色之一是Scala不能定义静态成员,而是代之以定义单例对象(singleton Object)。除了用object关键字替换了class关键字之外,单例对象的定义看上去与类定义一致。

例子:ChecksumAccumulator.scala源码:

class ChecksumAccumulator {
    private var sum = 0
    def add(b:Byte){sum += b}
    def checksum():Int = ~(sum & 0xFF) + 1
}
import scala.collection.mutable.Map
object ChecksumAccumulator {
    private val cache = Map[String, Int]()
    def calculate(s:String):Int=
        if(cache.contains(s))
            cache(s)
        else {
            val acc = new ChecksumAccumulator
            for(c <- s)
                acc.add(c.toByte)
            val cs = acc.checksum()
            cache += (s -> cs)
            cs
        }
}

上面源码中的单例对象叫作ChecksumAccumulator,与前一个例子里的类同名。当单例对象与某个类共享同一个名称时,它就被称为是这个类的伴生对象(companion object)。类和它的伴生对象必须定义在一个源文件里。类被称为是这个单例对象的伴生类(companion class)。类和它的伴生对象能够相互访问其私有成员。

这段缓存代码的说明以下:

类和单例对象间的差异是,单例对象不带参数,而类能够。由于单例对象不是用new关键字实例化的,因此没有机会传递给它实例化参数。每一个单例对象都被实现为虚拟类(synthetic class)的实例,并指向静态的变量,由于它们与Java静态类有着相同的初始化语义。特别要指出的是,单例对象在第一次被访问的时候才会被初始化。

不与伴生类共享名称的单例对象被称为独立对象(standalone object)。它能够用在不少地方,例如做为相关功能方法的工具类,或者定义Scala应用的入口点。

Scala程序

  想要编写可以独立运行的Scala程序,就必须建立有main方法(仅带一个参数Array[String],且结果类型为Unit)的单例对象。任何拥有合适签名的main方法的单例对象均可以用来做为程序的入口点。

Summer.scala文件源码:

import ChecksumAccumulator.calculate

object Summer {
    def main(args:Array[String]) {
        for(arg <- args)
            println(arg + ": " + calculate(arg))
    }
}

要执行Summer应用程序,须要把以上的代码写入文件Summer.scala中,由于Summer使用了ChecksumAccumulator,因此还要把ChecksumAccumulator的代码,上面的源码(类及它的伴生对象),放在文件ChecksumAccumulator.scala中。

Scala和Java之间有一点不一样,Java须要类名称与源码文件名同名,而在Scala对于源文件的命名没有硬性规定。然而一般状况下若是不是脚本,推荐的风格是像在Java里那样按照所包含的类名来命名文件,这样程序员就能够比较容易地根据文件名找到类。

Scala的脚步必须以结果表达式介绍。所以若是你尝试以脚本方式执行Summer.scala,Scala解释器将会报错说Summer.scala不是以结果表达式结束的。正确作法是:须要用Scala编译器真正的编译这些文件,而后执行输出的类文件,方式之一使用Scala的基本编译器,scalac。

D:\work\workspace\scala>scalac ChecksumAccumulator.scala Summer.scala

D:\work\workspace\scala>

D:\work\workspace\scala>fsc ChecksumAccumulator.scala Summer.scala

D:\work\workspace\scala>

 

D:\work\workspace\scala>scala Summer of love
of: -213
love: -182

D:\work\workspace\scala>

Scala中的Application

为了使代码更简洁,Scala还提供了另一种运行Scala程序的方式,那就是直接继承scala.Application接口(Trait)。
直接继承自Application的运行方式:
object RunAppWithoutMain extends Application {  
    println("runing scala app without main")  
}
之因此这里无须定义main方法,那是 由于在Application这个接口中定义了一个main方法,main方法在执行时会初始化RunAppWithoutMain这个对象,并执行它的主构造方法,而全部直接写在对象中的代码都会被scala编译器收集到主构造方法中,因而就被运行了。
extends Application虽然比编写main方法要方便,可是也有一些反作用。直接继承自Application致使的反作用:
1. 没法接受命令行参数。由于args参数不会被传入
2. 在Scala中,若是一个程序是多线程的,那么这个程序必须具备一个main方法。因此第二种写法只能适用于单线程的程序
3. Application这个接口在执行一个程序的代码以前,须要进行一些初始化。而某些JVM不会对这些初始化代码进行优化。

因此第二种方法只适用于一些很是简单的场合,大部分状况不推荐使用。
 

Scala类中getter和setter

getter和setter

Scala类中使用公有字段的话,任何人均可以修改这个字段,使得安全性大大下降。因此咱们更倾向于使用getter和setter方法:

Scala对类中每一个字段都提供了getter和setter方法,分别叫作age和age_=,

代码示例:

建立一个exam1.scala文件,文件内容以下

class Person {
var age = 0
}

若是想查看这些方法,能够先编译Person类,用scalac命令编译,而后用javap查看字节码:

D:\work\workspace\scala>scalac exam1.scala

D:\work\workspace\scala>javap Person
Compiled from "exam1.scala"
public class Person {
  public int age();
  public void age_$eq(int);
  public Person();
}

D:\work\workspace\scala>

 

 

你能够本身从新定义getter和setter方法。

代码示例:

object exam1 {
 
    def main(args: Array[String]): Unit = {
        val per: Person = new Person
        per.age = 18
        per.age = 16 //因为在setter里面控制了age不能变小,因此执行结果age不会变
        println(per.age)
        per.age = 19 //使用setter,赋予大于原来的age
        println(per.age)
        }
    }
 
    class Person {
        private var privateAge = 0 //变成私有变量并更名
        def age = privateAge
        def age_=(newAge: Int) { // age_= 不能有空格
        if (newAge > privateAge) privateAge = newAge //使得年龄不能变小
    }
}

打印结果为:

18
19

Scala中每一个字段生成getter和setter的限制:

  1. 若是字段是私有的,则getter和setter方法也是私有的。
  2. 若是字段是val,则只有getter方法被生成。
  3. 若是你不须要任何getter或setter,能够将字段声明为private[this]

Scala在实现类中属性时的四个选择:

  1. 自动生成一个getter和setter。
  2. 自动生成一个getter。
  3. 自定义foo和foo_=方法。
  4. 自定义foo方法。

Bean属性

JavaBean规范把Java属性定义为一堆getFoo和setFoo方法。相似于Java,当你将Scala字段标注为 @BeanProperty时,getter和setter方法会自动生成。

代码示例:

import scala.beans.BeanProperty
 
object exam1 {
    def main(args: Array[String]): Unit = {
        val per: Person = new Person
        per.name = "zhagnsan"
        per.setName("lisi") //BeanProperty生成的setName方法
        println(per.getName) //BeanProperty生成的getName方法
    }
}
 
 
class Person {
    @BeanProperty var name:String = _
}

打印结果为:

Lisi

上述类Person中由@BeanProperty生成了四个方法:

name: String
name_= (newValue: String): Unit
getName(): String
setName (newValue: String): Unit

图示为针对字段生成的方法:

 

Scala类中构造器

和java或C++同样,Scala也能够有任意多的构造器。不过,Scala类有一个构造器比其余全部构造器都更为重要,它就是主构造器。除了主构造器外,Scala类还能够有任意多的辅助构造器。

辅助构造器

Scala中辅助构造器和Java或C++十分类似,只有两处不一样:

  1. 辅助构造器的名称为this。
  2. 每个辅助构造器都必须以一个对先前已定义的其余辅助构造器或主构造器的调用开始

这里有一个带有两个辅助构造器的类。

代码示例:

object exam1 {
    def main(args: Array[String]): Unit = {
        val per1: Person = new Person //主构造器
        val per2: Person = new Person("Bob") //第一个辅助构造器
        val per3: Person = new Person("Bob",18) //第二个辅助构造器
    }
}
 
    class Person {
        private var name = ""
        private var age = 0
        //一个辅助构造器
        def this(name: String) {
            this() //调用主构造器 
            this.name = name
        }
 
        //另外一个辅助构造器
        def this(name: String, age: Int) {
            this(name) //调用前一个辅助构造器
            this.age = age
        }
    }    

主构造器

在Scala中,每一个类都有主构造器。主构造器并不以this方法定义,而是与类定义交织在一块儿

主构造器的参数直接放在类名以后

代码示例:

object exam1 {
def main(args: Array[String]): Unit = {
val per: Person = new Person("Bob", 18) //使用主构造器实例化对象
println(per.name + " : " + per.age)
}
}
 
class Person(val name: String, val age: Int) {
//产生私有的name和age,没有setter
//.....
}

打印结果:

Bob : 18

上述简短的Person类定义极大简化了相同功能的Java代码:

与上例相同功能的Java代码示例:

class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
 
public String name() {
return this.name;
}
 
public int age() {
return this.age;
}
}

 

Scala主构造器定义的全部语句都会执行

代码示例:

class Person(val name: String, val age: Int) {
//产生私有的name和age,没有setter
println("主构造器定义的全部语句都会执行") //每次使用主构造器时都会执行
def description = name + "is" + age + "years old"
}

不一样类型的主构造器参数对应会生成的字段和方法:

 

若是想让主构造器变成私有的,能够像这样放置private关键字:

class Person private (val name: String, val age: Int) {
//......
}

这样一来,类用户就必须经过辅助构造器来构造Person对象了.

Scala嵌套类

在Scala中,你几乎能够在任何语法结构中内嵌任何语法构造。你能够在函数中定义函数,在类中定义类。

代码示例:

class Network {
 
//在Network类中定义类Member
class Member(val name: String) {
    val contacts = new ArrayBuffer[Member]
}
 
private val members = new ArrayBuffer[Member]
 
def join(name: String) = {
    val m = new Member(name)
    members += m
    m
}
}

 

考虑有以下两个网络:

al chatter = new Network
val myFace = new Network
 

在Scala中,每一个实例都有它本身的Member类(内部类),就和它们有本身的members(内部类)字段同样。也就是说chatter.Member和myFace.Member是不一样的两个类

做用:拿网络示例来讲,你能够在各自的网络中添加成员,可是不能跨网添加成员。

代码示例:

val chatter = new Network
val myFace = new Network
val fred = chatter.join("Fred")
val wilma = chatter.join("wilma")
fred.contacts += wilma //OK
val barney = myFace.join("Barney")
 
//错误:不能将一个myFace.Member添加到chatter.Member元素缓冲中
fred.contacts += barney

在嵌套类中,你能够经过 外部类.this 的方式来访问外部类的this引用,就像Java那样。

class Network(val name: String) {
//在Network类中定义类Member
class Member(val name: String) {
//....
def description = name + "inside" + Network.this.name
}
}

若是你以为须要,也可使用以下语法创建一个指向该引用的别名:

class Network(val name: String) { outer =>
//在Network类中定义类Member
class Member(val name: String) {
//....
def description = name + "inside" + outer.name
}
}

上述语法使得outer变量指向Network.this。对这个变量,你可使用任何合法的名称。

转自:https://blog.csdn.net/u011204847/article/details/51105362

相关文章
相关标签/搜索