Kotlin学习笔记之 5 类和对象

首发于公众号: DSGtalk1989

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学习笔记之 1 基础语法

Kotlin学习笔记之 2 基本数据类型

Kotlin学习笔记之 3 条件控制

Kotlin学习笔记之 4 循环控制

Kotlin学习笔记之 5 类和对象

Kotlin学习笔记之 6 继承

Kotlin学习笔记之 7 接口

Kotlin学习笔记之 8 扩展

Kotlin学习笔记之 9 数据类与密封类

Kotlin学习笔记之 10 泛型

Kotlin学习笔记之 11 枚举类

Kotlin学习笔记之 12 对象表达式和对象声明

Kotlin学习笔记之 13 基础操作符run、with、let、also、apply

Kotlin学习笔记之 14 包与导入

Kotlin学习笔记之 15 伴生对象

Kotlin学习笔记之 16 委托

Kotlin学习笔记之 17 可观察属性

Kotlin学习笔记之 18 函数

Kotlin学习笔记之 19 高阶函数与 lambda 表达式

Kotlin学习笔记之 20 内联函数

Kotlin学习笔记之 21 解构声明

Kotlin学习笔记之 22 集合

Kotlin学习笔记之 23 相等判断

Kotlin学习笔记之 24 操作符重载

Kotlin学习笔记之 25 异常捕捉

Kotlin学习笔记之 26 反射

Kotlin学习笔记之 27 类型别名

Kotlin学习笔记之 28 协程基础

Kotlin学习笔记之 29 上下文与调度器

Kotlin学习笔记之 30 协程取消与超时

Kotlin学习笔记之 31 协程挂起函数的组合

Kotlin学习笔记之 32 协程异常处理

Kotlin学习笔记之 33 协程 & Retrofit

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342