Kotlin进阶 - 类

这篇属于Kotlin进阶,讲了与类相关内容,主要涉及到:
类定义、类属性和函数、类继承、抽象类和接口、委托与代理、单例、数据类、伴生对象、运算符重载、枚举和密封类

1、面向对象入门,类的创建

示例:

//创建一个简单的类,该类有两个变量
class HelloKotlin(var letter:String ,var letter2: String)

fun main() {
    var demo = HelloKotlin("Hello"," Kotlin")

    println("${demo.letter}${demo.letter2}")
}

2、定义类的属性和方法

访问权限:默认为public,私有属性或方法用private修饰
示例:

package com.example.kotlin_demo
//创建一个简单的类,该类有两个变量
class HelloKotlin(var letter:String ,var letter2: String){
    /**
     * 方法的定义
     */
    fun method(){
        println("我是定义的一个方法")
    }
}

fun main() {
    var demo = HelloKotlin("Hello"," Kotlin")
    println("${demo.letter}${demo.letter2}")
    demo.method()
}

3、类继承

Kotlin中类默认为final类型,无法被继承,若想被继承和方法重写必须要对类或重写的方法加关键字open。如下示例:

//父类,
//有open修饰:允许其他类继承
//无open修饰:不允许其他类继承
open class Parent {
    //父类中的一个方法
    //有open修饰:允许子类重写
    //无open修饰:不允许子类重写
    open fun action(){
        println("Parent action----------")
    }
}

//Child类继承自Parent类
class Child : Parent() {
    //子类重写了父类的action方法
    override fun action(){
        println("Child action----------")
    }
}

4、抽象类和接口

抽象类用关键字abstract,定义如下:

/**
 * 抽象类
 */
abstract class AbstractDemo {
    //定义一个抽象方法
    abstract fun absAction()
}

接口使用关键字Interface,示例如下:

/**
 * 定义一个接口
 */
interface InterfaceDemo {
    //没有实现的方法
    fun action()
    //实现了方法
    fun action2(){
        println("我是action2。")
    }
}

抽象类和接口的使用,示例如下:

/**
 * 继承了抽象类AbstractDemo和接口InterfaceDemo
 */
class InheritDemo : AbstractDemo(),InterfaceDemo {
    //实现了抽象类中的抽象方法
    override fun absAction() {
        println("我来自抽象类中的抽象方法")
    }

    //实现了接口中的方法
    override fun action() {
        println("我来自接口中的action方法")
    }

    //重写了接口中的方法
    override fun action2() {
        super.action2()
        println("我来自接口中的实现的方法")
    }
}

注:
1.抽象类和接口已隐式声明为open类型,允许其他类继承,因此这里不需要显示的声明open。
2.抽象类中的非抽象方法若要让子类重写,需要加open标识。

5、Kotlin中实现代理和委托

使用关键字 by 来实现代理和委托

1.接口代理

针对实现了相同接口的两个类,如下示例:
接口类:

//定义了一个接口
interface IDelegate {
    fun delegateAction()
}

一个实现了IDelegate接口的DelegateImp.kt类

//继承了IDelegate接口
class DelegateImp1 : IDelegate {
    //实现了接口方法
    override fun delegateAction() {
        println("我来自Child,实现了接口中的delegateAction方法")
    }
}

定义了一个实现了代理接口类,通过by实现委托给DelegateImp1类实现IDelegate接口,如下:

//继承了IDelegate接口,并委托给Child类实现
//委托条件:继承自相同的接口
class ByDelegateImp : IDelegate by DelegateImp1() {

}
2.属性代理

自定义属性代理类:


import kotlin.reflect.KProperty
//定义一个属性代理类
class PropertyDelegate(var value:String) {
    //属性代理必须实现一个getValue方法
    operator fun getValue(any:Nothing?, property: KProperty<*>):String{
        return value
    }
    //属性代理必须实现一个setValue方法
    operator fun setValue(any:Nothing?, property:KProperty<*>, str:String){

    }
}

通过by使用这个属性代理类,如下:

fun main() {
    //通过by使用属性代理的方式
    var str : String by PropertyDelegate("我是代理类")
    println(str)
    str = "haha"
    println(str)
}

