全网最全面的由浅到深的Kotlin基础教程(五)

前言

本篇文章接着上一篇文章全网最全面的由浅到深的Kotlin基础教程(四)继续进阶学习kotlin,建议看完上一篇文章,再来看本篇文章。前面4篇文章都是kotlin的基础知识,本篇文章开始进入深水区,主要讲解kotlin语言中类的继承,数据类,枚举类,接口类,抽象类,密封类,泛型,运算符重载,out协变,in逆变等等。

1. 继承与重载的open关键字

kotlin需要使用open关键字使得类可以被继承,使得类中的方法可以被重写。示例代码如下:

open class Person(val name:String){
    //默认都是final方法,无法继承,需要用open关键字去除final
    open fun show() = println("人类name=$name")
}

class Student(name: String):Person(name){
    override fun show() {
        println("学生name=$name")
    }
}
fun main() {
    var person:Person = Student("MEKEATER")
    person.show()
}

运行结果如下:
在这里插入图片描述

2. 类型转换 is + as 配合类型转换

is用户判断是否为指定类型,as用于类型转换,示例代码如下:

fun main() {
    //Student类继承自Person
    var p: Person = Student("mekeater")
    if (p is Student) {
        (p as Student).show()
    }
}

4. Any超类学习

kotlin任何类默认都继承了Any类

class Test04:Any() //任何类默认都继承了Any类
fun main() {
    println(Test04().toString())
}

5. 对象声明:实现单例实例

kotlin通过object关键字生命的类,就是实现一个单例对象,可以反编译查看java代码的实现。通过这种方式实现java的单例模式,示例代码如下:

//object 就是实现一个单例对象。可以反编译查看java代码的实现。
object Test5{
    //因为是单例,主构造函数用不到了,所以,init实际写在静态代码块了,可以反编译查看
    init {
        println("Test5 init")
    }

    fun show() = println("show函数")
}
fun main() {
    //都是同一个实例
    println(Test5)
    println(Test5)
}

运行结果如下:
在这里插入图片描述

6. object :对象表达式

kotlin通过object : 对象表达式匿名实现接口或者继承类,示例代码如下:

open class Test06{
    open fun show(info:String) = println("[$info]")
}
fun main() {
    //匿名对象 表达式,即匿名实现类对象
    var test06 = object : Test06() {
        override fun show(info: String) {
            println("匿名对象实现【$info】")
        }
    }
    test06.show("mekeater")

    //对接口,用对象表达式(匿名对象)实现
    var run = object : Runnable {
        override fun run() {
            println("通过对象表达式,匿名实现接口")
        }
    }
    run.run()
}

7. 伴生对象companion object

kotlin通过companion object关键字实现静态变量和静态方法,示例代码如下:

class Test07{
    companion object{
        val info = "mekeater"
        fun show() = println("info=$info")
    }
}
fun main() {
    Test07.show()
}

8. 嵌套类与内部类

在类中再定义一个类,如果不加inner关键字,则为嵌套类,否则为内部类,内部类可以访问外层类的变量,嵌套类不可以。示例代码如下:

class Test08{
    val info = "mekeater"
    //不加inner默认是嵌套类,必须加inner使得嵌套的类成为内部类,而内部类才能访问外部类的成员变量
    inner class Test1{
        fun show() = println("info=$info")
    }
}
fun main() {
    Test08().Test1().show()
}

9. 数据类

kotlin通过data关键字声明的类称为数据类,数据类会自动生成toString hashCode equals 解构操作,这点可以通过反编译为java代码证明。数据类适用于类似javaBean的场景,示例代码如下:

//可以通过反编译为java代码比较普通类和数据类的区别。
//普通类只有get set和构造函数,而数据类相对于普通类,会再自动生成toString hashCode equals 解构操作
class Test09(var name:String)
data class Test091(var name: String)
fun main() {
    println(Test09("mekeater"))
    println(Test091("mekeater")) //因为数据类自动重写了toString,所以,输出结果与普通类不一样
}

