Kotlin(二)面向对象

作者: zcwfeng | 来源:发表于2020-12-19 18:24 被阅读0次

    面向对象

    2.1 kotlin中的类

    class Bird {
        val weight: Double = 500.0
        val color: String = "blue"
        val age: Int = 1
        fun fly() {}// 全局可见
    }
    
    1. 不可变属性成员。用val声明不可见成员,底层是java 的final实现
      2)属性默认值,除非显示声明延迟初始化,否则必须指定属性默认值。java 不用指定,引用默认null,其他对应有默认值
      3)不同的可访问的修饰符。kotlin默认是全局可见。java 默认是包可见,java 必须用public才能达到同样效果

    可带有属性和默认方法的接口

    Java 8 中引入

    //java 8 默认实现
    public interface Flyer {
        long height = 1_000l;
        public String kind();
        default public void fly(){
            System.out.println("I can fly");
        }
    }
    
    

    kotlin 是基于java6 但是,同样的实现,反编译底层使用final static final class DefaultImpl实现

    //kotlin 中的接口
    interface Flyer {
        val speed: Int
        val height: Long
            get() = 1_000L
    
        fun kind()
        fun fly() {
            println("I can fly")
        }
    }
    

    虽然,kotlin接口支持属性声明,但是通过一个get方法来实现,对比不难发现

    2.2 更简洁构造类的对象

        // kotlin 直接声明一个对象
        val bird = Bird()
    

    对于Java来说有多个重载

    package top.zcwfeng.java.base;
    public class Bird {
        public double weight;
        public String color;
        public int age;
        public Bird(double weight) {
            this.weight = weight;
        }
        public Bird(String color, int age) {
            this.color = color;
            this.age = age;
        }
        public Bird(double weight, String color, int age) {
            this.weight = weight;
            this.color = color;
            this.age = age;
        }
    }
    
    

    缺陷:

    • 支持任意多个参数组合创建对象,实现多个构造方法(用设计及模式 Builder模式能解决,但是还是比较麻烦,各种组合对外暴露)
    • 每个构造方法餐宿不同冗余严重

    Kotlin 构造方法的解决

    1. 多参数指定默认值,避免不必要的重载
    2. init 语句块,属于构造的一部分,但是确实分离开的,对初始化进行额外操作用
    class Bird(weight: Double = 0.00, age: Int = 0, color: String = "blue") {
        val weight: Double
        val color: String
        val age: Int 
        fun fly() {}
        //构造方法可以在init 块调用
        init {
            this.weight = weight
            this.color = color
            this.age = age
        }
        init {
            println("多个init")
        }
    }
    

    可以有多个init 可以将初始化进行职能分离

    3. 延迟初始化: by lazy 和 lateinit

    by lazy

    class Bird3Bird(val weight: Double, age: Int, color: String ) {
        val sex:String by lazy{
            if (color=="yellow") "male" else "female"
        }
    }
    
    • by lazy 必须是引用不可变,而且不能通过var声明
    • 在第一次调用是,才被赋值,赋值 后不能更改

    lazy 背后是接受一个lambda 并且返回一个Lazy<T>实例的函数

    /**
     * Creates a new instance of the [Lazy] that uses the specified initialization function [initializer]
     * and the default thread-safety mode [LazyThreadSafetyMode.SYNCHRONIZED].
     *
     * If the initialization of a value throws an exception, it will attempt to reinitialize the value at next access.
     *
     * Note that the returned instance uses itself to synchronize on. Do not synchronize from external code on
     * the returned instance as it may cause accidental deadlock. Also this behavior can be changed in the future.
     */
    public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)
    

    第一此 访问执行lazy的lambda表达式并记录结果,后续访问该属性,只返回记录的结果。
    系统会默认给lazy属性加上同步锁,也就是LazyThreadSafetyMode.SYNCHRONIZED,在同一时刻只允许一个线程对lazy属性进行初始化,所以他是线程安全的。

    如果 你能确认该属性可以并行执行,没有线程安全问题,那么可以给lazy传递LazyThreadSafetyMode.PUBLICATION参数。

    还可以传递LazyThreadSafetyMode.NONE,不会有任何线程开销,这个没有线程安全的保证了。

    class Bird3Bird(weight: Double, age: Int, color: String ) {
        //并行模式
        val sex:String by lazy(LazyThreadSafetyMode.PUBLICATION){
            if (color=="yellow") "male" else "female"
        }
        // 不安全,没有线程开销
        val sex2:String by lazy(LazyThreadSafetyMode.NONE){
            if (color=="yellow") "male" else "female"
        }
    }
    

    lazyinit

    用于var不能用于基本类型,要用Integer等包装

    class Bird(weight: Double = 0.00, age: Int = 0, color: String = "blue") {
        val weight: Double
        val color: String
        val age: Int
        fun fly() {}
        //构造方法可以在init 块调用
        init {
            this.weight = weight
            this.color = color
            this.age = age
        }
        
        lateinit var sex:String
        
        fun printSex(){
            if (color=="yellow") "male" else "female"
            print(sex)
        }
    
        init {
            println("多个init")
        }
    }
    
    

    lateinit var 用语法Delegates.notNull<T>实现,举个栗子:

    // 委托 lateinit var 实现原理
    var test by Delegates.notNull<Int>()
    fun doSomething(){
        test = 1
        println("test value is ${test}")
        test = 2
    }
    

    2.3 主从构造方法

    class Bird4(age: Int) {
    
        var age: Int
    
        constructor(color: String):this(0) {
            //....
            println("------$color-----从构造")
        }
    
        constructor(color: String,weight: Double):this(0) {
            //....
            println("------$color-----从构造2")
        }
    
        init {
            this.age = age
            println("init")
        }
    
    
    }
    

    kotlin也支持多个构造方法。这个看Android中自定义view多个重载构造
    目的方便扩展第三方库

    2.4 限制修饰符,访问控制

    • kotlin中的extends和implemens用的是符号”:“
    • kotlin默认是不能继承和重写的,必须加上关键字open 才可以
    
    open class Bird2 {
        val weight: Double = 500.0
        val color: String = "blue"
        val age: Int = 1
        open fun fly() {}// 全局可见
    }
    
    class Penguin: Bird2() {
        override fun fly() {
            super.fly()
        }
    }
    

    根据里氏替换原则类默认final的,如果fly 不加open也是不允许继承的

    除了利用final外,kotlin还可以用sealed密封类来限制继承

    要继承sealed类,就必须在同一个文件中,其他文件无法实现。而且sealed类是抽象类实现不能被初始化,背后实现是一个抽象类

    修饰符 含义 与Java对比
    public Kotlin中默认全局可见 与java中的public相同
    protected 受保护修饰,类和子类可见 含义一致,除了类和子类,包内可见
    private 私有修饰,类内修饰只有本类可见,类外修饰只有文件内可见 只有类内可见
    internal 模块内可见

    2.4 多继承问题

    C++ 支持多继承的。有一个经典问题---> 钻石问题---骡子的多继承困惑,我们用类图表示

    2020-12-19 16.23.53.png

    也叫做菱形问题,但是kotlin和java一样支持单继承

    kotlin可以通过其他方式完成多继承需求

    接口实现多继承

    一个类实现多个接口这个是java经常干的事。kotlin也是这样。

    
    //多继承
    interface Flyer1{
        fun fly()
        fun kind() ="[Flyer]flying animals"
    }
    interface Animal{
        val name:String
        fun eat()
        fun kind() = "[Animal] flying animals"
    }
    
    class Bird6(override val name:String):Flyer1,Animal{
        override fun eat() {
            println("i can eat")
        }
    
        override fun fly() {
    
            println("I can fly")
        }
    
        override fun kind() = super<Flyer1>.kind()
    }
    

    Bird6 继承了Flyer1和Animal接口,但他们都用有默认kind的实现,同样会引起钻石问题。kotlin 提供了super关键字解决问题。可以指定继承那个父类接口super<Flyer1>.kind(). 也可以实现kind覆盖父类方法

    1)Kotlin 中实现一个接口,需要实现没有默认实现的方法以及未初始化的属性。同时实现多个接口有相同名字的方法默认实现,需要主动指定使用那个接口的方法或者重写方法
    2)如果是默认的接口方法,可以在实现类中用super<T> 的方式调用,T为拥有改方法的接口名字
    3)在实现接口的方法和属性时候,需要带上Override关键字,不能省略

    我们是通过主构造方法实现的Animal接口中的name属性。
    还可以通过val 声明构造参数,其实是在内部定义了一个同名的属性

    class Bird6(name:String):Flyer1,Animal{
        override val name:String
        
        init {
            this.name = name
        }
        
        override fun eat() {
            println("i can eat")
        }
    
        override fun fly() {
    
            println("I can fly")
        }
    
        override fun kind() = super<Flyer1>.kind()
    }
    

    getter 和 setter

    koltin没有字段,只有属性。幕后都帮着实现好了setter和getter
    当然我们自己可以主动声明。

    1)val 声明的属性只有 getter 因为不可以修改;var 声明可同时拥有setter和getter
    2)用private声明的属性,编译器会省略getter和setter因为外部已经无法访问了

    这个和C# setter getter 相似

    内部类解决多继承问题

    java 内部类

    public class OuterJava {
        private String name ="Java inner clas syntax";
        class InnerJava{
            public void printName(){
                System.out.println(name);
            }
        }
    }
    

    Kotlin 必须用inner 修饰内部类否则相当于java中static修饰的嵌套类

    // 内部类
    class OuterKotlin{
        val name = "This is kotlin inner class syntax"
        inner class InnerKotlin{
            fun printName(){
                println(name)
            }
        }
    }
    

    java 加static 嵌套类,不加内部类
    kotlin 不加 嵌套类,加 inner内部类

    内部类可以包含对外部name属性的引用,嵌套类不行

    open class Hourse{
        fun runFast(){
            println("I can run fast")
        }
    }
    
    open class Dounkey{
        fun runFast(){
            println("I can work long time")
        }
    }
    
    class Mule{
        fun runFast(){
            HorseC().runFast()
        }
        
        fun doLongTimeThing(){
            
            DounkeyC().runFast()
        }
        private inner class HorseC:Hourse()
        private inner class DounkeyC:Dounkey()
    }
    
    1. 类内部定义多个内部类,每个内部类都有自己独立的状态,与外部对象信息相互独立
      2)让HorseC和DonkeyC 分别集成Horse,Donkey.就可以在Mule中定义他们的实现从二获得不同的状态和行为
      3)利用private 外部类不能访问内部,具有很好的封装性
      因此某种场景,内部类也可以实现多继承

    使用委托代替多继承

    委托,(C# 里面有delegate思想是一样的)我们之前有提过by lazy,除了延迟外,委托还有观察的行为,比较像观察者模式。

    open class Flyer2 : CanFly {
        override fun fly() {
            println("I can fly")
        }
    }
    
    open class Animal2 : CanEat {
        override fun eat() {
            println("I can eat")
        }
    }
    
    class Bird7(flyer: Flyer2, animal: Animal2) : CanFly by flyer, CanEat by animal {}
    
    // 委托
        val mFly = Flyer2()
        val mAnimal = Animal2()
        val mBird = Bird7(mFly,mAnimal)
        mBird.fly()
        mBird.eat()
    

    1) 接口是无状态,只能提供默认简单实现。利用委托的方式,虽然是接口委托,但是用类实现,可以有更强大的能力
    2)A 委托 B,C 并不是想组合一样A.B.method 而是直接A.metod 更加直观

    2.5 数据类

    Java 中 定义Bean很麻烦。setter,getter .有时候需要重写hashcode,equals方法

    而kotlin只需要一行,data class 声明

    data class Bird8(var weight:Double,var age:Int,var color:String)
    

    反编译后,和javaBean 一模一样,多了两个方法copy和componentN

    copy 帮助我们实现了简单的浅拷贝相当于,数据类被var修饰,引用是可以修改的

    componentN 对应的具体按顺序属性,kotlin中称为解构。

    还提供了元组类Pair 两个属性 Triple 三个属性

    // 元祖
        val pair = Pair("a",11)
        val triple = Triple(2,3.0,"aaa")
        val name= pair.first
        val pV = pair.second
        val tripA = triple.first
        val tripB = triple.second
        val tripC= triple.third
        // 解构
        val (pName,pValue) = Pair("name",2)
        val (ta,tb,tc)= Triple(2,3,"blue")
        
    

    数据类的使用约定

    • 数据类必须有一个构造方法,至少包含一个参数,无参没意义
    • 数据类方法强制使用var 或者val,普通类可以不强制
    • data class 之前不能用abstract open sealed 或者innder修饰
    • data class kotlin 1.1 之后可以实现接口也可以继承类

    2.6 object

    kotin不会出现static,因为object替代了他

    kotlin 伴生对象,cmpaanion object{} 替代了java中的static 声明的类中定义的属性

    Java 中的类

    public class Prize {
        private String name;
        private int count;
        private int type;
    
        public Prize(String name, int count, int type) {
            this.name = name;
            this.count = count;
            this.type = type;
        }
        static int TYPE_REDPACK = 0;
        static int TYE_COUPON = 1;
        static boolean isRedPackl(Prize prize){
            return prize.type == TYPE_REDPACK;
        }
    
        public static void main(String[] args) {
            Prize prize = new Prize("红包",10,Prize.TYPE_REDPACK);
            System.out.println(prize.isRedPackl(prize));
        }
    }
    
    

    Kotlin 伴生类替换static

    class Prize private constructor(val name: String, val count: Int, val type: Int) {
        companion object {
            val TYPE_COMMON = 0
            val TYPE_REDPACK = 1
            val TYE_COUPON = 2
    
            val defaultCommonPrize = Prize("普通奖品", 10, Prize.TYPE_COMMON)
            fun newRedPackPrize(name: String, count: Int) = Prize(name, count, Prize.TYPE_REDPACK)
            fun newCoupo(name: String, count: Int) = Prize(name, count, Prize.TYE_COUPON)
            fun defaultCommonPrize() = defaultCommonPrize
        }
    }
    
    
     // 伴生
        val redPackPrize = Prize.newRedPackPrize("红包",10)
        val couplePrize = Prize.newCoupo("十元代金券",10)
        val commonPrize = Prize.defaultCommonPrize()
    

    伴生对象是实现工厂方法的一种方式。

    object 是天生单例

    object DataConfig{
      var host:String = "127.0.0.1"
    }
    

    object 全局声明对象只有一个,并不用语法上面初始化,甚至不需要构造方法。

    object 表达式

    Java 中的匿名内部类

    public class 匿名内部类 {
    
        public static void main(String[] args) {
            List<String> list = Arrays.asList("jffy", "lrri", "tony");
            Collections.sort(list, new Comparator<String>() {
                @Override
                public int compare(String o1, String o2) {
                    if (o1 == null)
                        return -1;
                    if (o2 == null)
                        return 1;
                    return o1.compareTo(o2);
                }
            });
        }
    }
    

    Kotlin object 表达式改善

    val comparator = object : Comparator<String>{
        override fun compare(o1: String?, o2: String?): Int {
            if(o1 == null)
                return -1
            else if(o2 ==null)
                return 1
            return o1.compareTo(o2)
        }
    }
    
        Collections.sort(list,comparator)
    

    object 表达式可以赋值给一个变量,而且每次运行都会生成一个新的对象

    使用Lambda表达式改造

    Java

    Collections.sort(list,(String o1, String o2)->{
                if (o1 == null)
                    return -1;
                if (o2 == null)
                    return 1;
                return o1.compareTo(o2);
            });
    

    Kotlin

    val comparator2 = Comparator<String> { o1, o2 ->
        if (o1 == null)
           return@Comparator -1
        else if (o2 == null)
            return@Comparator 1
        o1.compareTo(o2)
    
    }
    

    匿名内部类接口需要实现一个方法的时候Lambda更加适合
    匿名内部类有多个方法的时候object更加适合

    相关文章

      网友评论

        本文标题:Kotlin(二)面向对象

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