类和无参构造器

在Scala中,类并不用声明为public;

Scala源文件中可以包含多个类,所有这些类都具有公有可见性;

val修饰的变量(常量),值不能改变,只提供getter方法,没有setter方法;

var修饰的变量,值可以改变,对外提供getter、setter方法;

如果没有定义构造器,类会有一个默认的无参构造器;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
package lagou.cn.part04

//在Scala中,类都有一个无参构造器
class Person {
//声明字段必须进行初始化,Scala编译器会根据初始化值的数据类型自动推断字段的类型,字段类型可以省略
var name = "lagou"

//_表示一个占位符,编译器会根据变量的数据类型赋予相应的初始值
//注意:使用占位符_进行赋初始值时,数据类型必须指定
var nickName: String = _
var numInt: Int = _
var numDouble: Double = _
var boolean: Boolean = _

//val修饰的变量不能使用占位符
// val test:Int=_

val num = 30

var age = 20

//如果赋值为null,就需要添加数据类型。如果不添加数据类型,那么就会认为是Null类型的。
var address: String = null
//类中的私有字段,有私有的getter和setter方法
//可以在类的内部访问,也可以被其伴生对象访问
private var hobby = "旅游"

//对象私有字段,访问权限更小,只能在当前类中访问
private[this] val cardInfo = "10010"

//自定义方法
def hello(message: String): Unit = {
//只能在当前类中访问cardInfo
println(s"$message, $cardInfo")
}

//自定义方法实现两数求和
def addNum(num1: Int, num2: Int): Int = {
num1 + num2
}
}

object ClassDemo {
def main(args: Array[String]): Unit = {
//使用类的无参构造器来创建对象
val person = new Person()
//小括号也是可以省略的
val person2 = new Person
println(s"${person.nickName} ${person.numInt} ${person.numDouble} ${person.boolean}")

//给类的属性赋值
person.age = 50
//注意:如果使用对象的属性加上 _= 给var修饰的属性进行赋值,其实就是调用age_=这个settter方法
person.age_=(20)
//调用类的属性,其实就是调用它的getter方法
println(person.age)

//调用类中的方法
person.hello("hello")
println(person.addNum(10, 20))

}
}

自定义getter和setter方法

对于 Scala 类中的每一个属性,编译后会有一个私有的字段和相应的getter、setter方法生成。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package lagou.cn.part04

class Dog {
private var _leg = 0

//自定义getter方法
def leg: Int = _leg

//自定义的setter方法
def leg_=(newLeg: Int): Unit = {
_leg = newLeg
}
}

object GetterAndSetterDemo {
def main(args: Array[String]): Unit = {
val dog = new Dog
//调用自定义的setter方法
dog.leg_=(4)
//调用自定义的getter方法
println(dog.leg)
}
}

自定义变量的getter和setter方法需要遵循以下原则:

- 字段属性名以“_”作为前缀,如: _leg

- getter方法定义为:def leg = _leg

- setter方法定义为:def leg_=(newLeg: Int)

Bean属性

JavaBean规范把Java属性定义为一堆getter和setter方法。

类似于Java,当将Scala字段标注为 @BeanProperty时,getFoo和setFoo方法会自动生成。

使用@BeanProperty并不会影响Scala自己自动生成的getter和setter方法。

在使用时需要导入包scala.beans.BeanProperty

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package lagou.cn.part04

//需要导入下面的包
import scala.beans.BeanProperty

class Teacher {
@BeanProperty
var name: String = _
}

object BeanDemo {
def main(args: Array[String]): Unit = {
val teacher=new Teacher
teacher.name="jacky"
teacher.name_=("tom")
println(teacher.name)
//BeanProperty生成的setName方法
teacher.setName("lisi")
//BeanProperty生成的getName方法
println(teacher.getName)
}
}

上述Teacher类中共生成了四个方法:

  1. name: String

  2. name_= (newValue: String): Unit

  3. getName(): String

  4. setName (newValue: String): Unit

构造器

如果没有定义构造器,Scala类中会有一个默认的无参构造器;

Scala当中类的构造器分为两种:主构造器和辅助构造器;

主构造器的定义与类的定义交织在一起,将主构造器的参数直接放在类名之后。

当主构造器的参数不用var或val修饰时,参数会生成类的私有val成员。

Scala中,所有的辅助构造器都必须调用另外一个构造器,另外一个构造器可以是辅助构造器,也可以是主构造器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package lagou.cn.part04

