0
点赞
收藏
分享

微信扫一扫

kotlin杂谈系列三

雅典娜的棒槌 2022-04-02 阅读 16

Kotlin杂谈系列三

  • 在kotlin中似乎有种对象和类可以分开的感觉来我一起感受一下吧

  • 先讲讲kotlin的匿名内部类 kotlin的匿名内部类在实现上很简单 被官方形象的成为对象表达式

fun descPeople(){
    //这个就是一个匿名内部类
    val people = object {
        val name = "Jack"
        val age = 19
        val sex = "男"
    }
    
    //利用 object{} 这就是对象表达式 他会返回一个对象
    
    println("姓名:${people.name} 年龄:${people.age} 性别:${people.sex}")
}
  • 有几点注意事项我不是很明白我先写出来

  • 匿名对象的内部类型不能作为函数或方法的返回值类型

  • 匿名对象的内部类型不能作为函数或方法的参数类型

  • 如果他们作为属性存储在类中,他们就会被视为Any类型 他们的属性和方法都无法直接访问

  • 我谈谈我对这几个注意事项的理解 这里匿名对象的内部类型我认为应该是指的匿名内部类的类型

class Test03 {

    val people = object {
        val name = "Jack"
        val age = 19
        val sex = "男"
    }

    fun printPeopleClass() = println(people::class.java) 

}

fun main() {
    Test03().printPeopleClass()//输出的结果是 class Test03$people$1
}
  • 感觉这里所指的就是这个意思 匿名对象的内部类型 在这里就是 Test03$people$1 这样的话就好理解这几个注意事项了
  • 第一点和第二点的理解 就迎刃而解了 我都不知道匿名内部类具体的类型怎么把他当做参数来传递 和返回呢?? 这不是笑话吗??
  • 第三点 还是同理我不知道他的具体类型 我们这访问他的方法和属性 至于前半句可以看反编译的代码

public final class Test03 {
   @NotNull
   private final Object people = new Object() {
      @NotNull
      private final String name = "Jack";
      private final int age = 19;
      @NotNull
      private final String sex = "男";

      @NotNull
      public final String getName() {
         return this.name;
      }

      public final int getAge() {
         return this.age;
      }

      @NotNull
      public final String getSex() {
         return this.sex;
      }
   };

   @NotNull
   public final Object getPeople() {
      return this.people;
   }

   public final void printPeopleClass() {
      Class var1 = this.people.getClass();
      System.out.println(var1);
   }
}


// Test03Kt.java
import kotlin.Metadata;
public final class Test03Kt {
   public static final void main() {
      (new Test03()).printPeopleClass();
   }

   // $FF: synthetic method
   public static void main(String[] var0) {
      main();
   }
}
  • 看了代码之后就相信你理解了作为属性时是Any类型的意思了

  • 对象表达式可以还是可以限制类型的范围但是你还是不知道具体类型

    val student = object : People(){
        override var name = "Jack"
        val age = 19
        val sex = "男"
    }

    fun printPeopleClass() = println(student::class.java)

    fun descStudent(stu:People){
        println("Pelple : ${stu.name} ${stu}")
    }

fun main() {
     descStudent(student)
}


open class People {
    open var name = ""
    get() {
        return field
    }
    set(value) {
        field = value
    }

}

//输出 : Pelple : Jack 
//因为在kotlin中你看我们似乎是对字段进行操作其实我们并没有操作过字段 kotlin帮我们把字段隐藏了 而是在访问属性时调用getter 设置属性时 调用setter 所以这里才会打印出来Jack
//而java可是直接访问字段信息也会打印出来

//这里补充一个小知识 : 
/*
	在定义类的时候不能 单纯的执行行为 可以定义状态和定义行为
*/
  • 所以对象表达式的作用最大的作用就是 可以让几个局部临时的变量不关系紧密

  • 单例和顶级函数

    kotlin给我们提供了顶级函数 相当于可以用来替代java的工具类但是单例又是来干嘛的呢

    单例就是保证应用中只存在一个对象实例 所以kotlin编译器可不认为他是一个类认为他是一个对象

    即然顶级函数可以替代工具类了那为什么还有单例呢 单例的作用就是可以让一些有联系而且依赖于状态的顶级函数在一个单例里面方便调用

看看在kotlin中实现单例的方法吧

//就是这样就实现了单例
//就是将class变成了object就可以实现了 里面可以有属性 可以有方法
object Sun {
    fun up(){
        println("太阳上升了")
        println(this.javaClass)
    }
}

