Kotlin总结之四 面向对象编程

一.Kotlin类的创建


1.类的声明

Kotlin 类可以包含:构造函数和初始化代码块、函数、属性、内部类、对象声明。
Kotlin 中使用关键字 class 声明类,后面紧跟类名:

// 类名为 Apple
class Apple{  
 // 大括号内是类体构成
}

当类没有结构体的时候,大括号可以省略。如:

class Apple
类的修饰符描述
abstract抽象类
final类不可继承,默认属性
enum枚举类
open类可继承,类默认是final的
annotation注解类
访问权限修饰符描述
private仅在同一个文件中可见
protected同一个文件中或子类可见
public所有调用的地方都可见
internal同一个模块中可见


2. 类的构造函数

在kotlin中有两种类型的构造函数:

  • 主构造函数(主构造器)。
  • 次级构造函数(次级构造器)。

在Kotlin类中只有一个主构造函数(主构造器),而辅助构造函数(次级构造器)可以是一个或者多个。


(1.)主构造函数

主构造函数是类头的一部分:它跟在类名(和可选的类型参数)后。

class 类名 construction(参数1,参数2….){  }

如果主构造函数没有任何注解或者可见性修饰符,可以省略这个 constructor 关键字,否则不能省略。
不能省略:

class Person private constructor(name:String){
 val mName: String = name
 ...
}

可以省略:

class Person(name: String) {
 val mName: String = name
  ...
}

如果不需要将构造函数中参数同时作为类属性,也可以写成如下形式(constructor表示构造函数,里面执行初始化的处理):

class Person{
    constructor(name:String){
    ....
    }
}

由于主构造函数没有函数体。如果需要在主构造函数中编写代码该怎么做?初始化的代码可以放 到以 init 关键字作为前缀的初始化块(initializer blocks)中,可以在这里编写要在主构造函数中完成的业务,init{…}中能使用构造函数中的参数:

class Person(username: String, age: Int){
	private val username: String
	private var age: Int
	init{
		this.username = username
		this.age = age
	}
}

注意,主构造的参数可以在初始化块中使用。它们也可以在 类体内声明的属性初始化器中使用:

class Person(name: String) {
    val name= name+"..."
    ...
}

事实上,声明属性以及从主构造函数初始化属性,Kotlin 有简洁的语法:

class Person(var name: String, var age: Int) {
    ...
}

与普通属性一样,主构造函数中声明的属性可以是 可变的(var)或只读的(val)。


(2.)次构造函数

类也可以声明前缀有 constructor的次构造函数,可以声明多个次构造函数:

class Person {
    constructor() {
        ...        
    }
   constructor(name: String) {
        ...        
    }
  constructor(name: String,age:Int) {
        ...      
    }
}
  1. kotlin声明了主构造器
    如果类有一个主构造函数,每个次构造函数需要委托给主构造函数, 可以直接委托或者通过别的次构造函数间接委托。委托到同一个类的另一个构造函数 用 this 关键字即可:
class Person (name : String?,age : Int) {
    var name : String?
    var age : Int
    init {
        println("执行初始化块")
        this.name = name
        this.age = age
    }
    constructor() : this(null,0) {//直接委托调用主构造器

    }
    constructor(name : String) : this() {//间接委托调用主构造器
        
    }
}
  1. kotlin没声明主构造器
    kotlin没声明主构造器,重载构造器不需要调用当前类的其他构造器,调用构造器时也会执行初始化块。
class Person {
    var name : String?
    var age : Int
    init {
        println("执行初始化块")
    }
    //在各自的构造器中对属性进行赋值
    constructor() {
        this.name = null
        this.age = 0
    }

    constructor(name : String) {
        this.name = name
        this.age = 0
    }

    constructor(name : String, age : Int) {
        this.name = name
        this.age = age
    }
}

如果一个非抽象类没有声明任何(主或次)构造函数,它会有一个生成的 不带参数的主构造函数。构造函数的可见性是 public。如果你不希望你的类 有一个公有构造函数,你需要声明一个带有非默认可见性的空的主构造函数(此时这个类不能够实例化):

