《Kotlin从小白到大牛》第11章:面向对象编程

第11章 面向对象编程

Kotlin语言目前还是以面向对象编程为主,函数式编程为辅。面向对象是Kotlin是重要的特性之一。本章将介绍Kotlin面向对象编程知识。

11.1 面向对象概述

面向对象的编程思想:按照真实世界客观事物的自然规律进行分析,客观世界中存在什么样的实体,构建的软件系统就存在什么样的实体。
例如:在真实世界的学校里,会有学生和老师等实体,学生有学号、姓名、所在班级等属性(数据),学生还有学习、提问、吃饭和走路等操作。学生只是抽象的描述,这个抽象的描述称为“类”。在学校里活动是学生个体,即:张同学、李同学等,这些具体的个体称为“对象”,“对象”也称为“实例”。
在现实世界有类和对象,面向对象软件世界也会有,只不过它们会以某种计算机语言编写的程序代码形式存在,这就是面向对象编程(Object Oriented Programming,OOP)。
在这里插入图片描述

11.2 面向对象三个基本特性

面向对象思想有三个基本特性:封装性、继承性和多态性。

11.2.1 封装性
在现实世界中封装的例子到处都是。例如:一台计算机内部极其复杂,有主板、CPU、硬盘和内存,而一般用户不需要了解它的内部细节,不需要知道主板的型号、CPU主频、硬盘和内存的大小,于是计算机制造商将用机箱把计算机封装起来,对外提供了一些接口,如鼠标、键盘和显示器等,这样当用户使用计算机就变非常方便。
那么,面向对象的封装与真实世界的目的是一样的。封装能够使外部访问者不能随意存取对象的内部数据,隐藏了对象的内部细节,只保留有限的对外接口。外部访问者不用关心对象的内部细节,使得操作对象变得简单。

11.2.2 继承性
在现实世界中继承也是无处不在。例如:轮船与客轮之间的关系,客轮是一种特殊轮船,拥有轮船的全部特征和行为,即数据和操作。在面向对象中轮船是一般类,客轮是特殊类,特殊类拥有一般类的全部数据和操作,称为特殊类继承一般类。在面向对象计算机语言中一般类称为“父类”或“超类”,特殊类称为“子类”或“派生类”,本书采用“父类”和“子类”提法。
在这里插入图片描述
11.2.3 多态性
多态性是指在父类中成员变量和成员函数被子类继承之后,可以具有不同的状态或表现行为。有关多态性会在12.4节详细解释,这里不再赘述。

11.3 类声明

类是Kotlin中的一种重要的数据类型,是组成Kotlin程序的基本要素。它封装了一类对象的数据和操作。为了方便使用Kotlin中的类有很多种形式:标准类、枚举类、数据类、内部类、嵌套类和密封类等,此外还有抽象类和接口。
在这里插入图片描述
Kotlin中的类声明的语法与Java非常相似。使用class关键词声明,它们的语法格式如下:
class 类名 {
声明类的成员
}

Kotlin中的类成员包括:
o 构造函数
o 初始化代码块
o 成员函数
o 属性
o 内部类和嵌套类
对象表达式声明

声明动物(Animal)类代码如下:
class Animal {
//类体
}
上述代码声明了动物(Animal)类,大括号中是类体,如果类体中没有任何的成员,可以省略大括号。代码如下:
class Animal
类体一般都会包括一些类成员,下面看一个声明属性示例:
class Animal {

// 动物年龄
var age = 1
// 动物性别
var sex = false
// 动物体重
private val weight = 0.0

}

下面看一个声明成员函数示例:
class Animal {

// 动物年龄
var age = 1
// 动物性别
var sex = false
// 动物体重
private val weight = 0.0

private fun eat() {     ①
    // 函数体
}

fun run(): Int {          ②
    // 函数体
    return 10          
}

fun getMaxNumber(n1: Int, n2: Int) = if (n1 > n2) n1 else n2              ③

}
上述代码第①、②、③行声明了三个成员函数。成员函数在类中声明的函数,它的声明与顶层函数没有区别,只是在调用时需要类的对象才能调用,示例代码如下:
//代码文件:chapter11/src/com/a51work6/section3/ch11.3.1.kt
package com.a51work6.section3

fun main(args: Array) {
val animal = Animal() ①
println(animal.getMaxNumber(12,16))//16 ②
}
上述代码第①行中Animal()表达式是实例化Animal类,创建一个animal对象。创建对象与Java相比省略了new关键字,与Swift相同。代码第②行是通过animal对象调用getMaxNumber成员函数。
在这里插入图片描述