fun main() {
    Sun.up()
}
  • 总结一下 : 如果我们更加关注行为 动作 和 计算的时候用函数和单例是很有意义的

  • 说到类大家都不陌生了 但是大家一定很烦写一些样板代码吧 所以把一些样板代码交给了IDE 而kotlin则是把样板代码交给了编译器 让编译器在编译时生成一些样板代码

  • 属性和字段

    在java中字段是暴露出来了 (就是我们定义的一个类变量)

    public class Person{
        String name = "Jack";
    }
    

    name 就是一个字段 但是他还不是属性 如果他要变成属性的话那么就的给他提供一套settergetter

    
    public class People {
        public String name = "Jack";
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    }
    
    
  • 总结一下 : 属性就是字段 + setter + getter

那kotlin是怎么实现的呢?

  • kotlin 很聪明直接帮你把字段隐藏了交给一个在getter和setter里面有特殊含义的引用(field)

  • 自动给你一套setter 和 getter

  • 意思是 你定义的类变量其实就是属性了 而且访问的时候你是无法直接访问field

  • 当然如果你觉得你想自己实现settergetter 可以在属性后面自定义

    class Student {
        var name = "jack"
        set(value) {
            if (value == "jack"){
                field = "tom"
            }else{
                field = "jack"
            }
        }
    }
    
    fun main() {
        val stu = Student()
        stu.name = "jack"
        println(stu.name)
    
    }
    
    • 在自定义的时候要注意 :

      1. 就是如果你的getter 和 setter 存在 而且必须要有一个是系统默认实现的 才会有幕后字段 或者在自定义的getter和setter中用到了field引用 否则不会有幕后字段的 就是说没有存储数据的空间

      2. 什么意思呢? 就是如果你的属性是只读的 那么他只有一个getter 如果你给他自定义了 而且还没有用到field那么这个属性是没有储存数据的空间的(即没有幕后字段) 他在字节码层面会变成一个方法

        kotlin杂谈系列三
        class Student {
            val name : String = "jack"
            get() {
                return "tom"
            }
        }
        
        // StudentKt.java
        import kotlin.Metadata;
        public final class StudentKt {
           public static final void main() {
              Student stu = new Student();
              String var1 = stu.getName();
              System.out.println(var1);
           }
        
           // $FF: synthetic method
           public static void main(String[] var0) {
              main();
           }
        }
        // Student.java
        import kotlin.Metadata;
        import org.jetbrains.annotations.NotNull;
        
        public final class Student {
           @NotNull
            //注意这里变成了一个方法
          /*-------------------------------*/
           public final String getName() {
              return "tom";
           }
          /*-------------------------------*/
        }
        
        • 在kotlin中为属性赋值和访问都是通过setter 和 getter方法来的
  • kotlin的访问修饰符

    • public(默认到的)
    • private(私有的)
    • protected(子类可以访问父类的)
    • internal(同一模块的类可以访问) 这并不是真正的修饰符 而是在底层通过特殊的命名规则来限制的
  • 默认的情况下,getter和setter的访问权限和属性的访问权限保持一致当然也可以单独设置

    class Student {
        var name : String = "jack"
        private set
    
    }
    
    • 但是注意 属性和getter的访问权限必须一致 也就是说你能让访问权限不一致的只有setter
  • 初始化块

    他是作为主构造函数的一部分来执行的 ----- 当主构造函数的逻辑不只是用来设置值的时候就会使用init代码块来初始化动作和行为之类的 初始化块和定义的属性是平级的关系

  • 在类中属性和方法,代码块的顺序

    顶部声明属性 >>> 在有需要到的时候编写代码块 >>> 然后实现二级构造函数 >>> 最后构造你的各种方法

  • 当然在init{}里面执行的逻辑越少越好 因为如果这个对象是个UI对象的话那么构造时间够长的话 用户的感觉是卡顿

  • 构造器

    在kotlin中构造器分为 主构造器 和 次构造器

    当我们没有实现构造器的时候默认实现一个无参的主构造器

  • 主构造器就是定义类的第一行 如果没有特殊的访问权限可以省略关键字 constrcutor 可以在主构造参数里面定义属性(在前面加val 或者 var)和参数

    class Student(val age : Int ,name : String) {
        var name : String = name
        private set
    }
    //(val age : Int ,name : String) 这个里面 age 是属性 而name是参数
    
  • 二级构造器就必须要有关键字 constrcutor 而且不能在其中定义属性只能是参数 最重要的是他必须调用其他的二级构造函数或者是主构造函数(只要不搞成循环就可以)

    class Student(val age: Int) {
        var name: String = ""
        var sex = ""
    
        
        constructor(age: Int, name: String) : this(age) {
            this.name = name
        }
    
       
        constructor(age: Int, name: String, sex: String) : this(age, name) {
            this.sex = sex
        }
    
    
    }
    
  • 伴生对象和类成员

    伴生对象是在类中定义的单例 在没有名字的情况下很容易看成匿名内部类

open class Boy{
    companion object {
        fun getIntance() = Boy()
    }
    
