在Scala里,类是用关键字“class”开头的代码定义,它是用于创建对象的蓝图。一个类就是一个类型,不同的类就是不同的类型,一个对象的类型就是创建它用的那个类。
在类里可以定义val或var
类型的变量,它们被称为“字段”
;还可以定义函数
,它们被称为“方法”
;此外还可以定义:常量、类型、对象、特质、类等等
,这些在类里定义的东西统称为“成员”
。
创建对象时是使用new
关键词,外部想要访问对象的成员时,可以使用句点符号“ . ”
,通过“对象.成员”
的形式来访问。此外,用new构造出来的对象还可以赋给变量,让变量名成为该对象的一个指代名称。
class Students {
var name = "None"
var age : Int = _ //会设置为默认值0
var average_score : Double = _ //会设置为默认值0.0
def register(n: String,a: Int,as: Double): Unit = {
name = n
age = a
average_score = as
}
}
val stu = new Students
println(stu.name)//None
println(stu.age )//0
println(stu.average_score )//0.0
stu.register("Alex",18,89.5)
println(stu.name)//None
println(stu.age )//18
println(stu.average_score )//89.5
注意,在Scala中,类不用声明为public,它就是公有的。Scala源文件可以包含多个类,所有这些类都具有公有可见性。类的成员也可以是公有public
,或者私有private
,或者protected类型
,它们主要影响继承和访问,这个在学习类继承时再说。
Scala不需要显式的定义主构造方法
,而是把类内部非字段、非方法的代码都当作“主构造方法”
。而且,类名后面可以定义若干个参数列表,用于接收参数,这些参数将在构造对象时用于初始化字段并传递给主构造方法使用。Scala的这种独特语法减少了一些代码量。例如:
class Students(n: String,a: Int,as: Double) {
val name = n
val age = a
val average_score = as
println(n + "'s age and average_score is " + age + " " + average_score)
}
//创建对象时会打印Alex's age and average_score is 18 89.5
val stu = new Students("Alex",18,89.5)
在这个例子中,Students类接收三个参数来初始化对象,这三个参数叫做类参数。这样做,就无需像之前那样把字段定义成var类型
,先创建对象再对字段一一赋值。而是使用函数式风格的val类型
,在创建对象时直接给出各字段的值。当然,如果真的有创建对象后修改字段值的需求,也可以仍然定义为var类型
。
函数println既不是字段,也不是方法定义,所以被当成是主构造函数的一部分。在构造对象时,主构造函数被执行,因此会打印相关信息。
val/var
修饰类参数从前面的例子可以发现,很多时候类的参数仅仅是直接赋给某些字段。Scala为了进一步简化代码,允许在类参数前加上val或var
来修饰,这样就会在类的内部会生成一个与参数同名的公有字段。构造对象时,这些参数会直接复制给同名字段。除此之外,还可以加上关键字private、protected或override来表明字段的权限
。
如果参数没有任何关键字,那它就仅仅是“参数”,不是类的成员,只能用来初始化字段或给方法使用。外部不能访问这样的参数,内部也不能修改它。例如:
scala> class Students(val name: String, var score: Int) {
| def exam(s: Int) = score = s
| override def toString = name + "'s score is " + score + "."
| }
defined class Students
scala> val stu = new Students("Tim", 90)
stu: Students = Tim's score is 90.
scala> stu.exam(100)
scala> stu.score
res0: Int = 100
除了主构造方法,还可以定义若干个辅助构造方法。辅助构造方法都是以“def this(......)”
开头,而且方法中第一句必须是调用该类的另一个构造方法,即第一条语句必须是“this(......)”
—可以是主构造方法,也可以是另一个辅助构造方法。
这种规则的结果就是任何构造方法最终都会调用到该类的主构造方法,使得主构造方法成为类的单一入口。例如:
class Person {
var name: String = _
var age: Int = _
//辅助构造器无论是直接或间接,最终都一定要调用主构造器,执行主构造器的逻辑
//而且需要放在辅助构造器的第一行
//辅助构造器 1
def this(name : String) {
this() //直接调用主构造器
this.name = name
}
//辅助构造器 2
def this(name : String, age : Int) {
this() //直接调用主构造器
this.name = name
this.age = age
}
//辅助构造器 3
def this(age : Int) {
this("匿名") //间接调用主构造器,因为 def this(name : String) 中调用了主构造器!
this.age = age
}
def showInfo(): Unit = {
println("person信息如下:")
println("name=" + this.name)
println("age=" + this.age)
}
}
因为Scala没有指针,同时使用了Java的垃圾回收器,所以不需要像C++那样定义析构函数。
如果在类名与类的参数列表之间加上关键字“private”
,那么主构造方法就是私有的
,只能被内部定义访问,外部代码构造对象时就不能通过主构造方法进行,而必须使用其他公有的辅助构造方法或工厂方法(专门用于构造对象的方法)。例如:下例定义了两个辅助构造函数,还有一个主构造函数,其中只有一个辅助构造函数this(s: Int)
不是私有的,所以在创建对象时只能使用这一个构造函数,使用另外两个都会出错。
class Students private (n: String, s: Int) {
val name = n
val score = s
def this(s: Int) = this("Alex", s)
private def this(n: String) = this(n, 100)
println(name + "'s score is " + score)
}
在Scala里,除了用new可以构造一个对象,也可以用“object”
直接定义一个对象。它类似于类的定义,只不过不能像类那样有参数,也没有构造方法。因此,不能用new来实例化一个object的定义,因为它已经是一个对象了。这种对象和用new实例化出来的对象没有什么区别,只不过new实例的对象是以类为蓝本构建的,并且数量没限制,而object定义的对象只能有这一个,故而得名“单例对象”
。
如果某个单例对象和某个类同名,那么这个单例对象又可称为这个类的“伴生对象”
,也即伴生对象
是特殊的单例对象。同样,类称为这个单例对象的“伴生类”
。伴生类和伴生对象必须在同一个文件里,而且两者可以互访对方所有成员。在C++、Java等oop语言里,类内部可以定义静态变量。这些静态变量不属于任何一个用new实例化的对象,而是它们的公有部分。Scala追求纯粹的面向对象属性,即所有的事物都是类或对象,但是静态变量这种不属于类也不属于对象的事物显然违背了Scala的理念。所以,Scala的做法是把类内所有的静态变量从类里移除,转而集中定义在伴生对象里,让静态变量属于伴生对象这个独一无二的对象。
注意,上面说的是互访所有成员,也即包括公有和私有。其实伴生对象里的公有成员也可以被其他非伴生类访问,因此最大的用处其实在于伴生类和伴生对象互访私有成员。还有就是两者通过定义成相同的名称,显式的表明伴生类的静态成员都在这个伴生对象中,这样定义更规范。更加详细的理解可以看下面将要举的例子。
既然单例对象和new实例的对象一样,那么类内可以定义的代码,单例对象同样可以拥有。例如,单例对象里面可以定义字段和方法。Scala允许在类里定义别的类和单例对象,所以单例对象也可以包含别的类和单例对象的定义。因此,单例对象除了用作伴生对象,通常也可以用于将某方面功能的函数打包成一个工具集,或者包含主函数成为程序的入口。
“object”后面定义的单例对象名可以认为是这个单例对象的名称标签,因此可以通过句点符号访问单例对象的成员——“单例对象名.成员”
,也可以赋给一个变量——“val 变量 = 单例对象名”
,就像用new实例的对象那样。
举例:
object main {
def main(args: Array[String]): Unit = {
/** ************************************ */
//伴生对象
object Person { //
private val sex : String = "Boy"
private val salary : Int = 30
val p = new Person
def showinfo(): Unit = {
println("Hello I am "+ p.name + ", my age is " + p.age)
}
}
//伴生类
class Person {
val name : String = "Alex"
private val age : Int = 18
def print() = Person.showinfo()
def show_private_info(): Unit = {
println("my sex is "+ Person.sex + ", my salary is " + Person.salary + "k")
}
}
val p = new Person
p.print()
p.show_private_info()
/** ************************************ */
}
}
Hello I am Alex, my age is 18
my sex is Boy, my salary is 30k
可以看到,伴生对象的私有成员Person.sex
和Person.salary
,在伴生类中都可以访问到,但是在伴生类的对象和子类中是不能访问上述两个私有成员的;伴生类的私有成员,在伴生对象中也可以访问到,如p.age
。
但是当两者不是伴生关系时(把两者的名字改成了两个不同的名字),公有成员依旧可以访问,但是私有成员的访问就会报错。
如果定义一个方法专门用来构造某一个类的对象,那么这种方法就称为“工厂方法”
。包含这些工厂方法集合的单例对象,也就叫“工厂对象”
。通常,工厂方法会定义在伴生对象里。使用工厂方法的好处是可以不用直接使用new来实例化对象,改用方法调用,而且方法名可以是任意的,这样对外隐藏了类的实现细节。例如:
// students.scala
class Students(val name: String, var score: Int) {
def exam(s: Int) = score = s
override def toString = name + "'s score is " + score + "."
}
object Students {
def registerStu(name: String, score: Int) = new Students(name, score)
}
val stu = Students.registerStu("Alex",100)
println(stu.name)//Alex
println(stu.score)//100
有一个特殊的方法名——apply,如果定义了这个方法,那么既可以显式调用——“对象.apply(参数)” ,也可以隐式调用——“对象(参数)”。隐式调用时,编译器会自动插入缺失的“.apply”。如果apply是无参方法,应该写出空括号,否则无法隐式调用。无论是类还是单例对象,都能定义这样的apply方法。
通常,在伴生对象里定义名为apply的工厂方法,就能通过“伴生对象名(参数)”来构造一个对象。也常常在类里定义一个与类相关的、具有特定行为的apply方法,让使用者可以隐式调用,进而隐藏相应的实现细节。例如:
// students2.scala
class Students2(val name: String, var score: Int) {
def apply(s: Int) = score = s
def display() = println("Current score is " + score + ".")
override def toString = name + "'s score is " + score + "."
}
object Students2 {
def apply(name: String, score: Int) = new Students2(name, score)
}
val stu2 = Students2("Jack", 60)
stu2(80)
stu2.display//Current score is 80.
其中,“Students2("Jack", 60)”
被翻译成“Students2.apply("Jack", 60)”
,也就是调用了伴生对象里的工厂方法,所以构造了一个Students2
的对象并赋给变量stu2
。“stu2(80)”
被翻译成“stu2.apply(80)”
,也就是更新了字段score
的数据。
参考:https://blog.csdn.net/qq_34291505/article/details/86760620