Scala入门:类

大数据学习路线图

【版权声明】博客内容由厦门大学数据库实验室拥有版权,未经允许,请勿转载!
[返回Spark教程首页]

类和对象是Java、C++等面向对象编程的基础概念。类是用来创建对象的蓝图。定义好类以后,就可以使用new关键字来创建对象。

简单的类

最简单的类的定义形式是:

class Counter{
     //这里定义类的字段和方法
}

然后,就可以使用new关键字来生成对象:

new Counter //或者new Counter()

给类增加字段和方法

下面我们给这个类增加字段和方法:

class Counter {
    private var value = 0
    def increment(): Unit = { value += 1}
    def current(): Int = {value}
}

在上面定义中,我们把value字段设置为private,这样它就成为私有字段,外界无法访问,只有在类内部可以访问该字段。如果字段前面什么修饰符都没有,就默认是public,外部可以访问该字段。对于类而言,我们并不需要声明为public,Scala文件中包含的多个类之间,都是彼此可见的。

对于方法的定义,是通过def实现的。上面的代码“def increment(): Unit = { value += 1}”中,increment()是方法,没有参数,冒号后面的Unit是表示返回值的类型,在Scala中不返回任何值,那么就用Unit表示,相当于Java中的void类型。方法的返回值,不需要靠return语句,方法里面的最后一个表达式的值就是方法的返回值,比如,上面current()方法里面只有一条语句“value”,那么,value的值就是该方法的返回值。

因为increment()方法只是对value的值进行了增加1的操作,并没有返回任何值,所以,返回值类型是Unit。Unit后面的等号和大括号后面,包含了该方法要执行的具体操作语句。如果大括号里面只有一行语句,那么也可以直接去掉大括号,写成下面的形式:

class Counter {
    private var value = 0
    def increment(): Unit = value += 1 //去掉了大括号
    def current(): Int = {value}  //作为对比,这里依然保留大括号
}

或者,还可以去掉返回值类型和等号,只保留大括号,如下:

class Counter {
    private var value = 0
    def increment() {value += 1} //去掉了返回值类型和等号,只保留大括号
    def current(): Int = {value} //作为对比,这里依然保留原来形式
}

创建对象

下面我们新建对象,并调用其中的方法:

val myCounter = new Counter
myCounter.increment() //或者也可以不用圆括号,写成myCounter.increment
println(myCounter.current)

从上面代码可以看出,Scala在调用无参方法时,是可以省略方法名后面的圆括号的。

编译和执行

现在,让我们把上面完整的代码拿到Linux系统中执行。请在登录Linux系统后,打开命令行终端(可以使用快捷组合键Ctr+Alt+T,快速打开命令行终端),进入到“/usr/local/scala/mycode”目录下,然后使用vim编辑器新建一个TestCounter.scala代码文件,如下:

cd /usr/local/scala/mycode
vim TestCounter.scala

在TestCounter.scala中输入以下代码:

class Counter {
    private var value = 0
    def increment(): Unit = { value += 1}
    def current(): Int = {value}
}
val myCounter = new Counter
myCounter.increment()
println(myCounter.current)

保存后退出vim编辑器。然后,使用scala命令执行这个代码文件:

scala TestCounter.scala

上面命令执行后,会在屏幕输出“1”。

这是在Linux的Shell命令中执行。实际上,我们也可以进入到Scala解释器下面去执行,首先启动Scala解释器,如下:

scala //在终端窗口中输入scala命令进入Scala解释器

这时就进入了scala命令提示符状态,可以在里面输入如下命令:

scala> :load /usr/local/scala/mycode/TestCounter.scala  //这是输入的命令
Loading /usr/local/scala/mycode/TestCounter.scala... //从这里开始是执行结果
defined class Counter
myCounter: Counter = Counter@17550481
1

完成上面操作以后,我们可以退出Scala解释器,回到Linux系统的Shell命令提示符状态,退出Scala解释器的命令如下:

scala> :quit

下面,我们尝试一下,看看是否可以使用scalac命令对这个TestCounter.scala文件进行编译,如下:

scalac TestCounter.scala