    val boy = object{
        fun hi(){
            println("hi~~~")
        }
    }
}
  • 看起来很像吧 就是多了一个companion 但是他们是不一样的 一个是单例 一个是匿名内部类 但是他们的本质上是一样的 都是在创建类的同时又是一个对象

  • 在kotlin中用伴生对象充当类成员 但是实际他里面的属性直接属于类成员

open class Boy{
    companion object {
        val name : String = "Jack"
        fun getIntance() = Boy()
    }

}

public class Boy {
   @NotNull
  
   private static final String name = "Jack";//kotlin中的幕后字段
   @NotNull
   public static final Boy.Companion Companion = new Boy.Companion((DefaultConstructorMarker)null);


   public static final class Companion {
      @NotNull
       //getter
      public final String getName() {
         return Boy.name;
      }

      @NotNull
      public final Boy getIntance() {
         return new Boy();
      }

      private Companion() {
      }

      // $FF: synthetic method
      public Companion(DefaultConstructorMarker $constructor_marker) {
         this();
      }
   }
}


  • 可以看到把伴生对象等价于类成员 还是有一定的缺点的那就是方法都还是实例方法 只是字段还在类成员上

  • 但是在和java的互操作上 还是有点割裂的 因为kotlin的访问伴生对象的属性像java访问静态成员一样丝滑 可是java来访问kotlin的伴生对象就不丝滑了 所以可以加上一个注解

    open class Boy{
        companion object {
            @JvmStatic
            val name : String = "Jack"
            @JvmStatic
            fun getIntance() = Boy()
        }
    
    }
    
    
    public class Boy {
       @NotNull
       private static final String name = "Jack";
       @NotNull
       public static final Boy.Companion Companion = new Boy.Companion((DefaultConstructorMarker)null);
    
       @NotNull
        /*--------------这里----------------*/
       public static final String getName() {
          Boy.Companion var10000 = Companion;
          return name;
       }
        /*--------------------------------*/
    
       @JvmStatic
       @NotNull
        /*--------------这里----------------*/
       public static final Boy getIntance() {
          return Companion.getIntance();
       }
        /*--------------------------------*/
    
      
       public static final class Companion {
          /** @deprecated */
          // $FF: synthetic method
          @JvmStatic
          public static void getName$annotations() {
          }
    
          @NotNull
          public final String getName() {
             return Boy.name;
          }
    
          @JvmStatic
          @NotNull
          public final Boy getIntance() {
             return new Boy();
          }
    
          private Companion() {
          }
    
          // $FF: synthetic method
          public Companion(DefaultConstructorMarker $constructor_marker) {
             this();
          }
       }
    }
    
    
    • 可以看到对于属性确实是变成了静态的了但是方法还是实例方法 只是在外部转接了一下
  • 注意一下 : 在单例中尽量不要用可变的属性 极有可能导致多线程不安全

  • 访问同伴

    在没有给伴生对象指定名字的情况下 使用 Companion 来访问

    open class Boy{
        val com = Boy.Companion//这里
        companion object {
            @JvmStatic
            val name : String = "Jack"
            @JvmStatic
            fun getIntance() = Boy()
        }
    
    }
    

    有名字的话就要用显式名字

    open class Boy{
        val com = Boy.X
        companion  object X{
            @JvmStatic
            val name : String = "Jack"
            @JvmStatic
            fun getIntance() = Boy()
        }
    
    }
    
  • 泛型类

    泛型类和java的操作基本一致 只是在类型安全上做的比较多了 前面的杂谈上将过了就不多说了

  • kotlin的专用类 — 数据类(data)

  • 数据类着重于数据的保存 而不是行为 他会自动的提供 toString() hashCode() equals() 方法 而且还提供了一个额外的copy()方法 以及用于解构的componentN(可以是1,2,3,4)()方法

  • 注意主构造器必须是要至少一个有意义的属性的 而且只有在主构造函数的属性才会重写到toString() hashCode() equals()方法中

  • 但是copy()是将对象的全部属性复制 但是是浅拷贝 不会深度复制(这个不是很理解)

    data class Gril(val name: String, val age: Int) {
        val boyFriend: Boy = Boy("Jack")
        val id: Int = 123
    }
    
    class Boy(val name: String)
    
    fun main() {
        val gril = Gril("Marry", 19)
        println(gril) // 输出 : Gril(name=Marry, age=19)
        val newGril = gril.copy(name = "Tom")
        println(newGril.boyFriend.name)//Jack
        println(newGril) // Gril(name=Tom, age=19)
    }
    
    • 按顺序解构

      val (name,age) = gril
          println("$name $age")
      
    • 注意结构的属性必须是在主构造器中的

  • 数据类的主要用途 :

    1. 给数据建模而不是行为
    2. 主构造器至少接受一个有意义的属性 数据类不允许使用无参的构造器
    3. 希望使用解构工具来从对象中提取数据
举报

相关推荐

0 条评论