//主构造器与类名交织在一起,类名后面的参数就是主构造器的参数
//主构造器直接在类中,其代码不包含在任何方法中
class Animal(name: String, age: Int) {
//类中不在任何方法中的代码,都属于主构造器的代码。
//创建类的对象时会去执行主构造器的代码。下面的println代码就是主构造器的一部分
println(name)
println(age)
println("=========================")

var gender: String = ""

def this(name: String, age: Int, gender: String) {
//每个辅助构造器必须以主构造器或其他辅助构造器的调用作为第一句代码
this(name, age)
this.gender = gender
}

var color: String = ""

def this(name: String, age: Int, gender: String, color: String) {
//此处调用的是上面的辅助构造器
this(name, age, gender)
this.color = color
}
}

object ConstructorDemo {
def main(args: Array[String]): Unit = {
val animal = new Animal("狗蛋", 4)
val animal2 = new Animal("旺才", 3, "雄性")
val animal3 = new Animal("小六", 5, "雄性", "黑色")
}
}

对象

单例对象

Scala并没有提供Java那样的静态方法或静态字段;

可以采用object关键字实现单例对象,具备和Java静态方法同样的功能;

使用object语法结构【object是Scala中的一个关键字】达到静态方法和静态字段的目的;对象本质上可以拥有类的所有特性,除了不能提供构造器参数;

对于任何在Java中用单例对象的地方,在Scala中都可以用object实现:

  • 作为存放工具函数或常量的地方

  • 高效地共享单个不可变实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package lagou.cn.part04

object Object {
println("这是单例对象的代码!")

def printInfo: Unit = {
println("Hello Scala Object!")
}
}

object ObjectDemo {
def main(args: Array[String]): Unit = {
// val object1=Object
// val object2=Object

Object.printInfo
Object.printInfo
}
}

结果:

1
2
3
这是单例对象的代码!
Hello Scala Object!
Hello Scala Object!

Scala中的单例对象具有如下特点:

1、创建单例对象不需要使用new关键字

2、object中只有无参构造器

3、主构造代码块只能执行一次,因为它是单例的

伴生类与伴生对象

当单例对象与某个类具有相同的名称时,它被称为这个类的“伴生对象”;

类和它的伴生对象必须存在于同一个文件中,而且可以相互访问私有成员(字段和方法);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package lagou.cn.part04

//伴生类和伴生对象,它们的名字是一样的,并且必须存在于同一文件中
class ClassObject {
private var name = "lagou"

def printInfo: Unit = {
//在伴生类中可以访问伴生对象的私有成员
println(ClassObject.num)
println("Hello Object!")
}
}

object ClassObject {
private val num = 10

def main(args: Array[String]): Unit = {
val classObject = new ClassObject
//在伴生对象中可以访问伴生类的私有成员
println(classObject.name)
classObject.printInfo
}
}

应用程序对象

每个Scala应用程序都必须从一个对象的main方法开始,这个方法的类型为 Array[String] => Unit;

备注:main方法写在class中是没有意义的,在IDEA中这样的 class 连run的图标都不能显示除了main方法以外,也可以扩展App特质(trait)

1
2
3
4
5
6
object Hello extends App {
if (args.length > 0)
println(s"Hello World; args.length = ${args.length}")
else
println("Hello World")
}

apply方法

object 中有一个非常重要的特殊方法 – apply方法;

  • apply方法通常定义在伴生对象中,目的是通过伴生类的构造函数功能,来实现伴生对象的构造函数功能;

  • 通常我们会在类的伴生对象中定义apply方法,当遇到类名(参数1,…参数n)时apply方法会被调用

  • 在创建伴生对象或伴生类的对象时,通常不会使用new class/class() 的方式,而是直接使用class()隐式的调用伴生对象的 apply 方法,这样会让对象创建的更加简洁;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package lagou.cn.part04

class Student(name: String, age: Int) {
private var gender: String = _

def sayHi(): Unit = {
println(s"大家好,我是$name,$gender 生")
}
}

object Student {
//apply方法需要定义在伴生对象中
def apply(name: String, age: Int): Student = new Student(name, age)

def main(args: Array[String]): Unit = {
//直接使用class(参数...)这种方式隐式调用伴生对象中的apply方法来创建class Student对象
val student = Student("jacky", 30)
//伴生类与伴生对象可以相互访问私有成员
student.gender = "男"

student.sayHi()
}
}

问题:在Scala中实现工厂方法,让子类声明哪种对象应该被创建,保持对象创建在同一位置。例如,假设要创建Animal工厂,让其返回Cat和Dog类的实例,基于这个需求,通过实现Animal伴生对象的apply方法,工厂的使用者可以像这样创建新的Cat和Dog实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
abstract class Animal {
def speak
}

class Dog extends Animal {
override def speak: Unit = {
println("woof")
}
}

class Cat extends Animal {
override def speak: Unit = {
println("meow")
}
}

object Animal {
def apply(str: String): Animal = {
if (str == "dog")
new Dog
else
new Cat
}

def main(args: Array[String]): Unit = {
val cat = Animal("cat")
cat.speak val dog = Animal("dog")
dog.speak
}
}