scala语言学习(六)、面向对象编程类、对象、继承、trait特质

scala面向对象编程之类

类的定义

scala是支持面向对象的,也有类和对象的概念。定义一个Customer类,并添加成员变量/成员方法。添加一个main方法,并创建Customer类的对象,并给对象赋值,打印对象中的成员,调用成员方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Customer {
var name:String = _
var sex:String = _
val registerDate:Date = new Date

def sayHi(msg:String) = {
println(msg)
}
}

object Main {
def main(args: Array[String]): Unit = {
val customer = new Customer
//给对象的成员变量赋值
customer.name = "张三"
customer.sex = "男"

println(s"姓名: ${customer.name}, 性别:${customer.sex}, 注册时间: ${customer.registerDate}")
//对象调用方法
customer.sayHi("你好!")
}
}

特说说明:

  1. var name:String = _表示使用默认值进行初始化, 例如:String类型默认值是null,Int类型默认值是0,Boolean类型默认值是false…
  2. val变量不能使用_来进行初始化,因为val是不可变的,所以必须手动指定一个默认值
  3. main方法必须要放在一个scala的object(单例对象)中才能执行

类的构造器

主构造器

主构造器是指在类名的后面跟上一系列参数,例如

1
2
3
class 类名(var/val 参数名:类型 = 默认值, var/val 参数名:类型 = 默认值){
// 构造代码块
}

辅助构造器

在类中使用this来定义,例如

1
2
3
def this(参数名:类型, 参数名:类型) {
...
}

演示

1
2
3
4
5
6
7
8
9
10
11
12
13
class Student(val name: String, val age: Int) {

val address: String = "beijing"
// 定义一个参数的辅助构造器
def this(name: String) {
// 辅助构造器的第一行必须调用主构造器或其他辅助构造器或者super父类的构造器
this(name, 20)
}

def this(age: Int) {
this("某某某", age)
}
}

scala面向对象编程之对象

scala中的object

scala中是没有Java中的静态成员的。如果将来我们需要用到static变量、static方法,就要用到scala中的单例对象object

定义object

定义单例对象和定义类很像,就是把class换成object

演示

定义一个工具类,用来格式化日期时间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
object DateUtils {

// 在object中定义的成员变量,相当于Java中定义一个静态变量
// 定义一个SimpleDateFormat日期时间格式化对象
val simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm")

// 构造代码
println("构造代码")

// 相当于Java中定义一个静态方法
def format(date:Date) = simpleDateFormat.format(date)

// main是一个静态方法,所以必须要写在object中
def main(args: Array[String]): Unit = {

println { DateUtils.format(new Date()) };
}
}

说明

  1. 使用object 单例对象名定义一个单例对象,可以用object作为工具类或者存放常量
  2. 在单例对象中定义的变量,类似于Java中的static成员变量
  3. 在单例对象中定义的方法,类似于Java中的static方法
  4. object单例对象的构造代码可以直接写在花括号中
  5. 调用单例对象的方法,直接使用单例对象名.方法名,访问单例对象的成员变量也是使用单例对象名.变量名
  6. 单例对象只能有一个无参的主构造器,不能添加其他参数

scala中的伴生对象

在同一个scala文件,有一个class和object具有同样的名字,那么就称这个object是class的伴生对象,class是object的伴生类,伴生类和伴生对象的最大特点是,可以相互访问;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class ClassObject {
val id = 1
private var name = "itcast"
def printName(): Unit ={
//在Dog类中可以访问伴生对象Dog的私有属性
println(ClassObject.CONSTANT + name )
}


}

object ClassObject{
//伴生对象中的私有属性
private val CONSTANT = "汪汪汪 : "
def main(args: Array[String]) {
val p = new ClassObject
//访问私有的字段name
p.name = "123"
p.printName()
}
}

说明

(1). 伴生类和伴生对象的名字必须是一样的
(2). 伴生类和伴生对象需要在一个scala源文件中
(3). 伴生类和伴生对象可以互相访问private的属性