11.4 属性

属性是为了方便访问封装后的字段而设计的,属性本身并不存储数据,数据是存储在支持字段(backing field)中的。
在这里插入图片描述
11.4.1 回顾JavaBean
JavaBean 是一种Java语言的可重用组件技术,它能够与JSP(Java Server Page)标签绑定,很多Java框架也使用JavaBean。JavaBean的字段(成员变量)往往被封装称为私有的,为了能够在类的外部访问这些字段,则需要通过getter和setter访问器访问。动物(Animal)类Java代码如下:
//代码文件:chapter11/src/com/a51work6/section4/s1/Animal.java
package com.a51work6.section4;

public class Animal {
// 动物年龄
private int age = 1; ①
// 动物性别
private boolean sex = false; ②

public int getAge() { ③
return age;
}

public void setAge(int age) { ④
this.age = age;
}

public boolean isSex() { ⑤
return sex;
}

public void setSex(boolean sex) { ⑥
this.sex = sex;
}
}
上述Java代码中有两个字段age和sex,见代码第①行和第②行。sex字段是布尔类型,为了访问私有字段age,需要提供getter访问器(见代码第③行),setter访问器(见代码第④行)。getter访问器是一个函数,它的命名规则是:get+第一个字母大写的字段。setter访问器也是一个函数,它的命名规则是:set+第一个字母大写的字段。但如果是布尔类型字段,getter访问器它的命名规则是:is+第一个字母大写的字段。
如果使用Kotlin语言同样的类,代码如下:
//代码文件:chapter11/src/com/a51work6/section4/s1/Animal.kt
package com.a51work6.section4

class Animal {

// 动物年龄
var age = 1  
// 动物性别
var sex = false

}
可见Kotlin代码非常的简洁,注意上述Animal类中的age和sex不是字段而属性,一个属性对应一个字段,以及 setter和getter访问器,如果是只读属性则没有setter访问器。

11.4.2 声明属性
Kotlin中声明属性的语法格式如下:
var|val 属性名
[ : 数据类型] [= 属性初始化 ]
[getter访问器]
[setter访问器]
从上述属性语法可见,属性最基本形式与声明一个变量或常量是一样的。val所声明的属性是只读属性。如果需要还可以重写属性的setter和getter访问器。
在这里插入图片描述
在这里插入图片描述
示例代码如下:
//代码文件:chapter11/src/com/a51work6/section4/s2/Employee.kt
package com.a51work6.section4.s2

// 员工类
class Employee {
var no: Int = 0 // 员工编号属性
var job: String? = null // 工作属性 ①
var firstName: String =“Tony” ②
var lastName: String =“Guan” ③
var fullName: String //全名 ④
get() { ⑤
return firstName +"." + lastName
}
set (value) { ⑥
val name =value.split(".") ⑦
firstName = name[0]
lastName = name[1]
}

var salary: Double = 0.0    // 薪资属性      ⑧
    set(value) {                                         
        if (value >= 0.0) field =value                  ⑨
    }

}

//代码文件:chapter11/src/com/a51work6/section4/s2/ch11.4.2.kt
package com.a51work6.section4.s2

fun main(args: Array) {

val emp = Employee()
println(emp.fullName)//Tony.Guan
emp.fullName = "Tom.Guan"
println(emp.fullName)//Tom.Guan

emp.salary = -10.0  //不接收负值
println(emp.salary)//0.0
emp.salary = 10.0
println(emp.salary)//10.0

}
上述代码第①行是声明员工的job它是一个可空字符串类型。代码第②行是声明员工的firstName属性,第③行代码是声明员工的lastName属性。代码第④行的声明全名属性fullName,fullName属性值是通过firstName属性和lastName属性拼接而成。代码第⑤行重写getter访问器,可以写成表达式形式。
get() = firstName + “.” + lastName
代码第⑥行是重写setter访问器,value是新的属性值,代码⑦行是通过String的split函数分割字符串,返回的是String数组。
代码第⑧行是声明salary薪资属性,薪资是不能为负数的,这里重写了setter访问器。代码第⑨行的判断如果薪水大于等于0.0 时,才将新的属性值赋值给field变量,field变量是访问支持字段(backing field),属于field软关键字。
在这里插入图片描述
11.4.3 延迟初始化属性
假设公司管理系统中两个类Employee(员工)和Department(部门),它们的类图如图11-1所示,它们有关联关系,Employee所在部门的属性dept与Department关联起来。这种关联关系体现为:一个员工必然隶属于一个部门,一个员工实例对应于一个部门实例。
在这里插入图片描述
下面看一下代码示例:
//代码文件:chapter11/src/com/a51work6/section4/s3/Employee.kt
package com.a51work6.section4.s3

