Kotlin学习笔记之 5

作者: super_shanks | 来源:发表于2019-03-20 14:47 被阅读11次

5.Kotlin 类和对象

  • 构造器

    kotlin中一个类只能有一个主构造器和一个或多个次构造器。主构造器可以直接跟在class定义的类名后面但是没有方法体,如下:

    class Person constructor(s : String) {
    }
    //也可以写成这样,记得,没有空格
    class Person(s : String){
    }
    //一旦构造函数存在修饰符或者是注解的情况下,我们就不能省去constructor的方法名
    class Child public constructor(s : String){
    }
    

    以上的都是主构造函数,次构造函数定义在类中,并且kotlin强制规定,次构造函数必须要调用主构造函数,如下:

    class Person constructor(s : String) {
          constructor(i : Int) : this("123"){
          }
    }
    
    //也可以没有主构造器,如下的方式就是直接起的次构造器
    //只有这种情况下,次构造器不需要再调用主构造器,所以一般如下这种方式跟我们的java习惯比较像
    class Person{
         constructor(){
         }
    }
    
  • private,public,protected,internal

    前面三个大家比较熟悉了,在java中都有,但是internal是kotlin中才引入的,叫做模块内可见,即同一个module中可见。

    我们分别来看下,用这四个修饰符来描述属性所带来的编译区别。

    //kt
    private var a = "a"
    public var b = "b"
    protected var c = "c"
    internal var d = "d"
    
    
    //decompiled
    private String a;
     @NotNull
     private String b;
     @NotNull
     private String c;
     @NotNull
     private String d;
    
     @NotNull
     public final String getB() {
        return this.b;
     }
    
     public final void setB(@NotNull String var1) {
        Intrinsics.checkParameterIsNotNull(var1, "<set-?>");
        this.b = var1;
     }
    
     @NotNull
     protected final String getC() {
        return this.c;
     }
    
     protected final void setC(@NotNull String var1) {
        Intrinsics.checkParameterIsNotNull(var1, "<set-?>");
        this.c = var1;
     }
    
     @NotNull
     public final String getD$app_debug() {
        return this.d;
     }
    
     public final void setD$app_debug(@NotNull String var1) {
        Intrinsics.checkParameterIsNotNull(var1, "<set-?>");
        this.d = var1;
     }
    
    

    总结一下,就是不管是哪一个修饰符,最终经过编译之后生成的在java中的参数描述都是private的,修饰符真正造成的区别是在编译了自后的getset的方法不同。

    private的话,就不会生成getset方法,因为对于这个参数来说,是外部不可访问的。publicprotected就是相应的setget。而internal则是publicsetD$app_debuggetD$app_debug方法。我们可以认为这两个方法,在model中都是可以被访问的。

  • init关键字

    上面说到主构造器直接写在类名之后是没有方法体的,因此一旦我们想要在构造函数中做一些初始化的操作,就需要挪到init中实现了。

    class Person constructor(firstName: String) {
          init {
              println("FirstName is $firstName")
          }
    }
    
  • getter和setter

    跟java差的有点多,首先属性定义前面说过了,kotlin中getter和setter直接定义在属性下方,由于kotlin的本身属性的直接访问性,只要你创建的是public的属性,都可以直接获取到属性值即get方法和修改属性值即set方法。

    所以免去了为了fastjson而专门去写的settergetter,但是一旦你需要在gettersetter时做一些其他的操作,我们就需要去显示的写出getset

    var sex = "boy"
          get() {
              return "girl"
          }
          set(value) {
              when {
                  value.contains("girl") -> field = "boy"
              }
          }
    
    var age = 16
          get() = field + 10
          private set(value) = action(value)
          
    fun action(int: Int) {
    
    }      
          
    

    getset可以直接包含方法体,也可以直接通过等号的方式链到单行表达式或者方法。
    即我们认为,一旦触发取值和赋值的时候会做相应的操作。其中field就是指的他自己。

    >>注:field的重要性<<

    在kotlin中,我们定义了一个参数name然后,只要去调用他,比如parent.name或者说去对他进行赋值name = "Tony"最终都会被编译成使用了getset方法,如下

     //Observer.kt
     fun main(args : Array<String>){
          var observer = Observer()
          observer.name = "Tony"
          var newName = observer.name
      }
      
      //Observer.decompiled.java
      public static final void main(@NotNull String[] args) {
        Intrinsics.checkParameterIsNotNull(args, "args");
        Observer observer = new Observer();
        //调用了set
        observer.setName("Tony");
        //调用了get
        String newName = observer.getName();
     }
    

    所以我们在类中定义属性的settergetter的时候,如果直接操作属性本身,就会出现死循环。这就是field的用途

    //kt
    var no: Int = 100
      get() = no
      set(value) {
          if (value < 10) {       // 如果传入的值小于 10 返回该值
              no = value
          } else {
              no = -1         // 如果传入的值大于等于 10 返回 -1
          }
    }
    
    //decompiled
    int no = 100;
      public int getNo() {
          return getNo();// Kotlin中的get() = no语句中出来了变量no,直接被编译器理解成“调用getter方法”
      }
      
      public void setNo(int value) {
          if (value < 10) {
              setNo(value);// Kotlin中出现“no =”这样的字样,直接被编译器理解成“这里要调用setter方法”
          } else {
              setNo(-1);// 在setter方法中调用setter方法,这是不正确的
          }
      }
    

    很显然,造成了死循环。

  • lateinit关键字

    我们都知道kotlin中,在方法中定义属性时,我们必须进行初始化的操作。而类中本身我们一开始并不知道他到底是什么,所以会有希望晚一点再初始化的需求,这里就可以使用lateinit关键字来描述,那我们就可以不用给出具体的初始化值,但是kotlin会要求你必须给出属性的类型。

    lateinit var game : String
    

    那么这个game我们可以之后再对其赋值。

    从kotlin 1.2开始已经支持全局和局部变量都是用lateinit, 并且我们可以通过isInitialized来判断是否已经初始化过

  • 抽象类

    我们默认定义的class都是final的,无法被继承的。所以一旦需要这个class能够被继承,我们需要加上open关键字。如果这是个抽象类,那我们需要添加abstract关键字。一旦被abstract描述,就无需再加上open了。

    open class Person(){
    }
    
    abstract class Parent{
    }
    

    紧接着,另外几种场景

    • 方法是否可以被重写

      默认方法都是final的,如果需要让方法可以被重写,需要在方法前再加上open

      所有我们平时在java中写的一个单纯的类实际上转换成kotlin是如下这个样子的:

      open class Person constructor(s: String) {
              open fun getName(){}
      }
      

      同样的abstract也是这个意思,加载class前面只是形容类,跟方法和属性什么的一点关系都没有

    • 抽象属性

      这是一个比较新的东西,因为java不支持抽象属性。就是说,你要是继承我,你就必须要初始化我所要求初始化的属性。

      abstract class Parent(ame : String){
             abstract var ame : String
      }
      
      //两种集成方式,一种是直接在构造函数中对抽象属性进行复写
      //由于父类构造需要传一个字符串,所以在继承时也需要直接传入,此处传入的是Child1自己的构造参数s
      class Child1 constructor(s: String, override var ame: String) : Parent(s) {
      }
      //一种是在类中对属性进行复写
      class Child2  constructor(s: String) : Parent(s) {
             override lateinit var ame: String
       }
       
       //如果子类没有主构造函数,也可以通过次构造函数调用`super`方法实现
       class Child: Parent {
             constructor() : super("s")
             override lateinit var ame: String
         }
      
  • 嵌套类

    直接在class内部定义class,基本和java差不多

    class Outer {                  // 外部类
        private val bar: Int = 1
        class Nested {             // 嵌套类
            fun foo() = 2
        }
    }
    
    fun main(args: Array<String>) {
        val demo = Outer.Nested().foo() // 调用格式:外部类.嵌套类.嵌套类方法/属性
        println(demo)    // == 2
    }
    
  • 内部类

    在刚才嵌套类的基础上加上inner的关键字申明。

    class Outer {
         private val bar: Int = 1
         var v = "成员属性"
         /**嵌套内部类**/
         inner class Inner {
             fun foo() = bar  // 访问外部类成员
             fun innerTest() {
                 var o = this@Outer //获取外部类的成员变量
                 println("内部类可以引用外部类的成员,例如:" + o.v)
             }
         }
     }
    

    唯一的区别在于内部类持有了外部类的引用,可以通过@外部类名的方式,来访问外部类的成员变量。

  • 内部类和嵌套类的区别

    我们来看如下两个嵌套类和内部类的以及让门各自编译成class文件的例子

    //Out.kt
    class Out{
         class Inner{
     
         }
     }
     
     //Out.decompiled.java
     public final class Out {
        public static final class Inner {
        }
     }
     
     //Out.kt
    class Out{
         inner class Inner{
     
         }
     }
     
     
     //Out.decompiled.java
     public final class Out {
        public final class Inner {
        }
     }
    

    已经很明显了,inner之所以持有外部的引用,是因为他不是static的。也就是说kotlin的class默认就是static final的。

    调用嵌套类的方式与调用内部类的方式差别也只是一个括号而已

    fun main(args : Array<String>){
        //内部类调用
         Out().Inner().method()
         //嵌套类的调用
         Out1.Inner().method()
     }
    

    其实比较容易理解,嵌套类是static的直接可以通过类名来进行访问嵌套类。

  • 匿名内部类

    一般用在接口层面的很多,我们通常知道的是传参是个接口,方法中调用了接口方法的形式,如下:

    class Observer{
         fun getIt(listener: Listener){
             listener.onClick()
         }
     }
     
     interface Listener{
         fun onClick()
     }
     
     fun main(args : Array<String>){
         var observer = Observer()
         //注意,此处的object是kotlin独有的关键字
         //不是随便写写的,匿名内部类必须通过这个关键字来申明
         observer.getIt(object : Listener{
             override fun onClick() {
                 TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
             }
         })
     }
    

    通常我们也可以直接使用接口名 + lambda表达式的方式来生成匿名内部类,但条件是这个接口必须是函数式java接口,即只有一个抽象方法的java文件中定义的接口。

    比如我们基本碰到的所有的什么OnclickListener等等

    tv_case_id.setOnClickListener { View.OnClickListener{
    
     } }
    

    不过kotlin中定义的接口,我们就必须通过object的方式去实现了

    同时匿名内部类我们可以单独的拿出来进行定义,实际上我们可以把object :理解成一个匿名的内部类实现了一个接口,也就是说我们还可以实现多个接口,比如:

    open class A(x: Int) {
         public open val y: Int = x
     }
     
     interface B { …… }
     
     val ab: A = object : A(1), B {
         override val y = 15
     }
    

    通常我们在java中是无法做到匿名内部类实现多个接口的,因为我们只能new一个接口出来。

