Kotlin第三弹

1.抽象类与接口

abstract class Person(var age:Int){

    abstract fun talk()

    fun sayAge(){ println("$age") }
}

interface Sport{
    fun run(){
        println("run like this")
    }

    fun jump()
}

interface Study{
    var j : Int

    fun studyEnglish()
}

class Man(override var j:Int,var name:String,age: Int):Person(age),Sport,Study{
    override fun studyEnglish() {
        println("$name's age is $age,studyEnglish now,level is $j")
    }

    override fun talk() {
        println("$name's age is $age,talking now")
    }

    override fun run() {
        super<Sport>.run()
        println("$name's age is $age,runing now")
    }

    override fun jump() {
        println("$name's age is $age,jumping now")
    }

}

在kotlin中,抽象类用abstract修饰,可以有成员变量,可以有普通方法,用open修饰后也可以被继承,可以有抽象方法,抽象方法要用abstract修饰,但是不能有方法体。子类继承抽象类后,将继承抽象类的成员变量,普通方法,需要去实现抽象方法。
在kotlin中,接口使用interface关键字,接口中定义的方法都需要被重写,接口中定义的方法可以有默认实现,可以定义变量,但是不能赋值,因为接口是无状态的,要由继承的子类去赋值。
抽象类是描述对象本质,接口描述的是对象行为。跟接口相比,抽象类已经算是半成品。子类中被重写的方法要使用override关键字。

2.继承(实现)

kotlin中关于继承实现跟java中大同小异,下面介绍一个kotlin特有的叫接口代理。

interface MathLesson{
    fun teachMath()
}

interface EnglishLesson{
    fun techEnglish()
}

class Teacher:MathLesson,EnglishLesson{
    override fun teachMath() {
        println("teach math")
    }

    override fun techEnglish() {
        println("teach english")
    }

}

上面是正常使用接口的方式,简单的说就是一个老师不仅要教数学,还要教英语,感觉好累。然后就有了接口代理

class MathTeacher : MathLesson{
    override fun teachMath() {
        println("MathTeacher teach math")
    }
}

class EnglishTeacher :EnglishLesson{
    override fun techEnglish() {
        println("EnglishTeacher teach english")
    }
}

class TeacherLeader(var mt : MathTeacher,var et :EnglishTeacher):MathLesson by mt,EnglishLesson by et{

}

上面的例子分别有一个数学老师,一个英语老师,一个老师头。老师头一样需要教数学和英语两门课,于是他雇了两个专项的老师去完成教学任务。这就是接口代理。要使用by关键字,之后我们还会介绍属性代理。

3.类及其成员的可见性

kotlin中的可见性修饰符和java大同小异,一样有public,private,protected,这几个修饰符的使用范围跟java一摸一样。kotlin中有一个特有的internal修饰符,它的作用范围是同一个模块内,还有一点不同是kotlin中默认的修饰符是public。

4.object

在java中object大家再熟悉不过了,java中Object是一切类的基类。而在kotlin中object是一个关键字,用来修饰类。被它修饰的类一样可以继承父类,实现接口,拥有成员变量,区别于一般类的是,object类在虚拟机内存中有且只有一份,简单的说,就是我们俗称的“单例”,kotlin在语法层面支持了单例模式。

object Single: A(),B{

    var a: Int = 0

    override fun aa() {

    }

    override fun bb() {

    }

    fun cc(){

    }

}


abstract class A{
    abstract fun aa()
}

interface B{
    fun bb()
}

上面的例子就是普通的被object修饰的类,使用起来跟一般类没什么区别,那它到底是不是单例呢?我们来看下编译后的字节码反编译的java代码

public final class Single extends A implements B {
   private static int a;
   public static final Single INSTANCE;

   public final int getA() {
      return a;
   }

   public final void setA(int var1) {
      a = var1;
   }

   public void aa() {
   }

   public void bb() {
   }

   public final void cc() {
   }

   private Single() {
      INSTANCE = (Single)this;
   }

   static {
      new Single();
   }
}

果然,这不就是一个单例的“饿汉”写法嘛。

5.伴生对象与静态成员

在java中有静态方法的概念,俗称“类方法”,一般用于工具类的工具方法,可以直接通过类名调用。在kotlin中,虽然没有静态方法的概念,但是可以通过伴生对象来实现,例如下面这段kotlin代码,模仿java标准库中的Math类的伪代码。

class Math private constructor(){