scala中object的apply方法

我们之前使用过这种方式来创建一个Array对象。

1
2
// 创建一个Array对象
val a = Array(1,2,3,4)

这种写法非常简便,不需要再写一个new,然后敲一个空格,再写类名。如何直接使用类名来创建对象呢?查看scala源代码发现:

答案就是: 实现伴生对象的apply方法,伴生对象的apply方法用来快速地创建一个伴生类的对象。

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
class Person(var name:String, var age:Int) {

override def toString = s"Person($name, $age)"
}

object Person {
// 实现apply方法
// 返回的是伴生类的对象
def apply(name:String, age:Int): Person = new Person(name, age)

// apply方法支持重载
def apply(name:String):Person = new Person(name, 20)

def apply(age:Int):Person = new Person("某某某", age)

def apply():Person = new Person("某某某", 20)
}

object Main2 {
def main(args: Array[String]): Unit = {
val p1 = Person("张三", 20)
val p2 = Person("李四")
val p3 = Person(100)
val p4 = Person()

println(p1)
println(p2)
println(p3)
println(p4)
}
}

说明

  1. 当遇到类名(参数1, 参数2…)会自动调用apply方法,在apply方法中来创建对象
  2. 定义apply时,如果参数列表是空,也不能省略括号(),否则引用的是伴生对象

scala中object的main方法

scala和Java一样,如果要运行一个程序,必须有一个main方法。而在Java中main方法是静态的,而在scala中没有静态方法。在scala中,这个main方法必须放在一个object。

1
2
3
4
5
object Main1{
def main(args:Array[String]) = {
println("hello, scala")
}
}

也可以继承自App Trait(特质),然后将需要编写在main方法中的代码,写在object的构造方法体内。其本质是调用了Trait这个特质中的main方法。

1
2
3
object Main2 extends App {
println("hello, scala")
}

scala面向对象编程之继承

继承extends

scala和Java一样,使用extends关键字来实现继承。可以在子类中定义父类中没有的字段和方法,或者重写父类的方法。

示例1:实现简单继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Person1 {
var name = "super"

def getName = this.name
}

class Student1 extends Person1

object Main1 {
def main(args: Array[String]): Unit = {
val p1 = new Person1()
val p2 = new Student1()

p2.name = "张三"

println(p2.getName)
}
}

示例2:单例对象实现继承

1
2
3
4
5
6
7
8
9
10
11
12
13
class Person2 {
var name = "super"

def getName = this.name
}

object Student2 extends Person2

object Main2 {
def main(args: Array[String]): Unit = {
println(Student2.getName)
}
}

override和super

  • 如果子类要覆盖父类中的一个非抽象方法,必须要使用override关键字
  • 可以使用override关键字来重写一个val字段
  • 可以使用super关键字来访问父类的成员

示例1:class继承class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Person3 {
val name = "super"

def getName = name
}

class Student3 extends Person3 {
// 重写val字段
override val name: String = "child"

// 重写getName方法
override def getName: String = "hello, " + super.getName
}

object Main3 {
def main(args: Array[String]): Unit = {
println(new Student3().getName)
}
}

isInstanceOf和asInstanceOf

我们经常要在代码中进行类型的判断和类型的转换。在Java中,我们可以使用instanceof关键字、以及(类型)object来实现。
scala中对象提供isInstanceOfasInstanceOf方法。

  • isInstanceOf判断对象是否为指定类的对象
  • asInstanceOf将对象转换为指定类型
Java Scala
判断对象是否是C类型 obj instanceof C obj.isInstanceof[C]
将对象强转成C类型 (C ) obj obj.asInstanceof[C]
获取类型为T的class对象 C.class classOf[C]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Person4
class Student4 extends Person4

object Main4 {
def main(args: Array[String]): Unit = {
val s1:Person4 = new Student4

// 判断s1是否为Student4类型
if(s1.isInstanceOf[Student4]) {
// 将s1转换为Student3类型
val s2 = s1.asInstanceOf[Student4]
println(s2)
}

}
}

