Kotlin 进阶之路(三) 面向对象

简介: Kotlin 进阶之路(三) 面向对象

Kotlin 进阶之路(三) 面向对象


3.1 面向对象的概念


面向对象是将要解决的问题按照一定的规则划分为多个独立的对象,然后通过调用对象的方法来解决

问题。面向对象的三大特性,封装性、继承性、多态性。

1.封装性

封装性是面向对象的核心思想,将对象的属性和行为封装起来,不需要让外界知道具体的实现细节。

例如,用户使用电脑,只需要手指敲键盘即可,无需知道电脑内部是怎么工作的。

2.继承性

继承性主要是指类与类之间的关系,通过继承,可对原有类的方法进行扩展。

例如,学生和工人都属于人类,人类都需要吃饭,学生和工人都可以继承人类,分别扩展各自吃饭

的方法。

3.多态性


多态性是指在程序中允许出现重名,在一个类中定义的属性和方法被其它类继承后,它们可以有


不同的数据类型或表现出不同的行为,这使得同一个属性和方法在不同的类中具有不同语义。


例如,当听到 Cut 这个单词时,理发师的行为是理发,演员的行为是停止表演,不同的对象,


所表现的行为是不一样的。


3.2 类与对象


  • 类的定义

类是对象的抽象,用于描述一组对象的共同特征和行为。类中可以定义成员变量(属性)

和成员函数(方法)

class Student{
  //成员变量
  private val name = ""
  private val age = 0
  //成员函数
  private fun sayHi(){
    println("你好,我是${name}, 我今年${age}岁")
  }
}
  • 对象创建

Kotlin 中对象通过 类名() 的形式来直接创建

var 对象名称 = 类名()

var stu = Student()

对象的成员变量访问方式

对象引用.对象成员

stu.name

class Student{
    //成员变量
    var name = "Mike"
    var age = 16
    //成员函数
    fun sayHi(){
        println("你好,我是${name}, 我今年${age}岁")
    }
}
fun main(args: Array<String>) {
    var student = Student()
    student.sayHi()
    println("姓名:${student.name}")
    student.name = "James"
    println("姓名:${student.name}")
    /*你好,我是Mike, 我今年16岁
    姓名:Mike
    姓名:James*/
}



  • 类的封装

类的封装是指在定义一个类时,将类中的属性私有化,使用 private 关键字来修饰,私有

属性只能在所属类中访问,为了让外界能范围私有属性,可以提供 public 修饰的共有方法。

class Student{
    var name : String = ""//默认是公有
    private var age : Int = 0
    fun setAge(age : Int){
        if (age > 0)
            this.age = age
        else
            println("输入年龄有误")
    }
    默认是公有
    fun sayHi(){
        println("你好,我是${name}, 我今年${age}岁")
    }
}
fun main(args: Array<String>) {
    var stu = Student()
    stu.setAge(24)
    stu.name = "Kobe"
    stu.sayHi()
    /*你好,我是Kobe, 我今年24岁*/
}


3.3 构造函数


构造函数是类的一个特殊成员,将在类实例化对象时被自动调用,可用来为类的属性赋值。

Kotlin 中的构造函数分为两种————主构函数和次构函数。构造函数使用关键字 constructor 定义

  • 主构函数

在 Kotlin 中,一个类可以有一个主构造函数和多个次构造函数。主构造函数位于类头跟

在类名之后,如果主构造函数没有任何注解或可见性修饰符, constructor 关键字可
省略
。语法格式如下

class 类名 constructor([形参1, 形参2, 形参3]){}

当定义一个类时,如果没有显示指定主构函数, Kotlin 编译器会默认为其生成一个无参

主构函数,这点和 Java 是一样的。无参主构函数的两种写法。

class 类名 constructor(){}

class 类名(){}


一般我们经常用的是有参的构造函数为属性赋值。在主构函数中赋值时,使用 init{} 初始化代码块