    companion object {
        @JvmStatic
        fun <T> sin(t:T):T{
            return t
        }

        fun <T> cos(t:T):T{
            return t
        }
        
        @JvmField
        val TAG = "tag"
    }

}

这样在kotlin中也可以用调“类方法”的写法去使用了。JvmStatic标注的作用是在java代码中调用kotlin代码,可以用java熟悉的静态方法写法。
同样也有伴生对象属性,就是java中的静态成员,JvmField标注作用于属性。

class SingleMode private constructor(){
    companion object {

        private val instance : SingleMode = SingleMode()

        @JvmStatic
        fun SingleMode():SingleMode{
            return instance
        }
    }
}

伴生对象也可以使用在单例中,上面就是kotlin中“饿汉单例”。

6.overload

方法重载的概念在java中也有,在kotlin中也没什么区别。这里讲一个概念,叫做“方法签名”,jvm语言都有这个概念,"方法签名"只取决于方法名,参数类型,参数数量,参数顺序,跟返回值无关。
由于kotlin中默认参数和具名参数的用法,所以一般情况下,方法重载都可以被代替。比如

    fun a(p1:Int){}
    fun a(p1: Int,p2:Int){}
    fun a(p1: Int,p2: Int,p3:String){}

例子中a方法有三种方式的重载,然后我们用默认参数的方式改写成一个方法。

fun a(p1: Int,p2: Int=0,p3:String?=null){}

默认参数和具名参数可以让方法的定义变的很灵活。

7.方法扩展和属性扩展

在java的日常开发中,大家自己都会定义一些工具类,将功能抽象出来,这样不同的模块项目就可以复用这些功能代码了。在java中主要使用静态方法来实现。在kotlin中,可以有伴生对象方式,包函数方式来实现,推荐使用包函数。今天再介绍一种更牛逼的方式,叫做“方法扩展”。
比如,现在有这么个需求,判断字符串中是否含有数字,之前我们都会采用的方式是定义一个StringUtils类,把这个需求设计成这个类的类方法,但是我们用方法扩展的方式,给String类加个方法呢?

fun String.hasNumber():Boolean{
    return this.filter { it.toInt() !in 48..57}.length != this.length
}

    val s = "anc123"
    println(s.hasNumber())
    val s2 = "adsf"
    println(s2.hasNumber())

神奇的发现,我们给系统api类增加了一个方法。当然有方法扩展,就会有属性扩展,不过感觉属性扩展暂时没找到特别好的用处。

val String.version : Int
    get() { return 1 }

8.属性代理

之前我们介绍过接口代理,现在我们来看下属性代理。之前我们写的属性懒加载就是一种属性代理。

val array by lazy { intArrayOf(1,2,3,4,5,6) }

类似这样的代理,当这个属性第一次被使用时,就会调用大括弧里面的lambda表达式,并返回结果。那我们自己要写个属性代理该怎么写呢?通过查看lazy源码发现有这个方法

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

这就是lazy代理属性的关键,既然知道原因了,我们自己试试。

class X{
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String{
        return "from X"
    }
}

我们定义了X类,里面定义了一个getValue方法,然后我们试着用X类去代理下属性看看

val x by X()

没有报错,使用打印就是“from X”,i get it!然后我试着用X去代理var变量时,报错了...查看警告发现缺少setValue方法,既然缺少什么,我们就补什么。

class Y{

    private var value :String? = null

    operator fun getValue(thisRef: Any?, property: KProperty<*>): String{
        println("$thisRef-$property")
        return value?:""
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>,value : String){
        println("$thisRef-$property-$value")
        this.value = value
    }
}

Y类不仅能代理val常量,也能代理var变量。代理其实就是“让狗叫出猫叫”,经典案例,大家懂的!

9.数据类

数据类又称为data class,它很某种程度上可以替代java中的javabean,但是这替代的过程并不是一帆风顺的,最后我们来讲下有哪些坑,先看用法,用法非常简单,使用data class声明一个类,例如:

data class User(var id:Int,var name:String)

虽然只有一行代码,但是编译器自动为我们生成了toString,copy,hashcode等方法,大大简化了我们书写模式代码的工作量,我们通过反编译kotlin字节码来看看具体情况。

public final class User {
   private int id;
   @NotNull
   private String name;

   public final int getId() {
      return this.id;
   }

   public final void setId(int var1) {
      this.id = var1;
   }

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

   public final void setName(@NotNull String var1) {
      Intrinsics.checkParameterIsNotNull(var1, "<set-?>");
      this.name = var1;
   }

