Kotlin(六)、类.对象和接口

本文深入解析Kotlin中的类、接口、构造方法、数据类及类委托等核心概念,展示如何通过Kotlin实现面向对象编程,包括类的定义、继承、抽象类、密封类及构造函数的使用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、定义类

接口
//使用interface关键字声明一个Kotlin接口
interface Clickable {
    fun click()
}

//实现这个接口
class Button : Clickable {//使用`:`代替Java中的extends和implements关键字,和Java一样单继承多实现
    override fun click() = println("I was clicked")//override修饰符和Java中@Override注解一样,不同的是Kotlin强制要求
}
 
//使用
Button.click()

接口方法可以有一个默认实现。Java8中需要在这样的实现上标注default关键字,Kotlin中只需要提供一个方法体

//接口中定义一个带方法体的方法
interface Clickable {
    fun click()
    fun showOff() = println("I'm clickable")
}

若实现了这个接口则需要为click提供一个实现,可以为showOff重新定义行为,如果默认行为可以满足需求也可以省略

//定义另一个实现同样方法的接口
interface Focusable{
    fun showOff()= println("I'm focusable")
}

在一个类中同时实现两个接口,每个接口都具有包含默认实现的showOff方法,这时候就算有默认实现也不行,必须显示的实现showOff

class Button :Focusable,Clickable{
    override fun showOff() {
        //必须提供显示实现,使用<父类名>标明想要调用的父类方法
        super<Focusable>.showOff()
        super<Clickable>.showOff()
        //当然也可以只super其中一个
    }

    override fun click() {
        TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
    }

}
open、final、abstract

Kotlin中类和方法默认final,好处就是拥有了智能转换功能。智能转换只能在进行类型检查后没有改变过的变量上起作用。对于一个类意味着只能是val类型切没有自定义getter的属性上使用,所以前提就是final。由于属性默认final,所以可以不加思考的使用智能转换。

open class Button : Clickable {//open的,其他类可以继承

    fun disable() {}//final的,不能在子类重写

    open fun animate() {}//open的,可以子类重写

    override fun click() {}//重写了子类的open,所以默认open

    final override fun showOff() {}//重写了其父类的open方法,但其子类不能重写

}

Kotlin中通Java一样可以为类声明abstract的,不能被实例化。一个抽象类通常包含一些没有实现并且必须在子类重写的抽象成员。抽象类始终是open的,所以不需要显示的使用open修饰符

abstract class Animated {//抽象类,不能创建实例
    abstract fun animate()//抽象函数,他没有实现必须被子类重写
    open fun stopAnimating() {}//抽象类的非抽象函数不是默认open,可以主表2000
    fun animateTwice() {}
}
修饰符相关成员评注
final不能被重写类中成员默认使用
open可以被重写需要明确的表明
abstract必须被重写只能在抽象类中使用;抽象成员不能有实现
override重写父类或接口中的成员如果没有使用final表明,重写的成员默认是开放的
可见性修饰符
修饰符类成员顶层声明
public所有地方可见所有地方可见
internal模块中可见模块中可见
protected子类中可见——
private类中可见文件中可见
内部类和嵌套类
类A在另一个类B中声明在Java中在Kotlin中
嵌套类static class Aclass A
内部类class Ainner class
密封类

open class Expr
class Num(val value: Int) : Expr()
class Sunm(val left: Expr, val right: Expr) : Expr()
fun eval(e:Expr):Int=
        when(e){
            is Num->e.value
            is Sunm-> eval(e.right)+ eval(e.left)
            else->
                throw IllegalArgumentException("Unknown expression")
        }

当使用when结构来执行表达式的时候,Kotlin编译器会强制检查默认选项。在这个例子中,不能返回一个有意义的值,所以直接抛出异常。
总是不得不添加一个默认分支很不方便。更重要的是,如果你有新添加了一个新的子类,编译器并不能发现有新的子类,这是就会走到默认分支,有可能导致潜在的bug。
Kotlin为这个问题提供了一个解决方案:sealed类。为父类添加一个sealed修饰符,对可能创建的子类做出严格的限制。所有的直接子类必须嵌套在父类中。

sealed class Expr {
    class Num(val value: Int) : Expr()
    class Sunm(val left: Expr, val right: Expr) : Expr()
}


fun eval(e: Expr): Int =
        when (e) {
            is Expr.Num -> e.value
            is Expr.Sunm -> eval(e.right) + eval(e.left)
        }

二、定义构造方法

写一个简单的拥有构造函数的class

//一个主构造,没有从构造
class User constructor(nickname: String) {//带一个参数的主构造方法
    val nickname: String

    init {//初始化语句块
        this.nickname = nickname
    }
}

因为主构造没有注解和可见性修饰,并且初始化可以与属性结合

class User (nickname: String) {
    val nickname = nickname
}

由于是val修饰的变量,所以还可以简化为

class User (val nickname: String)

那么这就是最简单的语法了

若不想被实例化,则需要构造函数私有化

class User private constructor(val nickname: String)
接口中声明属性

Kotlin中接口是可以包含属性声明的

interface User {
    val nickname: String
}

也就是说实现User接口的类需要提供一个取得nickname的方式

//PrivateUser使用了简洁的语法直接在主构造中声明了一个属性,这个属性来自User所以标记为override
class PrivateUser(override val nickname: String) : User

//SubscribingUser的nickname属性通过一个自定义getter实现
class SubscribingUser(private val email: String) : User {
    override val nickname: String
        get() = email.substringBefore('@')
}

//FacebookUser在初始化时将nickname属性与值关联。这个函数开销很大,每次都需要调用getFacebookName初始化很多次
class FacebookUser(val accountId: Int) : User {
    override val nickname = getFacebookName(accountId)
}

二、数据类和类委托

数据类

使用data修饰符得到一个重写了所有标准Java方法的类:

data class Client(val name: String, val postalCode: Int)
  • equals用来比较实例
  • hashCode用来作为如Hashmap这种基于哈希容器的键
  • toString用来为类生成按声明顺序排列的所有字段的字符串表达形式
    equals和hashCode方法会将所有在主构造方法中声明的属性纳入考虑。生成的equals方法会检测所有的属性的值是否相等。hashCode方法会返回一个根据所有属性生成的哈希值。注意没有在主构造中声明的属性将不会加入到相等性检查和哈希值计算中去。

这只是为data类生成的一部分方法,下一篇会介绍更多。

原则上是可以使用var可变类型的,但是还是更鼓励使用val不可变数据类型,好处很多,特别是在多线程代码中:一旦一个对象被创建出来了,他会一直保持初始状态,不用担心代码工作时被其他线程修改。

为了解决val的操作数据问题,Kotlin编译器多生成了一个方法:允许copy类实例,并在copy的同时修改属性值。副本有单独的生命周期与原实例互不影响

val bob = Client("Bob", 1234)
println(bob.copy(postalCode = 4321))
委托类

重写部分功能,其他通用功能委托给被包装类

class CountingSet<T>(val innerSet: MutableCollection<T> = HashSet<T>())
    : MutableCollection<T> by innerSet {
    override fun add(element: T): Boolean {
        //重写
        return innerSet.add(element)
    }
    //剩下的接口委托给被包装容器,字段为innerSet
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Qi T

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值