Scala学习-类和对象

时间:2023-01-25 23:10:39

1. 创建类

创建一个脚本Hyena.scala,内容如下:

yqtao@yqtao:~/scala$ cat Hyena.scala

class Hyena {
println("this is an class body")
//定义了方法go()
def go():String={"go home"}
}
//生成对象
val hyena = new Hyena
//调用函数
val h = hyena.go()
println(h)

执行:

yqtao@yqtao:~/scala$ scala Hyena.scala
this is an class body
go home

注意:Scala 的缺省修饰符为public,也就是如果不带有访问范围的修饰符 public 、protected、private 等,Scala 将默认定义为 public 。类的方法以 def 定义开始,要注意的Scala的方法的参数都是 val类型,而不是 var 类型,因此在函数体内不可以修改参数的值。

例如,定义一个加法类:

yqtao@yqtao:~/scala$ cat Sum.scala 
class Sum {
private var sum=0
def add(b:Byte)={
b=1
sum+=b
}
}

运行:

yqtao@yqtao:~/scala$ scala Sum.scala 
/home/yqtao/scala/Sum.scala:4: error: reassignment to val
b=1
^
one error found

可以看到其报错:重新赋值给val常量。

再强调一下,Scala 代码无需使用 ; 结尾,也不需要使用 return 返回值,函数的最后一行的值就作为函数的返回值。

但如果你需要在一行中书写多个语句,此时需要使用 ; 隔开,不过不建议这么做。你也可以把一条语句分成几行书写,Scala 编译器大部分情况下会推算出语句的结尾,不过这样也不是一个好的编码习惯。

2. 类参数

Scala 的类定义可以有参数,称为类参数,如下面程序中的a,b,c, Scala 使用类参数,并把类定义和主构造函数合并在一起,在定义类的同时也定义了类的主构造函数。因此 Scala 的类定义相对要简洁些。

yqtao@yqtao:~/scala$ cat ClassArg.scala
class ClassArg(a:Int,b:Int,c:Int){
def add():Int={a+b+c}
}
val ret = new ClassArg(1,2,3).add()
println(ret)

运行:

yqtao@yqtao:~/scala$ scala ClassArg.scala 
6

可以使用可变元参数列表来支持任意数量的参数,方法是在末尾加上*,如下所示:

yqtao@yqtao:~/scala$ cat ClassArg.scala
class ClassArg(args:Int*){
def add():Int={
var total = 0
for (i <- args) {
total += i
}
total
}
}
val ret = new ClassArg(1,2,3,4,5).add()
println(ret)

运行:

yqtao@yqtao:~/scala$ scala ClassArg.scala 
15

在类参数中,构造器默默的初始化参数,并使得他们对其他对象是可访问的,在类参数指定的下,我们不需要编写构造器的代码,Scala会为我们做这件事。就像C++中的默认构造函数一样。
如下面的一个例子:

yqtao@yqtao:~/scala$ cat Coffee.scala

class Coffee(val shots:Int = 2,
val decaf:Int = 0,
val milk:Boolean = false,
val toGo:Boolean = false,
val syrup:String = "") {
var result = ""
println(caf, decaf, milk, toGo, syrup)
def getCup():Unit = {
if(toGo) {
result += "ToGoCup "
} else {
result += "HereCup "
}
}
def caf():Int = {
shots - decaf
}
def pourShots():Unit = {
for(s <- 0 until shots) {
if(decaf > 0) {
result += "decaf shot "
} else {
result += "shot "
}
}
}
def addMilk():Unit = {
if(milk) {
result += "milk "
}
}
def addSyrup():Unit = {
result += syrup
}
getCup()
pourShots()
addMilk()
addSyrup()
}
val usual = new Coffee
println(usual.result)

运行:

yqtao@yqtao:~/scala$ scala Coffee.scala
(2,0,false,false,)
HereCup shot shot

这里有一些神奇的地方:
1. 类参数中定义变量可以被类中的函数使用,并且使用了默认的缺省值
2. 在类中进行了函数的调用,所以result的值不为空
3. val usual = new Coffee使用了类参数的缺省值构造对象

3. 辅助构造器