class Person private constructor () {

}
  • 当只写了主构造函数,没有次构造函数,就会覆盖了默认的空造函数,此时创建对象只能通过你写的主构造函数来调用。
  • 当只写了次构造函数,不写主构造函数,创建对象必须通过次构造函数调用,若不写空的参数构造函数,则调用空的构造函数会报错。
  • 当写了既写了主构造,又写了次构造函数,可以直接调用主构造函数或者调用次构造函数创建对象。次构造函数最终都是调用主构造函数来创建对象的。

注意:在 JVM 上,如果主构造函数的所有的参数都有默认值,编译器会生成 一个额外的无参构造函数,它将使用默认值。


二.Kotlin类的继承

在 Kotlin 中所有类都有一个共同的超类Any,没有申明超类的类是默认继承超类Any:

class Person // 从 Any 隐式继承

Any不是 java.lang.Object;尤其是,它除了 equals()、hashCode()和toString()外没有其他属性或者方法。

在Kotlin中,所有类在默认情况下都是无法被继承的,简而言之,就是说Kotlin中,所有类在默认情况下都是final的,但如何才能被继承,Kotlin给我们提供了一个关键字open,只有被open修饰的类才可以被继承(关键字open的作用与final相反),否则编译器会报错。

open class Parent(p: Int)//申明该类是open修饰的,该类可以继承
class Child(p: Int) : Parent(p)//继承open类

方法重写:

//父类
open class Parent{
    open fun method(){}
    fun unOpen(){  //没有open显示指定,这个函数不能被重写覆盖
        println("unOpen")
    }
}

//子类
class Child: Parent() {
    ///重写覆盖的方法这里必须使用override关键字来修饰
    override fun method() {
        super.method()
    }
   override fun unOpen(){  //Child的子类不可再被重写覆盖
      super.open()
   }
}

覆盖属性和覆盖方法类似。


三.Kotlin抽象类

和Java一样,在kotlin中,抽象类用关键字abstract修饰,抽象类的成员可以在本类中提供实现,也可以不实现而交给子类去实现,不实现的成员必须用关键字abstract声明:

abstract class AbsBase{
    abstract fun  method()
}

在kotlin中,被继承的类需要用关键字open声明,表明该类可以被继承,但是抽象类或者抽象函数是不用 open 标注的。但是如果子类要实现抽象类的非抽象函数,需要在抽象类中将其声明为open。

抽象类有如下的特点:

  • 抽象类就是为继承设计的,因此即使不用open关键字修饰,抽象类也是可以被继承的。
  • 使用abstract关键字修饰的抽象函数也可以不加open。
  • 抽象类中的非抽象方法如果不用open修饰的话,不能被子类覆盖。
abstract class AbsBase{
    abstract fun  method()   // 如果子类要实现需声明为抽象 
    open fun method1(){//非抽象方法如果要类子类实现,需要声明为open
        println("输出")
    }
}

class Child : AbsBase() {
    override fun method() {
      //抽象类的实现    
    }
    override fun method1() {
        super.method1()
        println("子类实现")
    }
}


四.Kotlin接口


1.接口的定义

Kotlin接口用interface作为关键字,基本上和Java的接口类似,但是kotlin的接口不仅可以有抽象方法,也可以有已实现的方法。

interface Animal {
    val kind: String
    val color: String

    fun doing()

    fun eat() {
        println("吃骨头")
    }
}

class Dog : Animal {
    
    override val kind = "小黑狗"
    override val color = "黑色的"

    override fun doing() {
        println("正在玩")
    }
}


2.接口和抽象类区别

  • 抽象类可以为属性设置值和getter、setter,而接口不可以。
  • 抽象类和接口中都可以有默认的实现方法,但接口中的方法不能有状态,即涉及到属性的值需要到实现类中设置。
  • 子类只能继承一个父类,但可以实现多个接口。


3.解决实现多个接口导致的覆盖冲突

Kotlin中可以实现多个接口,可能会出现继承了同一个方法的多个实现。调用应该使用super<类名>.同名方法的形式调用,举个例子:

interface A {
    fun foo() {
        println("A")
    }
    fun bar()
}

interface B {
    fun foo() {
        println("B")
    }
    fun bar() {
        println("bar")
    }
}

class C : A {
    override fun bar() {
        println("C.bar")
    }
}