运行结果如下:
在这里插入图片描述
上面代码反编译为java的部分代码如下,可以明显看出加了data关键字的Test091类,自动生成了component1解构函数,copy函数,toString函数,hashCode函数,equals函数,但是,可以看出这些操作只是处理主构造的参数,所以,次构造的内容会丢失,这点需要注意!

public final class Test09 {
   @NotNull
   private String name;

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

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

   public Test09(@NotNull String name) {
      Intrinsics.checkNotNullParameter(name, "name");
      super();
      this.name = name;
   }
}

public final class Test091 {
   @NotNull
   private String name;

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

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

   public Test091(@NotNull String name) {
      Intrinsics.checkNotNullParameter(name, "name");
      super();
      this.name = name;
   }

   @NotNull
   public final String component1() {
      return this.name;
   }
   
   @NotNull
   public final Test091 copy(@NotNull String name) {
      Intrinsics.checkNotNullParameter(name, "name");
      return new Test091(name);
   }

   // $FF: synthetic method
   public static Test091 copy$default(Test091 var0, String var1, int var2, Object var3) {
      if ((var2 & 1) != 0) {
         var1 = var0.name;
      }

      return var0.copy(var1);
   }

   @NotNull
   public String toString() {
      return "Test091(name=" + this.name + ")";
   }

   public int hashCode() {
      String var10000 = this.name;
      return var10000 != null ? var10000.hashCode() : 0;
   }