最终输出为:

我是代理类
我是代理类
3.系统中常见的委托属性

1.延迟属性(lazy properties): 其值只在首次访问时计算;
lazy() 是接受一个 lambda 并返回一个 Lazy 实例的函数,返回的实例可以作为实现延迟属性的委托: 第一次调用 get() 会执行已传递给 lazy() 的 lambda 表达式并记录结果, 后续调用 get() 只是返回记录的结果。

//使用lazy委托属性
val lazyValue :String by lazy {
    println("computed!")
    "Hello"
}
println(lazyValue)

输出内容为:
computed!
Hello

2.可观察属性(observable properties): 监听器会收到有关此属性变更的通知;
Delegates.observable() 接受两个参数:初始值与修改时处理程序(handler)。 每当我们给属性赋值时会调用该处理程序(在赋值后执行)。它有三个参数:被赋值的属性、旧值与新值:

class User {
    var name: String by Delegates.observable("") {
        prop, old, new ->
        println("$old -> $new")
    }
}

fun main() {
    val user = User()
    user.name = "first"
    user.name = "second"
}

输出值为:
-> first
first -> second

3.把属性储存在映射中
一个常见的用例是在一个映射(map)里存储属性的值。 这经常出现在像解析 JSON 或者做其他“动态”事情的应用中。 在这种情况下,你可以使用映射实例自身作为委托来实现委托属性。

class UserMap(val map: Map) {
    val name: String by map
    val age: Int     by map
}

fun main() {
    val user = UserMap(mapOf(
            "name" to "John Doe",
            "age"  to 25
    ))

    println("name=${user.name},age=${user.age}")
}

输出为:
name=John Doe,age=25

系统属性委托参考自:kotlin-委托和属性委托

6、kotlin中单例

object
在Java中有类有静态方法,使用static关键字标识,在kotlin中则没有,但kotlin中有关键字object,通过该关键字标识类,即为单例,类中所有的方法都可通过类名直接访问,类似java中的静态方法。

//通过使用object标识为单例
object Singleton{
    fun log(str:String){
        println(str)
    }
}

fun main() {
    //直接通过类名调用方法,类似Java中的静态方法
    Singleton.log("我是单例中的log方法。")
}

输出为:
我是单例中的log方法。

companion object 伴生对象声明方式
通常情况下一个类中的方法只能通过类对象进行访问,但如何能像java中的类一样将部分方法声明为静态方法呢?答案就是companion object,通过使用该声明可以让直接通过类名调用方法。
伴生对象生成时机:①伴生对象函数或属性被调用;②类实例化时。
示例如下:

//Kotlin类中声明的方法都是非静态的
//但是有时候也会想把部分方法定义为静态,则可以使用companion object关键字进行标识
class CompanionObjectDemo {
    companion object{
        fun log(string: String){
            println("我来自伴生对象的方法---${string}")
        }
    }

    fun other(str : String){
        println(str)
    }
}

fun main() {
    //调用伴生对象的方法
    CompanionObjectDemo.log("companion function")

    var demo = CompanionObjectDemo()
    //调用对象中的方法
    demo.other("other function")
}

输出为:
我来自伴生对象的方法—companion function
other function

7、Kotlin中数据类(data修饰)

Kotlin中特有的一种类,采用关键字data进行修饰。

  • 数据类默认实现了超类(Any)的equals、hashCode、toString函数。
  • 提供了copy函数,类似Java的clone,拷贝的时候执行的是主构造函数,若其中一个变量是在次构造函数中赋值的,则无法copy过来,需要手动设置值。
  • 支持结构语法,即:val (x, y) = DataClassTest(10, 20)形式

定义的数据类需要满足以下条件:

  • 必须有至少带一个参数的主构造函数
  • 主构造函数的参数必须为val或者var,不能为临时变量
  • 数据类不能使用abstract、open、sealed和inner修饰;

使用场景
针对经常需要比较、复制或打印自身内容的类。
示例:

data class DataClassTest(var x: Int, var y: Int) {
    val isInvalidate = x > 0 && y > 0
}