   public User(int id, @NotNull String name) {
      Intrinsics.checkParameterIsNotNull(name, "name");
      super();
      this.id = id;
      this.name = name;
   }

   public final int component1() {
      return this.id;
   }

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

   @NotNull
   public final User copy(int id, @NotNull String name) {
      Intrinsics.checkParameterIsNotNull(name, "name");
      return new User(id, name);
   }

   // $FF: synthetic method
   // $FF: bridge method
   @NotNull
   public static User copy$default(User var0, int var1, String var2, int var3, Object var4) {
      if((var3 & 1) != 0) {
         var1 = var0.id;
      }

      if((var3 & 2) != 0) {
         var2 = var0.name;
      }

      return var0.copy(var1, var2);
   }

   public String toString() {
      return "User(id=" + this.id + ", name=" + this.name + ")";
   }

   public int hashCode() {
      return this.id * 31 + (this.name != null?this.name.hashCode():0);
   }

   public boolean equals(Object var1) {
      if(this != var1) {
         if(var1 instanceof User) {
            User var2 = (User)var1;
            if(this.id == var2.id && Intrinsics.areEqual(this.name, var2.name)) {
               return true;
            }
         }

         return false;
      } else {
         return true;
      }
   }
}

这就是我们反编译成java代码后的结果,kotlin的编译器真是为我们操碎了心啊。但是有两个方法感觉设计重复了getId和component1,getName和component2,这是为什么?我们先来看下下面这种语法:

var u = User(1,"u1")
var (id,name) = u

其实就是个操作符重载,因为这种语法设计,所以才有了component1和component2方法。那么我们自己设计的类是不是也能支持这种语法呢?答案是肯定的。

class Person(var id:Int,var name: String,var age:Int){
   operator fun component1():String{
        return this.name
    }
    operator fun component2():Int{
        return this.age
    }
}

var p = Person(0,"22",10)
var (name,age) = p

我们自己定义的类也可以使用这个操作符。

最后我们来说说,data class的坑,查看反编译后的java文件,你会发现User类是final的,而且没有无参的构造方法,在使用一些第三方数据层框架时,框架会通过无参的构造方法反射生成实例。而且javabean中的继承关系也能常见。显然这两点让我们用data class取代javabean近在咫尺,却又远在天边。这里介绍两个kotlin插件:

classpath "org.jetbrains.kotlin:kotlin-noarg:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-allopen:$kotlin_version"

至于这两款插件的用法,这里就不做展开了。

10.内部类

kotlin和java一样有内部类,也分为静态内部类,非静态内部类,匿名内部类,含义上也基本是一样的。我这边就简单介绍下几种内部类的写法:

class Outer{
    class Inner{

    }
}

这是kotlin种静态内部类,kotlin中默认就是静态内部类,为什么这么说?我们来看下他的用法

var inner = Outer.Inner()

这种用法是不是跟java中的静态内部类一样!

class Outer{
     inner class Inner{

    }
}

这就是kotlin中的非静态内部类,就比上个例子多了一个inner关键字,用法上是这样的

 var inner = Outer().Inner()

这种写法跟java中的非静态内部类也是一致的

interface OnClickListener{
    fun onClick()
}

var listener = object : OnClickListener{
        override fun onClick() {
            
        }

这就是kotlin的匿名内部类,有点区别的是,kotlin中的匿名内部类可以同时实现或者继承多个接口和抽象类。

11.枚举

kotlin中的枚举和java中的用法也大同小异,枚举就是实例个数已知的对象类型。

enum class Level{
    LOW,MID,HIGH
}

那么枚举类可以有构造方法吗?Ofcourse yes!

enum class Level(val id :Int){
    LOW(0),MID(1),HIGH(2)
}

我们自己也可以实现枚举类的设计,这里就不展开了,归根到底,他还是类嘛

12.密封类

密封类是kotlin中的全新的概念(sealed class),那什么是密封类呢?密封类是子类数量已知的一种设计,他可以防止外部程序继承串改。

sealed class Cmd{

    fun invoke(){}

    class ACmd(var id: Int):Cmd()

    class BCmd(var name: String):Cmd()

    object CCmd:Cmd()
}

这就是密封类,这样Cmd有且只有3个子类,外部不可能继承Cmd类了,个人觉得在第三方库的设计中可以使用密封类,保证核心代码的安全设计。
理解密封类,可以和理解枚举结合起来,枚举是实例数量已知,密封类是子类数量已知

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

推荐阅读更多精彩内容