美文网首页
六,类的进阶

六,类的进阶

作者: 从心开始的我 | 来源:发表于2019-12-19 15:07 被阅读0次

    1,类的构造器

    当我们定义一个Person类时

    class Person(var name:String,age:Int)
    
    • 带 var/val 字段的属性 是类内全局可见的
    • 而后面age 不带var/val 该属性仅在构造器内可见(init块,属性初始化)

    上面的定义相当于

    class Person(var name: String, age: Int) {
        var age = 0
        init {
            this.age = age
            println(this.age)
        }
    }
    
    <1>init块
    • init块跟Java中的构造块基本类似,唯一的区别是Kotlin中init块中能访问到构造函数中的不带var/val 的变量

    例如

    class Person(var name: String, age: Int) {
        var age = 0
        init {
            this.age = age
            println(this.age)
        }
        var testa=3
        init {
            this.age=testa
            println(this.age)  
        }
    }
    

    他们会一起编译到函数的构造函数中一起执行

    <2>类的属性必须初始化

    Kotlin类的属性必须初始化,类的生命周期和属性的相同,这样使用者在调用类的属性时不会有负担

    <3>类的继承

    子类继承父类必须调用父类的构造方法

    abstract class Anim
    
    class Pig(var name: String):Anim(){
        //必须调用父类构造函数
    }
    
    <4>副构造器
    • 副构造器:定义在主构造器后在类内部定义的构造器都被成为福构造器
    • 副构造器必须调用到主构造器,确保构造路径唯一性
    abstract class Anim
    
    class Pig(var name: String) : Anim() {
        //必须调用父类构造函数
        var age = 0;
        constructor(age: Int) : this("fff") {
            this.age = age
        }
    }
    
    不定义主构造器(不推荐)
    abstract class Anim
    class Dog : Anim{
        var name:String
        var age:Int
        constructor(name: String,age:Int):super(){
            this.name=name
            this.age= age
        }
    }
    
    一般情况下有这种情况的,比较推荐使用:主构造器+默认参数的形式
    <5>工厂函数
    fun main() {
        //Person的工厂方法
        Person("1111111")
        //String中系统的工厂方法
        String(charArrayOf('1'))
        //自定义String
        String(IntArray(2){ it })
    }
    
    val persons=HashMap<String,Person>()
    
    fun Person(name: String):Person{
        //缓存person对象
        return persons[name]?:Person(name,25).also { persons[name] = it }
    }
    
    fun String(ints:IntArray):String{
        return ints.contentToString()
    }
    

    工厂函数和扩展方法:个人理解扩展方法的场景更大一些,而工厂函数的意义就是为了创建该类的对象,扩展方法可以返回该类对象也可以返回为Unit

    2,类与成员的可见性

    <1>模块的概念
    • Intelij IDEA 模块
    • Maven工程
    • Gradle SourceSet
    • Ant 任务中一次调用<kotlinc>的文件
    • 直观的讲模块的可以认为是一个jar包,一个aar包,通常一个jar包或者aar包里面所有的源文件会一次通过<kotlinc>通过编译
    <2> internal VS default
    • 一般由SDK或者公共组件开发者用于隐藏内部实现细节
    • default 可通过外部创建相同的包名进行访问,访问控制比较弱
    • default 会导致不同抽象层次的类聚集到相同的包之下
    • internal 可方便处理内外隔离,提升模块代码内聚减少接口暴露
    • internal 修饰的Kotlin类或者成员在Java中可直接访问
    针对Kotlin中被internal修饰的类能被Java访问,可以通过一些特殊手段让其不能访问,例如下例:
    internal class CoreApiKotlinA {
        @JvmName("%abcd")
        internal fun a(){
            println("Hello A")
        }
    }
    
    <3>构造器可见性
    class Cat :Anim {
        var name: String
        var age: Int
        private constructor(name: String, age: Int):super() {
            this.name = name
            this.age = age
        }
    }
    

    可用于单例

    <4>类的属性可见性
    class Half (private var name: String,var age: Int){
        private var sex:Boolean=true
        init {
            println("$name---$age")
        }
    }
    //只能访问到age和sex
    var half = Half("111", 2)
    half.age=11
    

    属性的getter和setter

    - 属性的getter必须和属性的可见 一致

    下例中 sex是public 所以不能把getter设置为private

    class Half (private var name: String,var age: Int){
        var sex:Boolean=true
        private set
        //private get
    }
    
    - 属性的setter可见性不得大于属性的可见性
    class Half(private var name: String, var age: Int) {
        private var firstName: String = ""
    //    public set
    }
    
    <5>顶级声明的可见性(Kotlin独有)
    • 顶级声明指文件内直接定义的属性,函数,类等
    • 顶级声明不支持protect
    • 顶级声明被private修饰表示文件内可见

    3,类属性的延迟初始化

    为什么要延迟初始化?

    • 类属性必须在构造时进行初始化
    • 某些成员只有在类初始化后才会别初始化
    class Beer(var name: String,var age: Int){
       lateinit var firstName:String
    }
    

    常见的例子有:在Android中控件的初始化是在Activity的onCreate()方法中才能初始化,可以使用lateinit进行延迟初始化

    lateinit的注意事项
    • lateinit 会让编译器忽略变量的初始化,不支持Int等基本类型
    • 开发者必须能够在完全确定变量的生命周期下使用lateinit
    • 不要在复杂的逻辑中使用lateinit,它只会让代码变得更加脆弱
    • Kotlin在1.2引入判断lateinit属性是否初始化的api 最好不要用
    lazy

    4,代理 Delegate

    接口代理

    对象X 代替当前类A 实现接口B的方法

    我 代替 你 处理了 它

    属性代理

    对象X代替属性a实现getter/setter方法

    <1>接口代理

    定义接口

    interface InterFace {
        fun funA()
        fun funB()
        fun funC()
    }
    

    定义增强接口类

     class InterfaceWrapper(var inter:InterFace):InterFace{
         override fun funA() {
             inter.funA()
         }
    
         override fun funB() {
             inter.funB()
         }
    
         override fun funC() {
             println("---------------")
             inter.funC()
         }
    

    代理形式

     class InterWrapper (private val inter: InterFace):InterFace by inter{
             override fun funC() {
                 println("---------------")
             }
         }
    

    接口 inter 代理 InterWrapper 实现 接口 InterFace 的方法

    对于对象inter的唯一要求就是实现被代理的接口,因为如果inter没有实现接口方法的话,就不具有代理的能力

    小案例 创建一个SupportArray

    class SupperArray<E>(
        private val list: MutableList<E?> = ArrayList(),
        private val map: MutableMap<String, E> = HashMap()
    ) : MutableList<E?> by list, MutableMap<String, E> by map {
        override fun isEmpty(): Boolean {
            return list.isEmpty() && map.isEmpty()
        }
    
        override fun clear() {
            list.clear()
            map.clear()
        }
    
        override val size: Int
            get() = list.size + map.size
    
        override fun set(index: Int, element: E?): E? {
            if (list.size <= index) {
                repeat(index - list.size + 1) {
                    list.add(null)
                }
            }
            return list.set(index, element)
        }
    
        override fun toString(): String {
            return "list:${list};map:${map}"
        }
    }
    

    SupportArray

    • 实现 MutableList 被list代理了接口的实现
    • 实现MutableMap 被map代理了接口的实现

    使用SupperArray

    fun main() {
        val supperArray = SupperArray<String>()
        supperArray.add("Hello")
        supperArray["Hello"] = "wwwwwwwwww"
        supperArray["Word"] = "fffffffffffff"
        supperArray[4] = "$$$$$$$$$"
        println(supperArray)
    }
    

    输出

    list:[Hello, null, null, null, $$$$$$$$$];
    map:{Word=fffffffffffff, Hello=wwwwwwwwww}
    
    <2>属性代理
    getter方法代理
    class PersonNow(var sex: Boolean) {
        val firstName: String by lazy {
            if (sex)
                "nan"
            else
                "nv"
        }
    }
    

    源码分析

    --lazy方法

    public actual fun <T> lazy(initializer: () -> T): Lazy<T> = 
    SynchronizedLazyImpl(initializer)
    

    --Lazy.kt

    @kotlin.internal.InlineOnly
    public inline operator fun <T> Lazy<T>.getValue(thisRef: Any?, 
    property: KProperty<*>): T = value
    

    可以看到Lazy定义的方法 getValue

    getValue(thisRef: Any?, property: KProperty<*>)
    

    要想代理属性的getter需要重写该方法

    setter方法代理

    对于setter方法,其代理可分为两步:开始设置和设置之后

    Delegates.observable代理设置之后
    class Manger {
        var state:Int by Delegates.observable(0){
            property, oldValue, newValue ->
            println("$oldValue,$newValue")
        }
    }
    
    Delegates.vetoable代理设置之前
    class Mantou {
        var ss: Int by Delegates.vetoable(0) { property, oldValue, newValue ->
            println("$oldValue,$newValue")
            newValue % 2 == 0
        }
    }
    

    调用

    fun main() {
        val mantou = Mantou()
        mantou.ss = 1
        mantou.ss = 2
        mantou.ss = 3
        mantou.ss = 4
    }
    

    结果

    0,1
    0,2
    2,3
    2,4
    

    Delegates.vetoable 给定初始化值后其结果返回值为Boolean,如果返回true表示允许本次对属性的设置,属性的值为newValue,返回false表示拦截此次设置值,属性的值仍然为oldValue

    源码得知
    Delegate.observable 返回 ReadWriteProperty

    public interface ReadWriteProperty<in R, T> {
        public operator fun getValue(thisRef: R, property: KProperty<*>): T
        public operator fun setValue(thisRef: R, property: KProperty<*>, value: T)
    }
    

    对于var 来说需要实现下列方法,就可以代理属性

        public operator fun getValue(thisRef: R, property: KProperty<*>): T
        public operator fun setValue(thisRef: R, property: KProperty<*>, value: T)
    

    对val来说 实现下方法即可代理属性

      public operator fun getValue(thisRef: R, property: KProperty<*>): T
    

    具体例子

    class Food {
        var x: Int by X()
    }
    
    class X {
        operator fun getValue(thisRef: Any?, property: KProperty<*>): Int {
            println("getValue")
            return 2
        }
    
        operator fun setValue(thisRef: Any?, property: KProperty<*>, i: Int) {
            println("setValue")
        }
    }
    
    fun main() {
        val food = Food()
        food.x=1;
        println(food.x)
    }
    

    运行结果

    setValue
    getValue
    2
    

    对food 的属性 x 调用其getter或者setter时会调用的属性代理

    5,单例object

    Kotlin中单例

    • 使用object进行定义
    • 默认生成无参构造函数且不能自定义构造器,但是可以通过init{}代码块进行一些初始化操作
    • 跟普通的Kotlin类一样可以实现接口,可接继承其他类
    • 其本质就是Kotlin使用饿汉式内部生成了INSTANCE的实例
    public object Sigten {
        var x: Int = 0
        var y: String = ""
        fun mm() {}
    }
    

    Kotlin中访问

    fun main() {
        Sigten.x = 3
        Sigten.y = "WWWWWWWW"
        println("${Sigten.x}||${Sigten.y}")
    }
    

    Java中访问

    Sigten.INSTANCE.mm();
    Sigten.INSTANCE.setX(1);
    Sigten.INSTANCE.getX();
    

    由于Kotlin是一门跨平台语言,不能因为Jvm上有静态变量就定义出相应的变量类型,这对于C语言和JavaScript行不通

    @JvmStatic 模拟JVM平台的静态变量
    public object Sigten {
        @JvmStatic var x: Int = 0
        @JvmStatic fun mm() {
        }
    }
    

    Java调用

    Sigten.mm();
    Sigten.setX(1);
    Sigten.getX();
    
    @JvmField 不让Kotlin属性生成getter和setter,直接访问属性
    public object Sigten {
        @JvmStatic var x: Int = 0
        @JvmField var y:Int=0
        @JvmStatic fun mm() {
        }
    }
    

    Java调用

    Sigten.mm();
    Sigten.setX(1);
    Sigten.getX();
    Sigten.y=1;
    
    伴生对象
    • 是某个类的另一半
    • 其名称和所在类相同
    • 伴生对象也是单例的
    class FoodX {
        companion object {
            @JvmStatic var x: Int = 0
            @JvmStatic fun mm() {
            }
        }
    }
    

    想当于Java中

    public class FoodX {
        public static int x = 0;
        public static void mm(){}
    }
    

    6,内部类

    • 内部类分为静态内部类和非静态内部类
    • 静态内部类不需要引用外部对象,可直接创建对象
    • 非静态内部类需要引用外部对象,容易引起内存泄露
    class Outer{
        //内部类
        inner class Inner(){}
        //静态内部类
        class StaticInner(){}
    }
    

    使用

    fun main() {
        //创建非静态内部类
        var inner = Outer().Inner()
        //创建静态内部类
        var staticInner = Outer.StaticInner()
    }
    
    <1>内部object
    • 内部object 不存在非静态,都是静态
    object OuterObject {
        object InnerStaticObject {
            var x: Int = 0
        }
    }
    

    使用

    OuterObject.InnerStaticObject.x=3
    
    <2>匿名内部类
    object :Runnable{
            override fun run() {
            }
        }
    
    和Java中不同的是,Kotlin中匿名内部类支持多实现或继承父类

    例如

    object :Runnable,Cloneable{
            override fun run() {
            }
        }
    
    Java中也可以实现方法中多继承 使用LocalClass
    public class TestObjeck {
        public static void main(String[] args) {
            class LocalClass implements Cloneable,Runnable{
                @Override
                public void run() {
                }
            }
        }
    }
    
    Kotlin中不仅支持LocalClass 还支持localMethod(方法中定义方法)
      object :Runnable,Cloneable{
            override fun run() {
            }
        }
    
        class LocalClass:Runnable,Cloneable{
            override fun run() {
            }
        }
    
        fun localMethod(){}
    

    7,数据类 data class

    component: 定义在主构造器中的属性又被称为component

    • 数据类和一般类最大的区别就在于component
    • Kotlin中的data class 不等价与Java bean
    • 编译器会基于component 自动生成 equals/hashCode/toString/copy
    data class TT(val name: String, val age: Int) {}
    fun main() {
        var tt = TT("", 4)
    }
    
    如何合理的使用data class
    • 单纯的把data class 当成数据结构使用既是纯数据,大多数情况下不需要额外的实现
    • 主构造函数的变量类型 最好为基本类型\String 或者其他data class 保证数据类为纯数据
    • component 不能定义getter和setter 为了防止通过getter或者setter 进行数据篡改
    • 定义构造函数的变量 最好定义为 val ,如果可变会导致其hashCode等值的改变 如果该对象在hashset中 则无法移除该元素

    为了实现一些特殊的需求,需要data class 有无参构造和可以被继承,官方提供了 noArg 和allOpen 插件进行支持

    id 'org.jetbrains.kotlin.plugin.noarg' version '1.3.60'
    id 'org.jetbrains.kotlin.plugin.allopen' version '1.3.60'
    

    8, 枚举类 enum class

    • 和 Java中枚举相似 区别是通过 enum class 定义

    无参构造枚举

    enum class CC {
        Idle, Busy
    }
    

    有参构造枚举

    enum class DD(arg: Int) {
        Idle(0), Busy(1)
    }
    

    枚举实现接口--统一实现方式

    enum class FF : Runnable {
        Idle, Busy;
    
        override fun run() {
        }
    }
    

    枚举实现接口--单独实现方式

    enum class EE : Runnable {
        Idle {
            override fun run() {
            }
        },
        Busy {
            override fun run() {
            }
        };
    }
    
    <1>因为enum 枚举本身是个类 所以可以为其定义扩展方法
    <2>枚举本事是可数的所在when 条件语句中可以不加else
    <3> 因为枚举本身有顺序的概念所以可以对其进行比较大小
    <4> 枚举有顺序所以也有区间概念
    enum class Color {
        White, Red, Black, Pink, Ori, Yellow
    }
    fun main() {
        val ran = Color.Black..Color.Yellow
        val color = Color.White
        color in ran
    }
    

    9,密封类 sealed class

    • 密封类是一种特殊的抽象类
    • 密封类的子类必须定义在与自身相同的文件中
    • 因为密封类的子类定义范围有限,所以密封类的子类有限,一旦定义好密封类的子类后,外部不可能出现其他子类
    • 密封类首先是个抽象类,其次是个密封类

    例子

    sealed class PlayState
    
    object Idle : PlayState()
    
    object Playing : PlayState() {
        fun start() {}
        fun stop() {}
    }
    
    object Error : PlayState() {
        fun recover() {}
    }
    

    使用上面定义的几个类型做个小案例

    class Player {
        var state: PlayState = Idle
    
        fun play() {
            this.state = when (val state = this.state) {
               is Idle -> {
                    Playing.also {
                        it.start()
                    }
                }
                is Playing -> {
                    state.stop()
                    state.also {
                        it.start()
                    }
                }
                is Error -> {
                    state.recover()
                    Playing.also {
                        it.start()
                    }
                }
            }
        }
    }
    
    如何区分使用密封类还是枚举呢
    1. 如果需要进行类型区分则是使用密封类,如果要进行值的区分 则用枚举
    2. 枚举类不能创建对象,而密封类可以创建对象

    枚举不能创建对象这点个人理解起来比较难一点,为什么不能枚举就不能创建对象呢?
    因为枚举就是实例对象的存在<O(∩_∩)O哈哈~>
    而密封类则不同,他的分支是类型,只要是子类的对象就行,因此可以创建多个对象

    10,内联类 inline class

    • 内联类是对某一个类型的包装
    • 类似于Java中装箱类型的一种类型
    • 编译器会尽可能使用被包装的类型进行优化
    • 内联类目前在1.3版本中处于公测阶段,谨慎使用
    使用注意
    1. 内联类不能有backingfiled 只能有方法
    2. 内联类可以实现接口,但不能继承父类也不能被继承

    例子

    inline class BoxInt(private val value: Int) {
        operator fun inc(): BoxInt {
            return BoxInt(value + 1)
        }
    }
    
    内联类的使用场景
    • 官方 使用内联类 实现无符号类型
    • 内联类模拟枚举,因为枚举内存开销比较大,和dex文件大小

    相关文章

      网友评论

          本文标题:六,类的进阶

          本文链接:https://www.haomeiwen.com/subject/wpzjnctx.html