   public boolean equals(@Nullable Object var1) {
      if (this != var1) {
         if (var1 instanceof Test091) {
            Test091 var2 = (Test091)var1;
            if (Intrinsics.areEqual(this.name, var2.name)) {
               return true;
            }
         }

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

10. 类实现解构声明

kotlin通过component加数字实现为类添加解构功能,至于解构是什么东西,直接看如下代码示例,相信一看就懂了。

//普通类,声明解构函数,使其具备解构能力,但一般没必要,需要解构,就定义数据类就行了
class Test11(var name:String, var age:Int){
    //函数名必须为component1 component2 ...
    operator fun component1() = name
    operator fun component2() = age
}

//数据类,上面也提到过,默认已经实现了解构
data class Test11_1(var name:String, var age:Int)

fun main() {
    var test11 = Test11("mekeater", 18)
    val (name,age) = test11
    println("name=$name,age=$age")
    // 下划线代表不接收某个解构数据,可以看反编译java源码,理解实现方式
    val (name1, _) = Test11_1("sun", 18)
    println("name=$name1")
}

代码运行结果如下:
在这里插入图片描述

11. 运算符重载

kotlin通过operator关键字及指定的函数名称实现重载运算符,如实现+运算符重载示例代码如下:

data class Add(var num1:Int, var num2:Int){
    //实现 + 运算符重载
    operator fun plus(p1:Add): Int {
        return (num1+p1.num1) + (num2+p1.num2)
    }
}
fun main() {
    println(Add(1,2) + Add(3,4))
}

运行结果如下:
在这里插入图片描述
其它一些运算符名称如下:
在这里插入图片描述
在这里插入图片描述

12. 枚举类

12.1 枚举类定义

通过enum关键字声明的类为枚举类,示例代码如下:

enum class Week{
    星期一,
    星期二
}
fun main() {
    println(Week.星期一)
    println(Week.星期二 is Week)
}

运行结果如下:
在这里插入图片描述

12.2 定义主构造函数带参数的枚举类

枚举类和普通类一样,也有主构造函数,定义主构造函数带参数的枚举类,示例代码如下:

//一般枚举类的参数都要私有化,因为参数值是在内部枚举值中传入的,不需要外部传入
enum class Info(private var status:String){
    START("开始"), //因为枚举类主构造函数带参数,因此枚举值需要传入值
    STOP("停止"); //枚举后面如果有函数,需要有结束符
    fun show() = println("status = $status")
    fun update(s:String){
        this.status = s
    }
}
fun main() {
    Info.START.show()
    Info.STOP.show()

    Info.START.update("启动")
    Info.START.show()
}

运行结果如下:
在这里插入图片描述

13. 代数数据类型

代数数据类型,其实就是把枚举类和when结合使用的场景,示例代码如下:

enum class Score{
    A,
    B,
    C,
    D
}
class Teacher(private val score:Score){
    fun show() = when(score){
        Score.A -> "成绩为A"
        Score.B -> "成绩为B"
        Score.C -> "成绩为C"
        Score.D -> "成绩为D"
    }
}
fun main() {
    println(Teacher(Score.A).show())
}

运行结果如下:
在这里插入图片描述

14. 密封类

密封类就是把一堆类收集在一个由sealed声明的类中,示例代码如下:

sealed class Person1 {
    object Bird:Person1()
    class Student(var name:String):Person1()
}

class Test16(private var person1:Person1){
    fun show() = when(person1){
        is Person1.Student -> "这是一个学生,学生姓名:${(person1 as Person1.Student).name}"
        is Person1.Bird -> "这是一只小鸟"
    }
}
fun main() {
    println(Test16(Person1.Student("mekeater")).show())
    println(Test16(Person1.Bird).show())
}

运行结果如下:
在这里插入图片描述

15. 接口类

kotlin定义接口和java类似,通过interface关键字定义接口,其中接口类中声明的成员变量需要实现类通过构造函数,或者重写成员变量实现,示例代码如下:

interface IUSB{
    //成员变量需要实现类通过构造函数,或者重写成员变量实现
    var usbVersion:String
    var usbInfo:String
    fun show():String
}

//方式一、通过构造函数实现接口中的成员变量
class Mouse(override var usbVersion: String, override var usbInfo: String) :IUSB{
    override fun show(): String {
        return "Mouse usbVersion=$usbVersion,usbInfo=$usbInfo"
    }
}

//方式二、通过重新接口类的成员变量实现
class Keyword():IUSB{
    override var usbVersion: String = ""
    override var usbInfo: String = ""
    override fun show(): String {
        return "Keyword usbVersion=$usbVersion,usbInfo=$usbInfo"
    }
}

fun main() {
    var usb:IUSB = Mouse("3.0", "usb3.0版本")
    println(usb.show())
    usb = Keyword()
    usb.usbVersion="3.1"
    usb.usbInfo="usb3.1"
    println(usb.show())
}

运行结果如下:
在这里插入图片描述

16. 抽象类

kotlin通过abstract关键字定义抽象类,这个和java类似,没啥好说的,直接看示例代码:

abstract class BaseActivity{
    fun onCreate(){
        setContentView(getLayoutID())
        initView()
    }

    private fun setContentView(layoutID:Int) = println("layoutID=$layoutID")

    abstract fun getLayoutID():Int
    abstract fun initView()
}

class MainActivity: BaseActivity() {
    override fun getLayoutID(): Int = 666

    override fun initView() = println("initView finish")
}

fun main() {
    MainActivity().onCreate()
}

运行结果如下:
在这里插入图片描述

17. 泛型学习

17.1 定义泛型类

kotlin定义泛型类与java类似,直接看示例代码:

class Kt19<T>(private val obj:T){
    fun show() = println("obj=$obj")
}
fun main() {
    Kt19<Int>(18).show()
    Kt19("hello").show() //kotlin具有类型推断能力,因此可以省略指定类型
}

运行结果如下:
在这里插入图片描述

17.2 泛型函数

kotlin定义泛型函数与java类似,直接看示例代码:

fun<T> show(info:T){
    info?.also {
        println("info=$it")
    } ?: println("info=null")
}

fun main() {
    show(10)
    show(10.66)
    show("good")
    show(null)
}

运行结果如下:
在这里插入图片描述

17.3 泛型变换

输入一种泛型类型,然后通过lambda表达式实现输出为另一种泛型类型,这种泛型转换写法在kotlin代码中很常见,示例代码如下:

inline fun <I, O> map(input: I, action: (I) -> O) =
        action(input)

fun main() {
    var map = map(123) {
        "[$it]"
    }
    println(map)
}

运行结果如下:
在这里插入图片描述

17.4 泛型类型约束

kotlin泛型类型约束相当于java中T extends ,示例代码如下:

open class Animal()
open class Dog():Animal()
//T:Dog,T必须是Dog或者继承自Dog,相当于java中T extends Dog
class Test22<T:Dog>(private val input:T, private val isT:Boolean = true){
    fun getInput() = input.takeIf { isT }
}

18. vararg关键字(动态参数)

通过vararg关键字实现动态参数,示例代码如下:

class Kt23<T>(vararg var ages: T){
    fun show(index:Int) = println("age=${ages[index]}")

    //运算符重载
    operator fun get(index:Int) = ages[index]
}
fun main() {
    val p1 = Kt23(16,17.7,"sun")
    p1.show(2)
    println(p1[0])
}

运行结果如下:
在这里插入图片描述

19. out-协变和in-逆变

  • 协变 out T具有以下特点
  1. 泛型只能读取,不能修改
  2. 子类泛型可以直接赋值给父类泛型,类似java ? extend T
  • 逆变 in T具有以下特点
  1. 泛型只能修改,不能读取
  2. 父类泛型可以直接赋值给子类泛型,类似java ? super T。父类去转子类,这应该是逆向过程,所以逆变的名称因此而来。

示例代码如下:

//协变
interface Product<out T>{
    //修改操作,无法通过编译
    //fun f1(item:T)
   fun product():T
}
//逆变
interface Consumer<in T>{
    fun consumer(item:T)
    //读取操作,无法通过编译
    //fun f2():T
}

open class Animal1

open class Dog1: Animal1()

class Product0:Product<Animal1>{
    override fun product(): Animal1 {
        println("生产 Animal1")
        return Animal1()
    }
}

class Product1:Product<Dog1>{
    override fun product(): Dog1 {
        println("生产 Dog1")
        return Dog1()
    }
}

class Consumer0:Consumer<Animal1>{
    override fun consumer(item: Animal1) {
        println("消费 Animal1")
    }
}

class Consumer1:Consumer<Dog1>{
    override fun consumer(item: Dog1) {
        println("消费 Dog1")
    }
}

fun main() {
    val p1:Animal1 = Product0().product()
    
    // 子类Dog1可以赋值给父类Animal1,正常子类是不能赋值给父类的,
    // 此处之所以可以,是因为Product中的泛型是采用协变,类似java的 ? extend Animal
    val p2:Animal1 = Product1().product()
    
    //父类泛型Animal1直接赋值给了子类泛型Dog1,原因是in起的作用,类似java中 ? super Dog1
    val p1:Consumer<Dog1> = Consumer0()
    val p2:Consumer<Dog1> = Consumer1()
}

20. reified关键字学习

reified修饰的泛型,才具有泛型类型判断的能力,即 obj is T,示例代码如下:

data class ObjClass1(val name:String)
data class ObjClass2(val name:String)
class Kt27{
    //只有用reified修饰泛型,才有泛型类型判断的能力 it is T。同时只有inline修饰的函数,才能使用reified修饰
    inline fun<reified T> getObj(defaultLambdaAction:()->T):T{
        var listOf = listOf(ObjClass1("obj1"), ObjClass2("obj2"))
        var obj = listOf.shuffled().first()
        println("随机对象:$obj")
        //T? 代表可为null,不然会有null as T转换异常
        return obj.takeIf { it is T} as T? ?: defaultLambdaAction()
    }
}
fun main() {
    Kt27().getObj<ObjClass1> {
        var result = ObjClass1("备用对象")
        println("启用备用对象:$result")
        result
    }
}

示例运行结果:
在这里插入图片描述

  • 12
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Mekeater

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值