class D : A, B {
    override fun foo() {
        super<A>.foo()
        super<B>.foo()
        println("D.foo")
    }

    override fun bar() {
        super<B>.bar()
    }
}

fun main(args: Array<String>) {
    var d = D()
    d.foo()
    d.bar()
}


五.Kotlin的嵌套类

只要将一个类放在另一个类中定义,这个类就变成了嵌套类,相当于Java中static修饰的静态内部类。

class Outer {                  // 外部类
    private val value: Int = 1

    class Nested {             // 嵌套类
        var inValue = 2
        fun nestedMethod() {
            // println(value)       //嵌套类无法直接访问外部类属性
            println(Outer().value) //但可以通过外部类的对象访问,因为有private修饰符,普通类通过对象也无法访问
        }
    }

    fun outerMethod() {
        // println(inValue)         //外部类无法直接访问嵌套类的属性
        // method()                 //外部类无法直接访问嵌套类的方法
        println(Nested().inValue) //外部类无法通过嵌套类的对象访问private属性,但是可以访问public
    }
}

fun main() {
    val demo = Outer.Nested().inValue // 调用格式:外部类.嵌套类.嵌套类方法/属性
    println(demo)    // == 2
}


六.Kotlin的内部类

内部类是一种特殊的嵌套类,被嵌套到里面的类使用inner关键字修饰,内部类可以拥有对外部类的引用。但是外部类没有内部类的引用。所以内部类可以访问外部类成员属性和成员函数。
内部类的特点:

  • 内部类成员可以直接访问外部类的私有数据,因为内部类相当于外部类的成员之一。
  • 外部类不能访问内部类的成员,如需访问,需要通过创建内部类对象,通过对象访问内部类成员。
class Outer {
    private val value1: Int = 1
    var value2 = "成员属性"

    //嵌套内部类
    inner class Inner {
        fun innerFun() = value1  // 访问外部类成员
        fun innerTest() {
            val outer = this@Outer //获取外部类的成员变量
            println("内部类可以引用外部类的成员,bar:${innerFun()}")
            println("内部类可以引用外部类的成员,例如:${outer.value2}")
        }
    }
}

fun main(args: Array<String>) {
    val demo = Outer().Inner().innerFun()
    println(demo) //   1
    Outer().Inner().innerTest()
}

为了消除歧义,要访问来自外部作用域的 this,我们使用this@label,其中 @label是一个 代指 this 来源的标签。


七.Kotlin的匿名内部类

名内部类主要是针对那些获取抽象类或者接口对象而来的。最常见的匿名内部类点击事件:

interface OnClickListener {
    fun onClick()
}

class View {
    fun setClickListener(clickListener: OnClickListener) {

    }
}

fun main(args: Array<String>) {
    val view = View()
    view.setClickListener(object : OnClickListener {
        override fun onClick() {

        }
    })
}


八.Kotlin的伴生对象 Companion Objects

与Java不同的是,在kotlin中,类是没有显式的定义static方法和static成员变量的。大多数情况下,推荐顶层函数。但是顶层函数不能访问类的private成员,因此需要通过伴生对象的形式间接提供的。
Kotlin语言中使用"companion object"修饰静态方法,可以使用类名.方法名的形式调用,如下:

class Util {
    companion object {
        fun getCurrentVersion(): String {
            return BuildConfig.VERSION_NAME
        }
    }
}

调用:

var version_name2 = Util .getCurrentVersion()

伴生对象的特点:

  • 每个类最多定义一个伴生对象;
  • 伴生对象相当于外部类的对象,可以直接通过外部类名访问伴生对象的成员;
  • 由于kotlin取消了static关键字,伴生对象是为了弥补kotlin没有static关键字修饰的静态成员的不足;
  • 虽然伴生对象是为其所在对象模拟静态成员,但是伴生对象成员依然属于伴生对象本身的成员,而不属于其外部类的成员。


九.Kotlin的数据类

在类的声明前添加data关键字,即可将一个类定义成数据类。

data class <类名> <(主构造函数参数列表)> [:继承类和实现接口] [(/*类体*/)]

主构造函数的参数列表必须使用val或var声明为类属性,而且要求 至少有一个,否则无法通过编译。

