面向对象
本次课程旨在让学员全面且深入地了解面向对象编程的核心概念,透彻掌握类与对象之间的紧密关系,熟练运用 Kotlin 语言中各类常用类进行程序开发。通过理论知识的系统讲解、丰富多样的实际案例分析以及详细的代码解读,帮助学员将面向对象编程思想融入到 Kotlin 编程实践中,提升解决实际问题的能力,为后续开发复杂的 Kotlin 应用程序奠定坚实基础。
面向对象编程(Object - Oriented Programming,简称 OOP)是一种强大的编程范式,它模拟了现实世界中事物的组织和交互方式。在 OOP 中,程序被看作是一组相互协作的对象集合,每个对象都有自己独特的状态(属性)和行为(方法)。例如,在一个电商系统中,商品、订单、用户等都可以被抽象为对象。商品对象有名称、价格、库存等属性,以及添加到购物车、修改价格等行为;订单对象有订单号、下单时间、商品列表等属性,以及支付、取消等行为;用户对象有用户名、密码、收货地址等属性,以及登录、下单等行为。这些对象之间通过消息传递进行交互,共同完成系统的各项功能。
封装是面向对象编程的重要特性之一,它就像一个黑盒子,将对象的内部数据和操作数据的方法封装在一起,对外只暴露必要的接口。这样做有以下几个好处:
继承允许一个类(子类)继承另一个类(父类)的属性和方法,实现了代码的复用和扩展。继承的优点如下:
多态是指同一个方法调用可以根据对象的不同类型表现出不同的行为。多态提高了代码的灵活性和可扩展性,主要体现在以下方面:
makeSound
方法,狗类和猫类继承自动物类并重写了 makeSound
方法。在调用 makeSound
方法时,可以传入狗对象或猫对象,程序会根据实际传入的对象类型调用相应的方法。makeSound
方法,原有的代码不需要做任何修改就可以支持鸟类的叫声模拟。类是对象的抽象描述,它是创建对象的模板。在 Kotlin 中,使用 class
关键字来定义类,类可以包含属性和方法。
// 定义一个简单的类
class Person {
// 定义类的属性
// var 关键字表示属性是可变的
var name: String = ""
var age: Int = 0
// 定义类的方法
// 该方法用于输出个人信息
fun introduce() {
println("我叫 $name,今年 $age 岁。")
}
// 该方法用于修改年龄
fun updateAge(newAge: Int) {
if (newAge > 0) {
age = newAge
} else {
println("年龄不能为负数。")
}
}
}
class Person
:使用 class
关键字定义了一个名为 Person
的类。var name: String = ""
和 var age: Int = 0
:定义了类的两个属性,name
是字符串类型,用于存储人的姓名,初始值为空字符串;age
是整数类型,用于存储人的年龄,初始值为 0。var
关键字表示这些属性是可变的,可以在对象创建后进行修改。fun introduce()
:定义了类的一个方法,方法名为 introduce
,用于输出个人信息。在方法内部,使用字符串模板将 name
和 age
的值插入到输出语句中。fun updateAge(newAge: Int)
:定义了一个用于修改年龄的方法,接收一个整数参数 newAge
。在方法内部,会检查新年龄是否大于 0,如果是则更新 age
属性,否则输出错误信息。对象是类的具体实例,通过类可以创建多个不同的对象。在 Kotlin 中,使用类名后面跟括号的方式来创建对象。
fun main() {
// 创建 Person 类的对象
val person1 = Person()
// 给对象的属性赋值
person1.name = "张三"
person1.age = 20
// 调用对象的方法
person1.introduce()
val person2 = Person()
person2.name = "李四"
person2.age = 25
person2.introduce()
// 调用修改年龄的方法
person2.updateAge(30)
person2.introduce()
}
val person1 = Person()
:创建了 Person
类的一个对象 person1
,val
关键字表示 person1
是一个不可变的引用,即不能再将 person1
指向其他对象。person1.name = "张三"
和 person1.age = 20
:给 person1
对象的属性赋值,将姓名设置为 "张三"
,年龄设置为 20。person1.introduce()
:调用 person1
对象的 introduce
方法,输出个人信息。person2
并进行操作,每个对象都有自己独立的属性值。person2.updateAge(30)
:调用 person2
对象的 updateAge
方法,将年龄修改为 30,然后再次调用 introduce
方法输出更新后的个人信息。类是对象的模板,它定义了对象的属性和方法;对象是类的实例,通过类可以创建多个不同的对象,每个对象都有自己独立的属性值。类就像是一个设计蓝图,规定了对象应该具备的特征和行为;而对象则是根据这个蓝图制造出来的具体产品,每个产品都有自己独特的状态。例如,Person
类是一个蓝图,person1
和 person2
就是根据这个蓝图创建出来的具体的人,他们有不同的姓名和年龄。
主构造函数是类头的一部分,紧跟在类名后面。可以在主构造函数中定义类的属性,并进行初始化。
// 定义一个带有主构造函数的类
class Student constructor(name: String, age: Int) {
// 将主构造函数的参数赋值给类的属性
var name: String = name
var age: Int = age
// 该方法表示学生正在学习
fun study() {
println("$name 正在学习。")
}
// 该方法表示学生参加考试
fun takeExam() {
println("$name 正在参加考试。")
}
}
// 简化写法,直接在主构造函数中定义属性
class StudentSimplified(var name: String, var age: Int) {
// 该方法表示学生正在学习
fun study() {
println("$name 正在学习。")
}
// 该方法表示学生参加考试
fun takeExam() {
println("$name 正在参加考试。")
}
}
fun main() {
// 创建 Student 类的对象
val student1 = Student("王五", 18)
student1.study()
student1.takeExam()
val student2 = StudentSimplified("赵六", 22)
student2.study()
student2.takeExam()
}
class Student constructor(name: String, age: Int)
:定义了一个带有主构造函数的类 Student
,constructor
关键字可以省略。主构造函数接收两个参数 name
和 age
,用于初始化对象的属性。var name: String = name
和 var age: Int = age
:将主构造函数的参数赋值给类的属性。class StudentSimplified(var name: String, var age: Int)
:简化写法,直接在主构造函数中定义属性,这样可以减少代码的冗余。main
函数中创建了 Student
和 StudentSimplified
类的对象,并调用了 study
和 takeExam
方法。次构造函数用于提供额外的构造方式,它必须直接或间接地调用主构造函数。
class Teacher(var name: String, var age: Int) {
// 次构造函数
constructor(name: String) : this(name, 0) {
println("次构造函数被调用。")
}
// 该方法表示老师正在授课
fun teach() {
println("$name 正在授课。")
}
// 该方法表示老师批改作业
fun gradeHomework() {
println("$name 正在批改作业。")
}
}
fun main() {
// 使用主构造函数创建对象
val teacher1 = Teacher("李老师", 30)
teacher1.teach()
teacher1.gradeHomework()
// 使用次构造函数创建对象
val teacher2 = Teacher("王老师")
teacher2.teach()
teacher2.gradeHomework()
}
constructor(name: String) : this(name, 0)
:定义了一个次构造函数,它接收一个参数 name
,并通过 this(name, 0)
调用主构造函数,将年龄初始化为 0。main
函数中,分别使用主构造函数和次构造函数创建了 Teacher
类的对象,并调用了 teach
和 gradeHomework
方法。当使用次构造函数创建对象时,会输出 “次构造函数被调用。” 的信息。在 Kotlin 中,构造函数还可以包含初始化块,用于在对象创建时执行一些额外的初始化操作。
class Book(var title: String, var author: String) {
var publishedYear: Int = 0
init {
println("正在初始化图书对象...")
// 可以在这里进行一些复杂的初始化操作
if (title.isBlank()) {
title = "未知书名"
}
if (author.isBlank()) {
author = "未知作者"
}
}
fun displayInfo() {
println("书名: $title,作者: $author,出版年份: $publishedYear")
}
}
fun main() {
val book = Book("", "")
book.displayInfo()
}
init
块是初始化块,在对象创建时会被执行。在这个例子中,初始化块会检查 title
和 author
是否为空,如果为空则设置为默认值。main
函数中创建 Book
对象时,会先执行初始化块,然后调用 displayInfo
方法输出图书信息。在 Kotlin 中,所有类默认是 final
的,即不能被继承。如果要让一个类可以被继承,需要使用 open
关键字修饰。
// 定义一个基类
open class Animal(var name: String) {
// 该方法表示动物发出声音
open fun makeSound() {
println("$name 发出声音。")
}
// 该方法表示动物移动
open fun move() {
println("$name 正在移动。")
}
}
// 定义一个派生类,继承自 Animal 类
class Dog(name: String) : Animal(name) {
// 重写基类的 makeSound 方法
override fun makeSound() {
println("$name 汪汪叫。")
}
// 重写基类的 move 方法
override fun move() {
println("$name 四条腿奔跑。")
}
// 狗类特有的方法
fun fetch() {
println("$name 正在捡东西。")
}
}
fun main() {
// 创建 Animal 类的对象
val animal = Animal("动物")
animal.makeSound()
animal.move()
// 创建 Dog 类的对象
val dog = Dog("旺财")
dog.makeSound()
dog.move()
dog.fetch()
}
open class Animal
:使用 open
关键字修饰 Animal
类,使其可以被继承。open fun makeSound()
和 open fun move()
:使用 open
关键字修饰方法,使其可以被重写。class Dog(name: String) : Animal(name)
:定义了一个派生类 Dog
,继承自 Animal
类,并在主构造函数中调用基类的主构造函数,将 name
参数传递给基类。override fun makeSound()
和 override fun move()
:使用 override
关键字重写基类的方法,实现了狗独特的发声和移动方式。fun fetch()
:狗类特有的方法,体现了子类的扩展性。main
函数中,分别创建了 Animal
类和 Dog
类的对象,并调用了相应的方法。子类可以继承父类的属性和方法,这样可以避免代码的重复编写。例如,Dog
类继承了 Animal
类的 name
属性和 move
方法。
子类可以重写父类的 open
方法,实现自己的行为。重写方法时,需要使用 override
关键字,并且方法的签名(方法名、参数列表、返回类型)要与父类的方法一致。在重写方法时,还可以使用 super
关键字调用父类的方法。
open class Shape {
open fun draw() {
println("绘制一个形状。")
}
}
class Circle : Shape() {
override fun draw() {
super.draw()
println("绘制一个圆形。")
}
}
fun main() {
val circle = Circle()
circle.draw()
}
Circle
类的 draw
方法中,使用 super.draw()
调用了父类 Shape
的 draw
方法,然后再输出自己的绘制信息。子类可以添加自己的属性和方法,扩展功能。例如,Dog
类可以添加一个 fetch()
方法,表示狗会捡东西。
接口是一种抽象类型,它定义了一组方法的签名,但不包含方法的实现。在 Kotlin 中,使用 interface
关键字来定义接口。
// 定义一个接口
interface Flyable {
fun fly()
}
// 定义一个类实现 Flyable 接口
class Bird : Flyable {
override fun fly() {
println("鸟儿在飞翔。")
}
}
fun main() {
// 创建 Bird 类的对象
val bird = Bird()
bird.fly()
}
interface Flyable
:使用 interface
关键字定义了一个名为 Flyable
的接口,其中包含一个抽象方法 fly()
。class Bird : Flyable
:定义了一个类 Bird
,实现了 Flyable
接口。override fun fly()
:使用 override
关键字实现了接口中的 fly
方法。main
函数中,创建了 Bird
类的对象,并调用了 fly
方法。abstract
的,不需要使用 abstract
关键字修饰。String
类String
类是 Kotlin 中用于表示字符串的类,它提供了许多实用的方法。
fun main() {
val str = "Hello, Kotlin!"
// 获取字符串的长度
val length = str.length
println("字符串长度: $length")
// 截取子字符串
val subStr = str.substring(0, 5)
println("截取的子字符串: $subStr")
// 转换为大写
val upperCaseStr = str.toUpperCase()
println("大写字符串: $upperCaseStr")
}
val str = "Hello, Kotlin!"
:定义了一个字符串变量 str
。str.length
:获取字符串的长度。str.substring(0, 5)
:截取从索引 0 到索引 5(不包含 5)的子字符串。str.toUpperCase()
:将字符串转换为大写。List
类List
类是 Kotlin 中用于表示列表的类,它是一个只读的集合。
fun main() {
// 创建一个只读列表
val numbers = listOf(1, 2, 3, 4, 5)
// 遍历列表
for (number in numbers) {
println(number)
}
// 获取列表的元素
val firstNumber = numbers[0]
println("第一个元素: $firstNumber")
}
val numbers = listOf(1, 2, 3, 4, 5)
:创建了一个只读列表 numbers
。for (number in numbers)
:使用 for
循环遍历列表中的元素。numbers[0]
:获取列表中索引为 0 的元素。Map
类Map
类是 Kotlin 中用于表示键值对映射的类。
fun main() {
// 创建一个只读映射
val map = mapOf("apple" to 1, "banana" to 2, "cherry" to 3)
// 遍历映射
for ((key, value) in map) {
println("$key: $value")
}
// 获取映射中的值
val appleValue = map["apple"]
println("apple 的值: $appleValue")
}
val map = mapOf("apple" to 1, "banana" to 2, "cherry" to 3)
:创建了一个只读映射 map
。for ((key, value) in map)
:使用 for
循环遍历映射中的键值对。map["apple"]
:获取键为 "apple"
的值。通过本次课程的学习,我们了解了面向对象的概念,掌握了类与对象的关系,熟悉了 Kotlin 中的常用类。面向对象编程是一种强大的编程范式,通过封装、继承和多态等特性,可以提高代码的可维护性、复用性和扩展性。在实际开发中,我们可以根据具体的需求,合理运用面向对象编程的思想,编写出高质量的代码。