在定义类时,很多时候需要定义多个构造函数,在 Scala 中,除主构造函数之外的构造函数都称为辅助构造函数(或是从构造函数)。
注意:任何辅助构造函数都必须对主构造函数或对另一个辅助构造函数调用。

  1 class Rational(n:Int,d:Int){
2 require(d!=0)
3 def this(n:Int)={
4 this(n,1)
5 }
6 }

上面的this函数就是辅助构造函数,调用了主构造函数。require为条件即d!=0.

4. 定义对象运算

如下定义了一个有理数的类,其中定义了两个对象的+,*法操作:

yqtao@yqtao:~/scala$ cat Rational.scala
class Rational (n:Int, d:Int) {
require(d!=0)
private val g =gcd (n.abs,d.abs)
val numer =n/g
val denom =d/g
override def toString = numer + "/" +denom
def +(that:Rational) =
new Rational(
numer * that.denom + that.numer* denom,
denom * that.denom
)
def * (that:Rational) =
new Rational( numer * that.numer, denom * that.denom)
def this(n:Int) = this(n,1)
private def gcd(a:Int,b:Int):Int =
if(b==0) a else gcd(b, a % b)
}
val x = new Rational(1,10)
val y = new Rational(3,10)
val z = x + y
println(x)
println(y)
println(z)
val d = x*y
println(d)

看一下这个:

def +(that:Rational)  =
new Rational(
numer * that.denom + that.numer* denom,
denom * that.denom
)

定义了一个+运算符,另个类加法生成一个新的类,然后用主构造函数进行初始化。

运行结果如下:

yqtao@yqtao:~/scala$ scala Rational.scala
1/10
3/10
2/5
3/100

5. 方法重载

同C++一样,Scala也支持重载函数,最简单的重载方式:

// 注意可以类型推导不用写返回值
def add(a:Int,b:Int)={a+b}
def add(a:Double,b:Double)={a+b}

下面对+进行重载:

def + (i:Int) =
new Rational (numer + i * denom, denom)

上面的是一个有理数类,其中,numer为分子,denom为分母,其余代码在下面,这里定义了+操作,可以实现x+1这样的操作,返回一个新的对象。

yqtao@yqtao:~/scala$ cat Rational.scala
class Rational (n:Int, d:Int) {
require(d!=0)
private val g =gcd (n.abs,d.abs)
val numer =n/g
val denom =d/g
override def toString = numer + "/" +denom
def +(that:Rational) =
new Rational(
numer * that.denom + that.numer* denom,
denom * that.denom
)
def * (that:Rational) =
new Rational( numer * that.numer, denom * that.denom)
def this(n:Int) = this(n,1)
private def gcd(a:Int,b:Int):Int =
if(b==0) a else gcd(b, a % b)
def + (i:Int) =
new Rational(numer + i*denom,denom)
}
val x = new Rational(1,10)
val y = x + 1
println(y)

运行:

yqtao@yqtao:~/scala$ scala Rational.scala
11/10

但是如果写成2+x则会报错,因为2不是一个有理数的对象,因此不能使用其+.

yqtao@yqtao:~/scala$ scala Rational.scala
/home/yqtao/scala/Rational.scala:21: error: overloaded method value + with alternatives:
(x: Double)Double <and>
(x: Float)Float <and>
(x: Long)Long <and>
(x: Int)Int <and>
(x: Char)Int <and>
(x: Short)Int <and>
(x: Byte)Int <and>
(x: String)String
cannot be applied to (this.Rational)
val y = 2 + x
^
one error found

如果 Int 类型能够根据需要自动转换为 Rational 类型,那么 3 + x 就可以相加。Scala 通过 implicit def 定义一个隐含类型转换,比如定义由整数到 Rational 类型的转换如下:

implicit def intToRational(x:Int) = new Rational(x)

其实此时 Rational 的一个 + 重载方法是多余的, 当Scala计算 2 + r ,发现 2(Int) 类型没有可以和 Rational 对象相加的方法,Scala 环境就检查 Int 的隐含类型转换方法是否有合适的类型转换方法,类型转换后的类型支持 + r ,一检查发现定义了由 Int 到 Rational 的隐含转换方法,就自动调用该方法,把整数转换为 Rational 数据类型,然后调用 Rational 对象的 + 方法。

参考资料:
1. 《scala编程思想》
2. https://www.shiyanlou.com/courses/490