data class User(val id:Int, val name:String)

数据类的特点:

  • 自动声明与构造函数入参同名的属性字段。
  • 自动实现每个属性字段的存取器方法set/get。
  • 自动提供equals方法用于比较两个数据对象是否相等。
  • 自动提供copy方法允许完整复制某个数据对象。
  • 自动提供toString方法。


十.Kotlin的密封类


1.密封类的定义

就像用enum类型一样,经常需要在代码中声明一些有限集合,如: 网络请求可能为成功或失败需要先定义一个enum类型 网络请求状态NetStatus,再定义Loading、Success、Error这些网络请求状态。
枚举就是为了控制住你所有要的情况是正确的,而不是用硬编码方式写成字符串“Loading”,“Success”,“Error”。 同样的,密封类(sealed)的目的与enum类似,一个类之所以设计成sealed,就是为了限制类的继承结构,将一个值限制在有限集中的类型中,而不能有任何其他的类型。
密封类用来表示受限的类继承结构(规定了有限个类型,不可以存在其他类型)。是一种同时拥有枚举类 enum 和 普通类 class 特性的类。可以保证我们写出更安全的代码。
密封类与枚举类的区别:
**相同点 ( 类型限制 ) **: 从类型种类角度对比 , 类与枚举类类似 , 枚举类的值的集合是受限制的 , 不能随意扩展 ;
**不同点 ( 对象个数限制 ) **: 从每个类型对象个数对比 , 枚举类的每个类型只能存在一个实例 (每一个枚举值代表一个类的实例), 而密封类的每个类型可以创建无数个实例 ;

所以可以将密封类看做是枚举的拓展,基于枚举,高于枚举。枚举类型的每个值只允许有一个实例,同时枚举也无法为每个类型添加额外信息,例如,您无法为枚举中的 “Error” 添加相关的 Exception 类型数据。
而且不能控制对象的构建时机,当枚举类构建时 NetStatus 中的各种子类也必须构建好。 虽然也可以使用一个抽象类然后让一些类继承它,这样就可以随意扩展,但这会失去枚举所带来的有限集合的优势。
也可以说成,密封类是包含了一组受限的类集合,因为里面的类都是继承自这个密封类的。但是其和其他继承类(open)的区别在,密封类可以不被此文件外被继承,有效保护代码。
所以说密封类 则同时包含了前面两者的优势 —— 抽象类表示的灵活性和枚举里集合的受限性。


2.密封类的使用场景

  • 禁止外部继承,对于一些只划分固定类型的数据,保证安全(作用类似于枚举)。
  • when遍历密封类的子类时,不用加else语句。(主要解决了when结构需要添加一个默认的else分支的问题)
    例如:新建一个 Result.kt
interface NetworkStatus
class Loading : NetworkStatus()
class Successed : NetworkStatus()

fun getResultMsg(result: NetworkStatus) = when(result) {
    is Loading -> {}
    is Successed -> {}
    else -> throw IllegalArgumentException()
}

这里定义了一个 NetworkStatus 接口,然后定义了两个类去实现 NetworkStatus 接口。然后再写一个 getResultMsg() 方法。实际上 Result 的执行结果只能是 Loading 或 Successed,所以里边的 else 永远无法走到,但我们不得不写上,只是为了满足 Kotlin 编译器的语法检查而已。

密封类就可以帮我们解决这个问题:

sealed class NetworkStatus
class Loading : NetworkStatus()
class Successed : NetworkStatus()

fun getResultMsg(result: NetworkStatus) = when(result) {
    is Loading -> {}
    is Successed -> {}
}

如果添加了一个新的分支,编译器就会发现有的地方发生了改变,编译器就会提醒处理。
例如:

sealed class NetworkStatus
class Loading : NetworkStatus()
class Successed : NetworkStatus()
class Error(val code: Int, val message: String) : NetworkStatus()

fun getResultMsg(result: NetworkStatus) = when(result) {
    is Loading -> {}
    is Successed -> {}
    is Error->{} //强制要求你将每一个子类所对应的条件全部处理,如果不进行处理编译器就会进行报错提醒
}

参考自:
kotlin官方文档

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值