更甚者说,我们很多时候甚至不需要这个object去实现或者是继承什么,我们可以直接搞一个object出来

fun foo() {
     val adHoc = object {
         var x: Int = 0
         var y: Int = 0
     }
     print(adHoc.x + adHoc.y)
 }
  • 匿名对象最为函数的返回类型

    我们上面是将匿名对象赋值给了对象,我们还可以吧匿名对象直接赋值给方法,比如下面这个样子。

    fun publicFoo() = object {
        val x: String = "x"
    }
    

    这里涉及到公有还是私有的问题。

    匿名对象我们一般只能用在私有域和本地。白话的说就是一旦变成了公有,那就说谁都可以去调用,由于匿名对象只在生命的本地和私有域起作用,导致公有调用拿到的对象只能是匿名对象的超类(即父类,比如上面的object : Listener就是Listener,如果没有显式的定义超类就是Any)那么这样一来,就会导致匿名内部类中定义的属性是拿不到的,比如上面的x,因为上面的object并没有显式的定义超类,所以他返回的是Any,而Any是没有x属性的.

  • 匿名对象访问变量

    在java中匿名内部类想要访问相应的属性变量必须要final才行,但是在kotlin中,我们直接可以访问包含匿名对象作用域中的所有变量。

    fun countClicks(window: JComponent) {
        var clickCount = 0
        var enterCount = 0
    
        window.addMouseListener(object : MouseAdapter() {
            override fun mouseClicked(e: MouseEvent) {
                clickCount++
            }
    
            override fun mouseEntered(e: MouseEvent) {
                enterCount++
            }
        })
    }
    

相关文章

网友评论

    本文标题:Kotlin学习笔记之 5

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