执行上述scalac命令后,你会发现,会出现一堆错误,无法编译。为什么呢?因为,当我们使用scalac命令对TestCounter.scala进行编译时,必须要求把声明(比如val myCounter = new Counter以及myCounter.increment()等)都封装在对象中,这也是JVM字节码的要求。但是,在TestCounter.scala中,这些声明都没有被封装在对象中,所以,无法编译。

不过,如果我们确实需要把TestCounter.scala编译为JVM字节码,那么,可以使用下面命令:

scalac -Xscript Upper1 TestCounter.scala  //编译
scala -classpath . Upper1 //执行

执行后会在屏幕上返回结果:1。在上面代码中,-Xscript后面跟着的名称Upper1是你自己定义的main类名称,你愿意起个名字叫Upper2,也是可以的。

好了,还记得我们在前面给大家介绍的HelloWorld程序吗?当时用的是一个包含了main()方法的大家比较熟悉的JVM应用程序,这种方式是本教程中推荐使用的方式,现在,我们采用这种方式重新编写上面的代码。

请在Linux系统的Shell命令提示符状态下,进入到“/usr/local/scala/mycode”目录下,然后使用vim编辑器新建一个TestCounterJVM.scala代码文件,如下:

cd /usr/local/scala/mycode
vim TestCounterJVM.scala

在TestCounterJVM.scala中输入以下代码:

class Counter {
    private var value = 0
    def increment(): Unit = { value += 1}
    def current(): Int = {value}
}
object MyCounter{
    def main(args:Array[String]){
        val myCounter = new Counter
        myCounter.increment()
        println(myCounter.current)
    }
}

保存后退出vim编辑器。然后,使用scalac命令编译这个代码文件,并用scala命令执行,如下:

scalac TestCounterJVM.scala
scala -classpath . MyCounter //MyCounter是包含main方法的对象名称,这里不能使用文件名称TestCounterJVM

上面命令执行后,会在屏幕输出“1”。

现在我们对之前的类定义继续改进一下,让方法中带有参数。我们可以修改一下TestCounterJVM.scala文件:

class Counter {
    private var value = 0
    def increment(step: Int): Unit = { value += step}
    def current(): Int = {value}
}
object MyCounter{
    def main(args:Array[String]){
        val myCounter = new Counter
        myCounter.increment(5) //这里设置步长为5,每次增加5
        println(myCounter.current)
    }
}

采用上面介绍的方法,编译执行这个文件,就可以得到执行结果是5。

getter和setter方法

下面我们来看一下如何给类中的字段设置值以及读取值。我们知道,在Java中,这是通过getter和setter方法实现的。在Scala中,也提供了getter和setter方法的实现,但是并没有定义成getXxx和setXxx。

我们继续修改TestCounterJVM.scala文件:

class Counter {
    var value = 0 //注意这里没有private修饰符,从而让这个变量对外部可见
    def increment(step: Int): Unit = { value += step}
    def current(): Int = {value}
}
object MyCounter{
    def main(args:Array[String]){
        val myCounter = new Counter
        println(myCounter.value)  //不是用getXxx获取字段的值
        myCounter.value = 3 //不是用setXxx设置字段的值
        myCounter.increment(1) //这里设置步长为1,每次增加1
        println(myCounter.current)
    }
}

编译执行这个文件,就可以得到两行执行结果,第一行是0,第二行是4。
但是,我们都知道,在Java中,是不提倡设置这种公有(public)字段的,一般都是把value字段设置为private,然后提供getter和setter方法来获取和设置字段的值。那么,到了Scala中该怎么做呢?
我们先把value字段声明为private,看看会出现什么效果,继续修改TestCounterJVM.scala文件:

class Counter {
    private var value = 0  //增加了private修饰符,成为私有字段
    def increment(step: Int): Unit = { value += step}
    def current(): Int = {value}
}
object MyCounter{
    def main(args:Array[String]){
        val myCounter = new Counter
        println(myCounter.value)  //不是用getXxx获取字段的值
        myCounter.value = 3 //不是用setXxx设置字段的值
        myCounter.increment(1) //这里设置步长为1,每次增加1
        println(myCounter.current)
    }
}

现在我们去用scalac命令编译上面的代码,就会报错,会出现“error:variable value in class Counter cannot be accessed in Counter”这样的错误信息。因为,value字段前面用了修饰符private,已经成为私有字段,外部是无法访问的。
那么,value变成私有字段以后,Scala又没有提供getter和setter方法,怎么可以访问value字段呢?解决方案是,在Scala中,可以通过定义类似getter和setter的方法,分别叫做value和value_=,具体如下:

class Counter {
    private var privateValue = 0  //变成私有字段,并且修改字段名称
    def value = privateValue //定义一个方法,方法的名称就是原来我们想要的字段的名称
    def value_=(newValue: Int){
        if (newValue > 0) privateValue = newValue //只有提供的新值是正数,才允许修改
    }
    def increment(step: Int): Unit = { value += step}
    def current(): Int = {value}
}
object MyCounter{
    def main(args:Array[String]){
        val myCounter = new Counter
        println(myCounter.value)  //打印value的初始值
        myCounter.value = 3 //为value设置新的值
        println(myCounter.value)  //打印value的新值 
        myCounter.increment(1) //这里设置步长为1,每次增加1
        println(myCounter.current)
    }
}

编译执行这个文件,就可以得到三行执行结果,第一行是0,第二行是3,第三行是4。

辅助构造器

Scala构造器包含1个主构造器和若干个(0个或多个)辅助构造器。
我们首先认识一下辅助构造器。辅助构造器的名称为this,每个辅助构造器都必须调用一个此前已经定义的辅助构造器或主构造器。
下面定义一个带有辅助构造器的类,我们对上面的Counter类定义进行修改:

class Counter {
    private var value = 0 //value用来存储计数器的起始值
    private var name = "" //表示计数器的名称
    private var mode = 1 //mode用来表示计数器类型(比如,1表示步数计数器,2表示时间计数器)
    def this(name: String){ //第一个辅助构造器
        this() //调用主构造器
        this.name = name
    }
    def this (name: String, mode: Int){ //第二个辅助构造器
        this(name) //调用前一个辅助构造器
        this.mode = mode
    }
    def increment(step: Int): Unit = { value += step}
    def current(): Int = {value}
    def info(): Unit = {printf("Name:%s and mode is %d\n",name,mode)}
}
object MyCounter{
    def main(args:Array[String]){
        val myCounter1 = new Counter  //主构造器
        val myCounter2 = new Counter("Runner") //第一个辅助构造器,计数器的名称设置为Runner,用来计算跑步步数
        val myCounter3 = new Counter("Timer",2) //第二个辅助构造器,计数器的名称设置为Timer,用来计算秒数
        myCounter1.info  //显示计数器信息
        myCounter1.increment(1)     //设置步长  
        printf("Current Value is: %d\n",myCounter1.current) //显示计数器当前值
        myCounter2.info  //显示计数器信息
        myCounter2.increment(2)     //设置步长  
        printf("Current Value is: %d\n",myCounter2.current) //显示计数器当前值
        myCounter3.info  //显示计数器信息
        myCounter3.increment(3)     //设置步长  
        printf("Current Value is: %d\n",myCounter3.current) //显示计数器当前值

    }
}

编译执行上述代码后,得到如下结果:

Name: and mode is 1
Current Value is: 1
Name:Runner and mode is 1
Current Value is: 2
Name:Timer and mode is 2
Current Value is: 3

主构造器

Scala的每个类都有主构造器。但是,Scala的主构造器和Java有着明显的不同,Scala的主构造器是整个类体,需要在类名称后面罗列出构造器所需的所有参数,这些参数被编译成字段,字段的值就是创建对象时传入的参数的值。
对于上面给计数器设置name和mode的例子,刚才我们是使用辅助构造器来对name和mode的值进行设置,现在我们重新来一次,这次我们转而采用主构造器来设置name和mode的值。

class Counter(val name: String, val mode: Int) {
    private var value = 0 //value用来存储计数器的起始值    
    def increment(step: Int): Unit = { value += step}
    def current(): Int = {value}
    def info(): Unit = {printf("Name:%s and mode is %d\n",name,mode)}
}
object MyCounter{
    def main(args:Array[String]){       
        val myCounter = new Counter("Timer",2)
        myCounter.info  //显示计数器信息
        myCounter.increment(1)  //设置步长  
        printf("Current Value is: %d\n",myCounter.current) //显示计数器当前值       
    }
}

编译执行上述代码后,得到如下结果:

Name:Timer and mode is 2
Current Value is: 1

子雨大数据之Spark入门
扫一扫访问本博客