scala和Java、Python一样是面向对象语言,本文讲解scala面向对象中的类与对象相关概念与实例。
类和对象是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实现的。上述increment()是方法,没有参数,冒号后面的Unit是表示返回值的类型,在Scala中不返回任何值,那么就用Unit表示,相当于Java中的void类型。方法的返回值,不需要靠return语句,方法里面的最后一个表达式的值就是方法的返回值,比如,上面current()方法里面只有一条语句“value”,那么,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
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)
}
}
现在我们去用scalac命令编译上面的代码,就会报错,因为,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
单例对象
Scala并没有提供Java那样的静态方法或静态字段,但是,可以采用object关键字实现单例对象,具备和Java静态方法同样的功能。
object Person {
private var lastId = 0 //一个人的身份编号
def newPersonId() = {
lastId +=1
lastId
}
}
从上面的定义可以看出,单例对象的定义和类的定义很相似,明显的区分是,用object关键字,而不是用class关键字。
假设有一个班级人员管理系统,每当新来一个班级成员,就给分配一个身份编号。当第一个人加入班级时,你就可以调用Person.newPersonId()获得身份编号。
伴生对象
在Java中,我们经常需要用到同时包含实例方法和静态方法的类,在Scala中可以通过伴生对象来实现。当单例对象与某个类具有相同的名称时,它被称为这个类的“伴生对象”。类和它的伴生对象必须存在于同一个文件中,而且可以相互访问私有成员(字段和方法)。
下面通过一个实例演示一下伴生对象的使用方法。在“/usr/local/scala/mycode”目录下,用vim编辑器重新创建一个test.scala,在该文件中输入如下代码:
class Person {
private val id = Person.newPersonId() //调用了伴生对象中的方法
private var name = ""
def this(name: String) {
this()
this.name = name
}
def info() { printf("The id of %s is %d.\n",name,id)}
}
object Person {
private var lastId = 0 //一个人的身份编号
private def newPersonId() = {
lastId +=1
lastId
}
def main(args: Array[String]){
val person1 = new Person("xiaoming")
val person2 = new Person("xiaohong")
person1.info()
person2.info()
}
}
伴生对象中定义的newPersonId()实际上就实现了Java中静态(static)方法的功能,所以,实例化对象person1调用newPersonId()返回的值是1,实例化对象person2调用newPersonId()返回的值是2。我们说过,Scala源代码编译后都会变成JVM字节码,实际上,在编译上面的源代码文件以后,在Scala里面的class和object在Java层面都会被合二为一,class里面的成员成了实例成员,object成员成了static成员。
应用程序对象
每个Scala应用程序都必须从一个对象的main方法开始,我们之前介绍的HelloWorld程序就是一个非常典型的例子。
我们可以在“/usr/local/scala/mycode”目录下,用vim编辑器重新创建一个test.scala,在该文件中输入如下代码:
object HelloWorld {
def main(args: Array[String]){
println("Hello, World!")
}
}
为了运行上述代码,我们现在可以使用两种不同的方法。
第一种方法:直接使用scala命令运行得到结果。
因为这段代码中没有定义类,就是一个单例对象,因此,可以不用编译,直接使用scala命令运行得到结果,命令如下:
cd /usr/local/scala/mycode
scala test.scala
apply & update
我们经常会用到对象的apply方法和update方法,虽然我们表面上并没有察觉,但是,实际上,在Scala中,apply方法和update方法都会遵循相关的约定被调用,约定如下:用括号传递给变量(对象)一个或多个参数时,Scala 会把它转换成对apply方法的调用;与此相似的,当对带有括号并包括一到若干参数的对象进行赋值时,编译器将调用对象的update方法,在调用时,是把括号里的参数和等号右边的对象一起作为update方法的输入参数来执行调用。
下面我们测试一下apply方法是否被调用。可以在Linux系统的“/usr/local/scala/mycode/test.scala”文件中输入以下代码:
class TestApplyClass {
def apply(param: String): String = {
println("apply method called, parameter is: " + param)
"Hello World!"
}
}
val myObject = new TestApplyClass
println(myObject("param1"))
运行后会得到以下结果:
apply method is called, parameter is:param1
Hello World!
如果把println(myObject(“param1”))这个语句给注释掉,就不会出现上面结果。可以看出,apply方法确实被调用了,而且是在执行myObject(“param1”)时被调用的,调用后,会把“Hello World!”作为返回值,因此会打印出“Hello World”。
前面介绍了apply方法,实际上,update方法也是类似的(感兴趣的读者,可以查找网络资料学习update方法如何测试,这里不再赘述),比如:
val myStrArr = new Array[String](3) //声明一个长度为3的字符串数组,每个数组元素初始化为null
myStrArr(0) = "BigData" //实际上,调用了伴生类Array中的update方法,执行myStrArr.update(0,"BigData")
myStrArr(1) = "Hadoop" //实际上,调用了伴生类Array中的update方法,执行myStrArr.update(1,"Hadoop")
myStrArr(2) = "Spark" //实际上,调用了伴生类Array中的update方法,执行myStrArr.update(2,"Spark")
从上面可以看出,在进行元组赋值的时候,之所以没有采用Java中的方括号myStrArr[0],而是采用圆括号的形式,myStrArr(0),是因为存在上述的update方法的机制。
参考链接:
http://dblab.xmu.edu.cn/blog/spark/
https://www.runoob.com/scala/scala-tutorial.html
历史推荐
妈妈再也不用担心双系统安装了!
Spark机器学习:模型评估指标
Spark机器学习:频繁模式挖掘
爬虫实战:Selenium爬取京东商品
爬虫实战:豆瓣电影top250爬取
爬虫实战:Scrapy框架爬取QQ音乐
数据分析与挖掘
数据结构与算法
机器学习与大数据组件
欢迎关注,感谢“在看”,随缘稀罕~