// 员工类
class Employee {

var dept = Department() // 所在部门属性 ①

}

// 部门类

class Department {
var no: Int = 0 // 部门编号属性
var name: String = “” // 部门名称属性
}

//代码文件:chapter11/src/com/a51work6/section4/s3/ch11.4.3.kt
package com.a51work6.section4.s3

fun main(args: Array) {
val emp = Employee()

println(emp.dept)
}
在创建Employee对象时,需要同时需要实例化Employee的所有属性,也包括实例化dept(部门)属性,代码第①行声明dept属性的同时进行了初始化,创建Department对象。如果是一个新入职的员工,有时不关心员工在哪个部门,只关心他的no(编号)和name(姓名)。但上述代码虽然不使用dept对象,但是仍然会实例化它,这样会占用内存。Kotlin可以对属性设置为延迟初始化的,修改代码如下:
//代码文件:chapter11/src/com/a51work6/section4/s3/Employee.kt
package com.a51work6.section4.s3

// 员工类
class Employee {

...
lateinit var dept: Department  // 所在部门属性             ①

}

// 部门类
class Department {
var no: Int = 0 // 部门编号属性
var name: String = “” // 部门名称属性
}

//代码文件:chapter11/src/com/a51work6/section4/s3/ch11.4.3.kt
package com.a51work6.section4.s3
fun main(args: Array) {
val emp = Employee()

emp.dept = Department()
println(emp.dept)

}
在代码第①行在声明dept属性前面添加了关键字lateinit,这样dept属性就是延时初始化。顾名思义,延时初始化属性就是不必在类实例化时初始化它,可以根据需要在程序运行期初始化。而没有lateinit声明的非可空类型属性必须在类实例化时初始化。
在这里插入图片描述
11.4.4 委托属性
Kotlin提供一种委托属性,使用by关键字声明,示例代码如下:
//代码文件:chapter11/src/com/a51work6/section4/s4/ch11.4.4.kt
package com.a51work6.section4.s4

import kotlin.reflect.KProperty

class User {
var name:String by Delegate() ①
}

class Delegate {
operator fun
getValue(thisRef: Any, property: KProperty<*>): String = property.name ②

operator fun

setValue(thisRef: Any?, property: KProperty<*>, value: String) { ③
println(value)
}
}

fun main(args: Array) {

val user =User()
user.name ="Tom"          ④   

println(user.name) ⑤

}
运行结果
Tom
name
上述代码第①行是声明委托属性,by是委托运算符,它后面的Delegate()就是属性name的委托对象,通过by运算符属性name的setter访问器被委托给Delegate对象的setValue函数,属性name的getter访问器被委托给Delegate对象的getValue函数。Delegate对象不必实现任何接口,只需要实现getValue和 setValue函数即可,见代码第②行和第③行。注意这两个函数前面都有operator关键字修饰,operator所修饰的函数是运算符重载函数,本例中说明了getValue和 setValue函数重载by运算符。
代码第④行给name属性赋值,这会调用委托对象的setValue函数,代码第⑤行是读取name数组值,这会调用委托对象的getValue函数。

11.4.5 惰性加载属性
实际开发中自己声明委托属性很少使用,而是通过使用Kotlin标准库中提供的一些委托属性,如:惰性加载属性和可观察属性,本节先介绍惰性加载属性。
惰性加载属性与延迟初始化属性类似,只有第一次访问该属性时才进行初始化。不同的是惰性加载属性使用的lazy函数声明委托属性,而延迟初始化属性lateinit关键字修饰属性。还有惰性加载属性必须是val的,而延迟初始化属性必须是var的。
示例代码如下:
//代码文件:chapter11/src/com/a51work6/section4/s5/Employee.kt
package com.a51work6.section4.s5

// 员工类
open class Employee {

var no: Int =0             // 员工编号属性
var firstName:String = "Tony"
var lastName:String = "Guan" 

val fullName:String by lazy {              ①
    firstName +"." + lastName 
}

lateinit var dept: Department              ②

}

// 部门类
class Department {
var no: Int =0 // 部门编号属性
var name:String = “” // 部门名称属性
}

//代码文件:chapter11/src/com/a51work6/section4/s5/ch11.4.5.kt
package com.a51work6.section4.s5

fun main(args: Array) {

val emp =Employee() 

println(emp.fullName

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值