fun main() {
    println(DataClassTest(10, 5))
    //data数据类默认提供了支持结构操作
    val (x, y) = DataClassTest(10, 20)
}

普通类中定义解构函数
定义方式
operator fun component1() = x
operator fun component2() = y

operator componentN() = n
主构造函数有几个参数就会生产对应几个component

/**
 * 普通类支持解构定义
 */
class Coor(var x: Int, var y: Int) {
    operator fun component1() = x
    operator fun component2() = y

}
fun main() {
    println(DataClassTest(10, 5))

    //普通类支持解构语法
    val (p,q) = Coor(10,20)
}

8、Kotlin类中运算符重载

Kotlin中支持对运算符进行重载,常见的运算符重载如下表

操作符 函数名 作用
+ plus 把一个对象添加到另一个对象里
+= plusAssign 把一个对象添加到另一个对象里,然后将结果赋值给第一个对象
- minus 把一个对象减去另一个对象
== equals 如果两个对象相等,则返回true,否则返回false
> compareTo 如果左边的对象大于右边的对象,则返回true,否则返回false
[] get 返回集合中指定位置的元素
rangeTo 创建一个range对象
in contains 如果对象包含在集合里,则返回true

使用示例如下:

class OperatorTest(var x: Int, var y: Int) {
    /**
     * 对“+”进行运算符重载
     */
    operator fun plus(other: OperatorTest) = OperatorTest(x + other.x, y + other.y)

    /**
     * 对"-"进行运算符重载
     */
    operator fun minus(other: OperatorTest) = OperatorTest(x - other.x, y - other.y)
}

fun main() {
    val op1 = OperatorTest(10, 20)
    val op2 = OperatorTest(5, 30)
    println(op1 + op2)
    println(op1 - op2)
}

9、Kotlin中的枚举类型(enum)和密封类(sealed)

1.枚举

跟java一样,kotlin中也有枚举类型,使用方式相同,示例如下:

//定义一个枚举类型
enum class EnumDemo {
    星期一,星期二,星期三,星期四,星期五,星期六,星期日
}

fun main() {
    //输出枚举类型的索引和值
    for (item in EnumDemo.values()){
        println("ordinal--${item.ordinal},name--${item.name}")
    }
}

输出结果为:
ordinal–0,name–星期一
ordinal–1,name–星期二
ordinal–2,name–星期三
ordinal–3,name–星期四
ordinal–4,name–星期五
ordinal–5,name–星期六
ordinal–6,name–星期日

2.密封类(sealed)

密封类,类似枚举,但是密封类主要是针对类的,密封类用来表示受限的类继承结构:当一个值为有限几种的类型、而不能有任何其他类型时。可以理解为枚举类型的扩展。
要声明一个密封类,需要在类名前面添加 sealed 修饰符。虽然密封类也可以有子类,但是所有子类都必须在与密封类自身相同的文件或者嵌套在密封类声明的内部中声明。原因:用sealed修饰的类是private访问类型
示例:

//方式一:嵌套在密封类内部声明
sealed class SealedDemo {
    class Monday(var string: String) : SealedDemo(){
        fun mondayAct(){
            println("Monday--->${string}")
        }
    }
    class Tuesday(var string: String) : SealedDemo(){
        fun mondayAct(){
            println("Tuesday--->${string}")
        }
    }
}

fun main() {
    val monday = SealedDemo.Monday("星期一")
    val second = SealedDemo.Tuesday("星期二")
    monday.mondayAct()
    second.mondayAct()
}

示例二:

//方式二:密封类声明在同一个文件中
sealed class SealedDemo2
data class Const(val number: Double) : SealedDemo2()
data class Sum(val e1: SealedDemo2, val e2: SealedDemo2) : SealedDemo2()
object NotANumber : SealedDemo2()

fun main() {
    val const = Const(10.0)
    val sum = Sum(const,const)
}

枚举类型和密封类对比

维度 枚举 密封类
关键字 enum sealed
声明 嵌套在类里面 嵌套在类内部或声明在同一个文件中
类型 枚举常量都是一个对象 密封类针对的是类

欢迎留言大家互相交流学习!


示例源码地址kotlin_demo

你可能感兴趣的:(Kotlin,kotlin,android,java)