class Click constructor(username : String){
    var name : String
    init {
        name = username
        println("我是$username")
    }
}
fun main(args: Array<String>) {
    var click = Click("猫哆哩")
    //我是猫哆哩
}


  • this 关键字

Kotlin 中也提供了 this 关键字,用于在函数中访问对象的其它成员。上面的 init{} 可写成

init{

this.name = username

}


  • 次构函数

Kotlin 中可以定义多个次构函数,次构函数必须调用主构函数或其它次构函数,调用方式

为 次构函数 : this(参数列表)

注意:

当新定义的次构函数调用主构函数或次构函数时,被调用的构造函数中参数顺序必须和新定义

的次构函数参数顺序一致,并且参数个数必须小于新定义的次构函数的参数个数。


class Workers constructor(name : String){
    var name : String
    init {
        this.name = name
        println("我叫${name}")
    }
    constructor(name: String, age: Int) : this(name){
        println("我叫${name}, 我今年${age}岁")
    }
    constructor(name: String, age: Int, sex: String) : this(name, age){
        println("我叫${name}, 我今年${age}岁,我是${sex}生")
    }
}
fun main(args: Array<String>) {
    var person = Workers("洛天依", 26, "女")
  /*我叫洛天依
  我叫洛天依, 我今年26岁
  我叫洛天依, 我今年26岁,我是女生*/
}


3.4 类的继承

  • 类的继承

在 Kotlin 中,类的继承是指在一个现有类的基础上去构建一个新类,构建出来的新类被

称为子类,现有类被称作父类,子类将自动拥有父类所有可继承的属性和方法。

继承使用关键字 : , class A : B{} A 类继承 B 类

Kotlin 中所有的类默认使用 final 修饰,因此,当继承某类时,需要在类前面加 open 关键字


open class Father(){
    fun sayHello(){
        println("Hello")
    }
}
class Son : Father(){}
fun main(args: Array<String>) {
    var son = Son()
    son.sayHello()
  //Hello
}


总结:

1、在 Kotlin 中,一个类只能继承一个父类,不能继承多个父类

2、多个类可以继承一个父类

3、在 Kotlin 中,多层继承是允许的,即一个类的父类可以再去继承另外的父类

4、在 Kotlin 中,子类和父类是一种相对概念,一个类可以是另一个类的子类,也可能是其它类的父类


  • 方法重写

子类对父类的方法或属性进行修改,该过程被称为方法或属性的重写。

注:

重写的方法或属性应和父类的方法名或属性名一样,并且方法前面使用 override 关键字标识

在父类中被重写的属性或方法前必须使用 open 关键字修饰


open class Father(){
    open var name = "洛天依"
    open var age = 26
    open fun sayHello(){
        println("Hello! 我叫$name, 我今年$age 岁")
    }
}
class Son : Father(){
    override var name = "阿黛尔"
    override var age = 48
    override fun sayHello() {
        println("Hello! It's me, 我叫 $name, 我今年$age 岁")
    }
}
fun main(args: Array<String>) {
    var father = Father()
    father.sayHello()
    var son = Son()
    son.sayHello()
  /*Hello! 我叫洛天依, 我今年26 岁
  Hello! It's me, 我叫 阿黛尔, 我今年48 岁*/
}


  • super 关键字

super 关键字用于访问父类的成员变量或方法,一般用于在子类中访问父类的成员

super.成员变量

super.成员方法([形参1, 形参2…])


注:

Kotlin 中所有类都继承 Any 类,它是所有类的父类,类比 Java 中的 Object类

如果一个类在声明时没有指定父类,则默认为 Any 类,在程序运行时, Any 类自动映射

为Java 中的 java.lang.Object 类


3.5 抽象类和接口


  • 抽象类


抽象类使用关键字 abstract 修饰,抽象方法也需要用 abstract 修饰。需要注意的是,包


含抽象方法的类必须声明为抽象类,但抽象类可以不包含任何抽象方法,只需用 abstract