getClass和classOf

isInstanceOf 只能判断出对象是否为指定类以及其子类的对象,而不能精确的判断出,对象就是指定类的对象。如果要求精确地判断出对象就是指定类的对象,那么就只能使用getClass和 classOf 。

  • 对象.getClass可以精确获取对象的类型
  • classOf[x]可以精确获取类型
  • 使用==操作符就可以直接比较
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Person5
class Student5 extends Person5

object Student5{
def main(args: Array[String]) {
val p:Person5=new Student5
//判断p是否为Person5类的实例
println(p.isInstanceOf[Person5])//true

//判断p的类型是否为Person5类
println(p.getClass == classOf[Person5])//false

//判断p的类型是否为Student5类
println(p.getClass == classOf[Student5])//true
}
}

访问修饰符

Java中的访问控制,同样适用于scala,可以在成员前面添加private/protected关键字来控制成员的可见性。但在scala中,没有public关键字,任何没有被标为private或protected的成员都是公共的==。

private[this]修饰符

被修饰的成员只能在当前类中被访问。或者可以理解为:只能通过this.来访问(在当前类中访问成员会自动添加this。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Person6 {
// 只有在当前对象中能够访问
private[this] var name = "super"

def getName = this.name // 正确!

def sayHelloTo(p:Person6) = {
println("hello" + p.name) // 报错!无法访问
}
}

object Person6 {
def showName(p:Person6) = println(p.name) // 报错!无法访问
}

protected[this]修饰符

被修饰的成员只能在当前类和当前子类中被访问==。也可以理解为:当前类通过this.访问或者子类this.访问

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
class Person7 {
// 只有在当前类以及继承该类的当前对象中能够访问
protected[this] var name = "super"

def getName = {
// 正确!
this.name
}

def sayHelloTo1(p:Person7) = {
// 编译错误!无法访问
println(p.name)
}
}

object Person7 {
def sayHelloTo3(p:Person7) = {
// 编译错误!无法访问
println(p.name)
}
}

class Student7 extends Person7 {
def showName = {
// 正确!
println(name)
}

def sayHelloTo2(p:Person7) = {
// 编译错误!无法访问
println(p.name)
}
}

调用父类的constructor

实例化子类对象,必须要调用父类的构造器,在scala中,只能在子类的主构造器中调用父类的构造器

1
2
3
4
5
6
7
8
9
10
11
12
13
class Person8(var name:String){
println("name:"+name)
}

// 直接在父类的类名后面调用父类构造器
class Student8(name:String, var clazz:String) extends Person8(name)

object Main8 {
def main(args: Array[String]): Unit = {
val s1 = new Student8("张三", "三年二班")
println(s"${s1.name} - ${s1.clazz}")
}
}

抽象类

  • 如果类的某个成员在当前类中的定义是不包含完整的,它就是一个抽象类**
  • 不完整定义有两种情况:
    • 1.方法没有方法体
    • 2.变量没有初始化
  • 没有方法体的方法称为抽象方法,没有初始化的变量称为抽象字段。定义抽象类和Java一样,在类前面加上abstract关键字就可以了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
abstract class Person9(val name:String) {
//抽象方法
def sayHello:String
def sayBye:String
//抽象字段
val address:String
}
class Student9(name:String) extends Person9(name){
//重写抽象方法
def sayHello: String = "Hello,"+name
def sayBye: String ="Bye,"+name
//重写抽象字段
override val address:String ="beijing "
}
object Main9{
def main(args: Array[String]) {
val s = new Student9("tom")
println(s.sayHello)
println(s.sayBye)
println(s.address)
}
}

匿名内部类

匿名内部类是没有名称的子类,直接用来创建实例对象。Spark的源代码中有大量使用到匿名内部类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
abstract class Person10 {
//抽象方法
def sayHello:Unit
}

object Main10 {
def main(args: Array[String]): Unit = {
// 直接用new来创建一个匿名内部类对象
val p1 = new Person10 {
override def sayHello: Unit = println("我是一个匿名内部类")
}
p1.sayHello
}
}

21. scala面向对象编程之trait特质

  • 特质是scala中代码复用的基础单元
  • 它可以将方法和字段定义封装起来,然后添加到类中
  • 与类继承不一样的是,类继承要求每个类都只能继承一个超类,而一个类可以添加任意数量的特质。
  • 特质的定义和抽象类的定义很像,但它是使用trait关键字

21.1 作为接口使用

  • 使用extends来继承trait(scala不论是类还是特质,都是使用extends关键字)
  • 如果要继承多个trait,则使用with关键字
继承单个trait
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
trait Logger1 {
// 抽象方法
def log(msg:String)
}

class ConsoleLogger1 extends Logger1 {
override def log(msg: String): Unit = println(msg)
}

object LoggerTrait1 {
def main(args: Array[String]): Unit = {
val logger = new ConsoleLogger1
logger.log("控制台日志: 这是一条Log")
}
}
示例二:继承多个trait
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
trait Logger2 {
// 抽象方法
def log(msg:String)
}

trait MessageSender {
def send(msg:String)
}

class ConsoleLogger2 extends Logger2 with MessageSender {

override def log(msg: String): Unit = println(msg)

override def send(msg: String): Unit = println(s"发送消息:${msg}")
}

object LoggerTrait2 {
def main(args: Array[String]): Unit = {
val logger = new ConsoleLogger2
logger.log("控制台日志: 这是一条Log")
logger.send("你好!")
}
}

21.2 定义具体的方法

和类一样,trait中还可以定义具体的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
trait LoggerDetail {
// 在trait中定义具体方法
def log(msg:String) = println(msg)
}

class PersonService extends LoggerDetail {
def add() = log("添加用户")
}

object MethodInTrait {
def main(args: Array[String]): Unit = {
val personService = new PersonService
personService.add()
}
}

21.3 定义具体方法和抽象方法

在trait中,可以混合使用具体方法和抽象方法,使用具体方法依赖于抽象方法,而抽象方法可以放到继承trait的子类中实现,这种设计方式也称为模板模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
trait Logger3 {
// 抽象方法
def log(msg:String)
// 具体方法(该方法依赖于抽象方法log
def info(msg:String) = log("INFO:" + msg)
def warn(msg:String) = log("WARN:" + msg)
def error(msg:String) = log("ERROR:" + msg)
}

class ConsoleLogger3 extends Logger3 {
override def log(msg: String): Unit = println(msg)
}

object LoggerTrait3 {
def main(args: Array[String]): Unit = {
val logger3 = new ConsoleLogger3

logger3.info("这是一条普通信息")
logger3.warn("这是一条警告信息")
logger3.error("这是一条错误信息")
}
}

定义具体字段和抽象字段

在trait中可以定义具体字段和抽象字段,继承trait的子类自动拥有trait中定义的字段,字段直接被添加到子类中.

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
trait LoggerEx {
// 具体字段
val sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm")
val INFO = "信息:" + sdf.format(new Date)
// 抽象字段
val TYPE:String

// 抽象方法
def log(msg:String)
}

class ConsoleLoggerEx extends LoggerEx {
// 实现抽象字段
override val TYPE: String = "控制台"
// 实现抽象方法
override def log(msg:String): Unit = print(s"$TYPE$INFO $msg")
}

object FieldInTrait {
def main(args: Array[String]): Unit = {
val logger = new ConsoleLoggerEx

logger.log("这是一条消息")
}
}

实例对象混入trait

trait还可以混入到实例对象中,给对象实例添加额外的行为, 只有混入了trait的对象才具有trait中的方法,其他的类对象不具有trait中的行为,使用with将trait混入到实例对象中.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
trait LoggerMix {
def log(msg:String) = println(msg)
}

class UserService

object FixedInClass {
def main(args: Array[String]): Unit = {
// 使用with关键字直接将特质混入到对象中
val userService = new UserService with LoggerMix

userService.log("你好")
}
}

评论