修饰即可。抽象类是不可以被实例化的,其中的抽象方法没有方法体,不可以被调用。

abstract class Animal{
    abstract fun eat()
}
class Monkey(food : String) : Animal(){
    var food = food
    override fun eat() {
        println("猴子正在吃$food")
    }
}
fun main(args: Array<String>) {
    var monkey = Monkey("香蕉")
    monkey.eat()
  //猴子正在吃香蕉
}
  • 接口

如果一个抽象类中的所有方法都是抽象的,则可以将类定义为接口。接口也就是一个特

殊的抽象类,使用关键字 interface 来声明,接口中定义的方法默认包含 abstract 修饰

符,可以省略不写

interface Animal{
    fun eat()
}
interface Monkey : Animal{
    fun sleep()
}
class GoldenMonkey(food: String) : Monkey{
    var food = food
    override fun eat() {
        println("我是金丝猴,我喜欢吃$food")
    }
    override fun sleep() {
        println("我是金丝猴,我喜欢睡觉")
    }
}
fun main(args: Array<String>) {
    var goldenMonkey = GoldenMonkey("香蕉")
    goldenMonkey.eat()
    goldenMonkey.sleep()
  /*我是金丝猴,我喜欢吃香蕉
  我是金丝猴,我喜欢睡觉*/


总结:

接口中的方法都是抽象的,不能实例化对象

当一个类实现接口时,如果这个类是抽象类,则实现接口中部分方法即可,否则需要实现所有方法

一个类可以实现多个接口,但 : 之间需要用 , 隔开

一个接口可以继承多个接口,但 : 之间需要用 , 隔开

一个类在继承另一个类的同时还可以实现接口,继承的类和实现的接口都放在 : 后面


3.6 常见类

  • 嵌套类、内部类

Kotlin 的嵌套类是指可以嵌套在其他类中的类,该类不能访问外部类的成员

Kotlin 的内部类是指使用 inner 修饰的嵌套类,可以访问外部类的成员

class Outer{
    var name = "洛天依"
    var age = 26
    class Nested{
        fun sayHello(){
            //println("Hello! 我叫${name}, 今年${age}岁")//无法访问外部类字段
        }
    }
}
class Outer{
    var name = "洛天依"
    var age = 26
    inner class Inner{
        fun sayHello(){
            println("Hello! 我叫${name}, 今年${age}岁")
        }
    }
}
fun main(args: Array<String>) {
    Outer().Inner().sayHello()
}


  • 枚举类

枚举就是一 一例举,每个枚举常量都是一个对象,枚举常量用逗号分隔,用关键字 enum 修饰

enum class Week1{
  星期一,星期二,星期三,星期四,星期五,星期六,星期日
}
enum class Week2(val what:String, val doSomeThine: String){
  MONDAY("星期一", "上班")
  TUESDAY("星期二", "聚会")
  WEDNEWSDAY("星期三", "上班")
  THURSDAY("星期四", "上班")
  FRIDAY("星期五", "上班")
  SATURDAY("星期六", "加班")
  SUNDAY("星期日", "休息")
}


  • 密封类

密封类用于表示受限制的类层次结构,当一个值只能在一个集合中取值,而不能取其他

值时,此时可以用密封类。在某种意义上,密封类是枚举类的扩展,即枚举类型的值集合。每个枚举常量只存在一个实例,而密封类的一个子类可以有包含状态的多个实例。

密封类必须用 sealed 关键字修饰

由于密封类的构造函数是私有的,因此密封类的子类只能定义在密封类的内部或同一文件中

sealed class Stark{
    //罗伯 斯塔克
    class RobStark : Stark(){}
    //桑莎 斯塔克
    class SansaStark : Stark(){}
    //艾丽娅 斯塔克
    class AryaStark : Stark(){}
    //嵌套类
    class BrandonStark(){}
}
//密封类 Stark 子类
class JonSnow : Stark(){}

注:

Kotlin 中密封类和枚举类的区别,密封类适用于子类可数的情况,而枚举类适用实例可数的情况

  • 数据类

Kotlin 中专门处理数据一些数据或对象的状态的类称为数据类,类似于 Java 中的 Bean

类、entity 类、model 类。数据类的语法格式为,

data class 类名({形参1, 形参2…})

注:

数据类的主构造函数至少有一个参数,如果需要一个无参的构造函数,可将构造函数的参数都设置默认值


数据类中的主构造函数中传递的参数必须用 val 或 var 修饰


在 Kotlin 1.1 版本之前数据类只能实现接口,1.1 版本后可继承其它类


编译器可自动生成一些常用的方法,如 equals()、hashCode()、toString()、

componentN()、copy()等,这些方法也可自定义

  • 单例模式

单例模式是指在程序运行期间针对该类只存在一个实例,就好比世界上只有一个太阳一

样。Kotlin 中的单例模式是通过 object 关键字来完成的,通过 object 修饰的类即为单例类。

object Singleton{
    var name = "单例模式"
    fun sayHello(){
        println("Hello, 我是一个$name,浑身充满正能量")
    }
}
fun main(args: Array<String>) {
    Singleton.name = "小太阳"
    Singleton.sayHello()
  //Hello, 我是一个小太阳,浑身充满正能量
}


注: 单例类不需要创建实例对象,直接通过 类名.成员名 调用类中的属性或函数

  • 伴生对象

Kotlin 中没有静态变量,因此使用伴生对象来替代 Java 中的静态变量,伴生对象是在类

加载是初始化,生命周期与该类的生命周期一致。通过关键字 companion 来标识,由于

每个类中有且仅有一个伴生对象,因此可以不指定伴生对象的名称,其它对象也可共享

伴生对象。


companion object 伴生对象名称(也可以不写){

程序代码...

}

有名称: 调用方式为 类名.伴生对象名.成员名 或 类名.成员名

无名称: 调用方式为 类名.Companion.成员名 或 类名.成员名


class Company{
    companion object Factory{
        fun sayHello(){
            println("我是一个伴生对象,与伴生类相伴")
        }
    }
}
fun main(args: Array<String>) {
  //第一种调用方式 类名.伴生对象名.成员函数名
    Company.Factory.sayHello()
  //第二种调用方式 类名.成员函数名
    Company.sayHello()

3.7 委托


委托也叫代理模式,是最常用的一种设计模式。简单说就是 A 的工作交给 B 来做。

在 Kotlin 中,委托是通过 by 关键字实现的,主要分为类委托,和属性委托。


  • 类委托

接下来演示定义接口 Wash,已经两个接口的实现类 Child 和 Parent, Parent 类要实现的功能委托给 Child 进行处理。

interface Wash{
    fun washDishes()
}
class Child : Wash{
    override fun washDishes() {
        println("委托大头儿子洗碗,耶!")
    }
}
//第一种委托方式
class Parent : Wash by Child(){}
//第二种委托方式
class Parent(washer : Wash) : Wash by washer
fun main(args: Array<String>) {
    //第一种
    var parent = Parent()
    parent.washDishes()
    //第二种
    var child = Child()
    Parent(child).washDishes()
  //委托大头儿子洗碗,耶!


  • 属性委托

Kotlin 中的属性委托是指一个类的某个属性值不是在类中直接进行定义,而是将其委托给一个代

理类,从而实现对该类的属性进行统一管理。语法格式如下:

val/var <属性名> : <类型> by <表达式>

by 后面的表达式是指委托类,属性对应的 get() 和 set() 会被委托给 getValue() 和

setValue() 方法,因此属性的委托不必实现任何接口。

class Parent{
    var money : Int = 0
    operator fun getValue(child: Child, property : KProperty<*>) : Int{
        println("getValue()方法被调用,修改的属性为: ${property.name}")
        return money
    }
    operator fun setValue(child: Child, property: KProperty<*>, value: Int){
        println("getValue()方法被调用,修改的属性为: ${property.name} 属性值: ${value}")
        money = value
    }
}
class Child{
    //将压岁钱委托给父母
    var money : Int by Parent()
}
fun main(args: Array<String>) {
    val child = Child()
    println("(1) 父母给孩子100元压岁钱")
    child.money = 100
    println("(2) 买玩具花了50")
    child.money -= 50
    println("(3) 自己还剩${child.money}")
  /*(1) 父母给孩子100元压岁钱
  getValue()方法被调用,修改的属性为: money 属性值: 100
  (2) 买玩具花了50
  getValue()方法被调用,修改的属性为: money
  getValue()方法被调用,修改的属性为: money 属性值: 50
  getValue()方法被调用,修改的属性为: money
  (3) 自己还剩50*/
}


注:

setValue() 和 getValue() 方法前必须用 operator 关键字修饰

getValue()方法返回类型必须与委托属性相同或是其子类

如果委托属性是 val 类型,被委托方法只需要实现 getValue(), 如果是 var 类型,则需要实现

getValue() 和 setValue()


  • 延迟加载

Kotlin 中提供了延迟加载功能,又叫懒加载,当变量被访问时才会被初始化。通过关键字

by lazy 标识,延迟加载要求变量声明为 val 类型。延迟加载也是委托的一种形式。

fun main(args: Array<String>) {
    val content by lazy {
        println("Hello")
        "world"//第一次初始化后,再次调用该变量时,只会输出 ^lazy 的内容
    }
    println(content)
    println(content)
    /*Hello
  world
  world*/
}


注:

延迟加载的变量在第一次初始化时会输出代码块中的所有内容,之后再次调用时,都只会

输出最后一行代码的内容。


后续


Kotlin 进阶之路(四) 集合

目录
相关文章
|
1月前
|
Java 物联网 Kotlin
Kotlin - 面向对象之抽象类与接口
Kotlin - 面向对象之抽象类与接口
|
2月前
|
IDE 开发工具 iOS开发
Kotlin教程笔记(11) - 面向对象之抽象类与接口
本系列教程笔记详细讲解了Kotlin语法,适合深入学习。若需快速掌握Kotlin,建议查阅“简洁”系列教程。本文重点介绍了Kotlin中的抽象类与接口,包括接口的定义、实现、继承,以及抽象类的定义、构造器、方法实现等关键概念。
34 2
|
23天前
|
Java Kotlin
Kotlin教程笔记(12) - 面向对象之继承与实现
Kotlin教程笔记(12) - 面向对象之继承与实现
28 4
|
23天前
|
Java 物联网 Kotlin
Kotlin教程笔记(11) - 面向对象之抽象类与接口
Kotlin教程笔记(11) - 面向对象之抽象类与接口
24 2
|
28天前
|
Java 物联网 Kotlin
Kotlin教程笔记(11) - 面向对象之抽象类与接口
Kotlin教程笔记(11) - 面向对象之抽象类与接口
29 6
|
1月前
|
Java Kotlin
Kotlin - 面向对象之继承与实现
Kotlin - 面向对象之继承与实现
31 4
|
1月前
|
Java Kotlin
Kotlin教程笔记(12) - 面向对象之继承与实现
Kotlin教程笔记(12) - 面向对象之继承与实现
75 9
|
1月前
|
Java 物联网 Kotlin
Kotlin教程笔记(11) - 面向对象之抽象类与接口
Kotlin教程笔记(11) - 面向对象之抽象类与接口
66 9
|
1月前
|
Java 物联网 Kotlin
Kotlin - 面向对象之抽象类与接口
Kotlin - 面向对象之抽象类与接口
52 5
|
1月前
|
Java 物联网 Kotlin
Kotlin - 面向对象之抽象类与接口
Kotlin - 面向对象之抽象类与接口
21 5