kotlin语法快速入门--(完整版)

Kotlin语法入门

文章目录

  • Kotlin语法入门
    • 一、变量声明
      • 1、整型
      • 2、字符型
      • 3、集合
        • 3.1、创建array数组
        • 3.2、创建list集合
        • 3.3、不可变类型数组
        • 3.4、Set集合--不重复添加元素
        • 3.5、键值对集合Map
      • 4、kotlin特有的数据类型和集合
        • 4.1、Any、Nothing
        • 4.2、二元组--Pair
        • 4.3、三元组--Triple
      • 5、const关键字
    • 二、条件控制和循环语句
      • 1、if...else
      • 2、when
        • 2.1、常规用法
        • 2.2、特殊用法--并列:
        • 2.3、特殊用法--类型判断:
        • 2.4、特殊用法--区间:
        • 2.5、返回值
      • 3、循环
        • 3.1、for..in
        • 3.2、forEach
        • 3.3、迭代器
        • 3.4、【扩展】for..in中的withIndex方法
        • 3.5、【扩展】forEachIndexed()方法
        • 3.6、do...while和while
      • 4、return,break,continue结束语句的使用
        • 4.1、结束循环分支
        • 4.2、标签备注代码块--@
        • 4.3、forEach退出循环的几种方式
    • 三、区间
      • 1、语法
      • 2、遍历
      • 3、查找是否在区间内
      • 4、字符区间
    • 四、函数
      • 1、函数定义
      • 2、infix关键字
      • 3、参数省略
      • 4、函数类型参数
      • 5、多参数--vararg
    • 五、访问和属性修饰符
      • 1、kotlin修饰符
      • 2、internal
      • 3、默认修饰符
      • 4、open关键字开启继承并实现
    • 六、类与对象
      • 1、声明和调用
      • 2、get和set
      • 3、init函数初始化
      • 4、constructor构造函数
        • 4.1、主构造函数
        • 4.2、二级构造函数
        • 4.3、多个构造函数
        • 4.4、省略主构造函数并写了次构造函数
      • 5、类的继承与重写
        • 5.1、继承
        • 5.2、继承构造函数初始化
        • 5.3、函数的重写
        • 5.4、属性的重写
      • 6、抽象类、嵌套类和内部类
        • 6.1、抽象类
        • 6.2、嵌套类
        • 6.3、内部类
    • 七、自定义注解
    • 八、接口与接口实现
      • 1、接口定义与实现
      • 2、接口成员变量
        • 2.1、单个接口函数复写
        • 2.2、多个接口同名函数复写
        • 2.2、接口成员存在变量和常量
    • 九、数据类、伴生类、枚举类
      • 1、数据类
      • 2、伴生类
        • 2.1、定义伴生类
        • 2.2、@JvmStatic注解
        • 2.3、const关键字
      • 3、枚举类
        • 3.1、定义
        • 3.2、传参
        • 3.3、继承与实现
    • 十、单例和对象表达式
      • 1、单例模式的创建
      • 2、对象表达式
    • 十一、密封类和密封接口
      • 1、密封类
      • 2、密封接口

一、变量声明

1、整型

fun main() {
    var b: Int = 1
  //var 变量名:变量类型=值
    val c=100
  //var 常量名:变量类型=值
}

kotlin中的变量是自动推导的,其中变量类型Int是可以省略的。

image-20240416181830036

val常量:image-20240416181934611

2、字符型

定义就与上面一样,调用的方法也与java差不多,equals等。

占位符:

fun main() {
    var string: String = "hello world !"
    println("hello ${string}") //输出
}

输出:image-20240416182642981

多模版输出:

fun main() {
    var string: String ="""
        1 2 3 4 5 6 7 8 9 10
    """.trimIndent() //此方法可以忽略缩进
    println(string)
}

3、集合

前景提要:除了数组都分为可变集合和不可变集合,区别就是在前面加上前缀mutable,这是kotlin语法中特有的。

关键字如下:

  • array==>arrayof
  • list==>listof
  • 可变list==>mutableListOf
3.1、创建array数组
fun main() {
    var array: Array<Int> = arrayOf<Int>( 1,2,3)
    var get = array.get(0)
    println(get)
}

以上是常规的创建方法,也可以用自定义的方法,下面以Int为例,其实基本数据类型都是有的。

fun main() {
    var array: IntArray = intArrayOf(1, 2, 3) //int数组
    var array1: Array<Int> = emptyArray<Int>()  // 空数组
}
3.2、创建list集合
fun main() {
    var listOf = listOf<Int>( 1, 2, 3)
    println(listOf[0]) //获取第1个数据
}

当然,Int前面或者后面写一个就行。上述集合是可变的

3.3、不可变类型数组
fun main() {
    var array: IntArray = intArrayOf(1, 2, 3) 
    var mutableListOf = mutableListOf(1, 2, 3)
    array.add()  //不可变,没有add()
    mutableListOf.add(6)  //可变,有add()
}

image-20240416184257482

3.4、Set集合–不重复添加元素

不可变集合的方式:

fun main() {
    setOf(1, 2, 3, 3, 3, 3).forEach{ //遍历集合
        println(it)
    }
}

image-20240416184742799

可变集合的方式:

fun main() {
    var mutableSetOf = mutableSetOf(1, 2, 3, 4, 5)
}
3.5、键值对集合Map

中间用to来实现键值对的配对

fun main() {
    var mutableMapOf = mutableMapOf("1" to 1, "11" to 11) //多个参数逗号隔开
    mutableMapOf.put("2", 2)
    mutableMapOf.put("3", 3)
    println(mutableMapOf["1"]) //获取键为1的数据
    println(mutableMapOf.keys) //keys是获取所有的key的set集合
    println(mutableMapOf.keys.size) //数据长度
}

image-20240416190358479

4、kotlin特有的数据类型和集合

4.1、Any、Nothing

Any不再介绍,类似于Java中的Object。

Nothing:没有什么是没有实例的。您可以使用Nothing来表示“一个永远不存在的值”:例如,如果函数的返回类型为Nothing,则意味着它永远不会返回(总是抛出异常)。

可以看kotlin中的null就是这样的一个数据类型:

image-20240416192010836

kotlin中最具有特色的是:不会有空指针存在

例如:Null不能是非Null类型Int的值法一:image-20240416193148945

法一:

官方推荐写法:image-20240416193258963

这样后续在调用b的时候就进行了为空的判定,例如:image-20240416193600947

解释:b在拼接3的时候,?就会进行判定b是否为空,如果b为空那就不会调用plus方法,这样就避免了空指针异常。

(上述输出为null,即调用b.toString()方法输出。)

法二:

还有一点就是b在一定不为空的情况下,则可以使用!!强制赋值,例如:image-20240416194204311

并且后续使用b调用方法都不用再使用?判定b值是否为空。

法三:

使用lateinit关键字(只能对复杂类型初始化)进行初始化延后,但是不推荐这种写法,类似于欺骗编译器我等一会初始化,例如:image-20240416194813610

4.2、二元组–Pair

与下面的Triple一样都是属于kotlin中特有的数据集合。

fun main() {
    var pair = Pair(1, 2)
    println("第一个值:${pair.first},第二个值:${pair.second}")
}
4.3、三元组–Triple
fun main() {
    Triple(1, 2, 3).let {
        println(it.first)
        println(it.second)
        println(it.third)
    }
}

5、const关键字

在Kotlin中,const关键字只能用于以下情况:

  1. 伴生对象中的属性:const关键字可以用于伴生对象中的属性,将其声明为常量。
  2. 顶层属性:const关键字可以用于顶层属性,将其声明为常量。
  3. 原生数据类型和String类型:const关键字只能用于基本数据类型(如Int、Long、Double等)和String类型。

需要注意的是,const关键字所修饰的属性必须在编译时就能确定其值,不能是运行时才能确定的值。

另外,const关键字只能用于编译期常量,而不能用于运行时常量。因此,在使用const关键字时需要遵守以上规则

二、条件控制和循环语句

1、if…else

常规分支:

fun main() {
    var a = 1
    if (a == 1) {
        println("a is 1")
    } else {
        println("a is not 1")
    }
}

kotlin新特性:if语句可以有返回值

fun main() {
    var a = 1
    val b = if (a == 1) {
        3
    } else {
        4
    }
    println(b)
}

像上述这种,假如条件成立,则赋值给a,返回值不用return 即可返回。

但是,注意的是假如要有返回值,则必须要写else分支。

image-20240417153651819

多分支已是如此:

fun main() {
    var a = 1
    val b = if (a == 1) {
        3
    } else if(a==3)  {
        4
    }else{
        5
    }
    println(b)
}

由于kotlin没有三元表达式,所以,可以使用if及其分支拟造一个三元表达式:

fun main() {
 var a = 1
 var b = 2
 val c = if (a == b) true else false
 println(c)
}

2、when

2.1、常规用法

关于when的用法,可以对标Java中的Switch,示例代码如下:

fun main() {
    var a = 1
    when (a) {
        1 -> println("a is 1")
        2 -> println("a is 2")
        else -> println("a is null")
    }

}

其中,else是默认输出语句,对标Java中的default

2.2、特殊用法–并列:
fun main() {
    var a = 1
    when (a) {
        1,3 -> println("a is 1")  //1或3
        2 -> println("a is 2")
        else -> println("a is null")
    }
}
2.3、特殊用法–类型判断:
fun main() {
    var a: Any = 1
    when (a) {
        is Int -> println("a is Int")
        is String -> println("a is String")
        else -> println("a is else")
    }
}
2.4、特殊用法–区间:
fun main() {
    var a: Any = 1
    when (a) {
        in 1..10 -> println("a is in range")
        is String -> println("a is a String")
        else -> println("none of the above")
    }
}

值得注意的是,每当判断到成立条件的时候,下面的条件不管成立否都不会执行。

还有,多个条件可以并列写,例如:

fun main() {
 var a: Any = 11
 when (a) {
     in 1..10,is Int -> println("a is in range or is Int")
     else -> println("none of the above")
 }
}
2.5、返回值

与if一样,kotlin的when也可以带上返回值,将返回值写在执行的最后一样即可:

fun main() {
    var a: Any = 11
    val b = when (a) {
        in 1..10 -> {
            println("a is in range")
            1
        }
        is Int -> {
            println("a is a Int")
            2
        }
        else -> {
            println("none of the above")
            3
        }
    }
    println(b)
}

3、循环

3.1、for…in
fun main() {
    var list = (1..<20).toList()
    for (i in list) {
        println(i)
    }
}
3.2、forEach

下述的forEach其实就是对原有的方法进行扩展,

fun main() {
    var list = (1..<20).toList()
    list.forEach {
        println(it)
    }
}

其循环默认名字是it,也可以用自定义的名字,例如下面value->:

fun main() {
    var list = ('a'..<'z').toList()
    list.forEach {value->
         println(value)
    }
}

其实其底层也就是一个lamda表达式进行赋值,然后进行for循环:

image-20240417164415857

3.3、迭代器

先看迭代器的方法:

image-20240417162941465

next():获取下一个元素的值,就像有一个指针,指向下一个值,并获取。

hasnext():判断是否存在下一个值,并且返回boolean值。

fun main() {
    var list = (1..<20).toList()
    var iterator = list.listIterator()
    while (iterator.hasNext()) {
        println(iterator.next())
    }
}
3.4、【扩展】for…in中的withIndex方法

image-20240417163753785

从上图可以看到,withIndex()方法要返回一个迭代器,其会返回一个对象,有下标index,和值i,可以通过kotlin的一个解构的方法获取:

fun main() {
    var list = ('a'..<'z').toList()
    for ((index,i)in list.withIndex()) {
        println("$index $i")
    }
}

输出:

image-20240417164130364

3.5、【扩展】forEachIndexed()方法
fun main() {
    var list = ('a'..<'z').toList()
    list.forEachIndexed{index, value->
         println("$index $value")
    }
}

forEachIndexed()方法:

image-20240417165201802

3.6、do…while和while

这两个语法没有变化,与常见语言的格式一样。

do…while:

fun main() {
    var i = 1
    do {
        println("$i")
    } while (i++ < 6)
}a

while:

fun main() {
    var i = 1
    while (i++ < 6) {
        println("$i")
    }
}

4、return,break,continue结束语句的使用

4.1、结束循环分支

例如:

fun main() {
    for (i in 1..10)
        for (j in 1..10) {
            if (i == 5 && j == 5) {
                break  //或者continue
            }
            println("$i $j ")
        }
}

解释:首先,break或者continue会优先执行的语句段是距离关键字(break或者continue)最近的循环关键字,所以上述代码中break的是j所在的for。

4.2、标签备注代码块–@
fun main() {
    a@for (i in 1..10)
        for (j in 1..10) {
            if (i == 5 && j == 5) {
                break@a //或者continue
            }
            println("$i $j ")
        }
}

像这段,就可以指定a@…@a这段代码区域执行break或者continue。

4.3、forEach退出循环的几种方式
fun main() {
    (1..<10).forEach {
        if (it == 5) {
            // 执行跳出循环                  
        }
        println(it)
    }
}

就像上述代码,想在it == 5的时候执行跳出循环 ,但是这个时候无法直接跳出循环,因为其底层是使用lamda表达式和函数实现的,即无法使用关键字break,continue。而使用return语句则会直接返回main函数,所以有下面几种写法:

  1. 寻找最近forEach,这样就不会打印5这个数字,就像continue一样。

    fun main() {
        (1..<10).forEach {
            if (it == 5) {
                 return@forEach // 执行跳过本次循环
            }
            println(it)
        }
    }
    
  2. a@…@a方式与return@forEach一样,跳过本次循环。

    fun main() {
        (1..<10).forEach  a@{
            if (it == 5) {
                 return@a // 执行跳过本次循环
            }
            println(it)
        }
    }
    
  3. run函数结束循环,类似于return函数那种感觉。

    fun main() {
        run {
            (1..<10).forEach{
                if (it == 5) {
                    return@run// 执行结束循环(run函数内的结束)
                }
                println(it)
            }
        }
    }
    
  4. 同理上述方法可以写成

    fun main() {
        run a@ {
            (1..<10).forEach{
                if (it == 5) {
                    return@a // 执行结束循环
                }
                println(it)
            }
        }
    }
    

三、区间

1、语法

fun main() {
    // 1-10的闭区间
    1..10
    // 1-10的开区间
    1 until 10
    // 10-1的倒序区间
    10 downTo 1
    // 步长为2的区间
    1..10 step 2
}

kotlin在1.8.2以上就推荐开区间的写法为:

1 ..< 10   //原来 1 until 10

2、遍历

遍历方法有很多,如for…in 、toList 等。

可以使用forEach进行遍历:

fun main() {
   ( 1..10 step 2).forEach(){
       println(it)
   }
}

但是,forEach方法不能进行遍历浮点型。

使用toList()进行遍历:

fun main() {
  (  1..<10).toList().forEachIndexed({ i, v -> println("$i: $v") })
}

3、查找是否在区间内

fun main() {
    println(1.1F in 1f..<10f)
}

返回一个boolean的类型。

4、字符区间

遍历变量c,范围从a到z:

fun main() {
    for (c in 'a'..'z') println(c)
}
fun main() {
    //步长为2
    for (c in 'a'..'z' step 2) println(c) 
}

四、函数

1、函数定义

fun 函数名(参数: 类型) :返回值类型{
    //函数体
    return 返回值
}
fun main() {
    a()
}

fun a() {}

像上述的代码,返回值类型可以省略,函数会自动推导,如果没有返回值,则默认返回Unit(等价于Java中的void)。

image-20240417173716908

加法函数:

fun main() {
    println(sum(1,2))
}

fun sum(a: Int, b: Int): Int {
    return a + b
}

可以简写,如果只有一行:

fun sum(a: Int, b: Int): Int = a + b

fun sum(a: Int, b: Int) = a + b

2、infix关键字

infix 是一个关键字,用于定义中缀函数(Infix Functions)。中缀函数是 Kotlin 提供的一项有用的功能,可以使代码更加清晰和易读,尤其是在某些领域特定语言中,它可以改善代码的可读性和表达能力。

fun main() {
    //以下三种写法结果相同。
    println(1.sum(2))
    println(1 sum (2))
    println(1 sum 2)
}
infix fun Int.sum(a: Int) = this + a
  • 中缀函数必须是成员函数或扩展函数。
  • 中缀函数必须只有一个参数。
  • 参数不能是可变参数(varargs)。

可以展现多态等面向对象的一些特性,例如:(分别调用不同的同名函数==>多态)

fun main() {
 println(1 sum 2)
 println(1.1F sum 2)
}
infix fun Int.sum(a: Int) = this + a
infix fun Float.sum(a: Int) = this + a

3、参数省略

与js的语法有点像,传参数的时候可以设置默认值,如果传入则覆盖,没有传入则使用默认值。

fun main() {
    println(sum(1))
}
fun sum(a: Int, b: Int = 3) = b + a

上述代码没有传入b的值,就取得默认值3

亦可使用b = 1, a = 2来忽略参数顺序:

fun main() {
    println(sum(b = 1, a = 2))
}
fun sum(a: Int, b: Int = 3) = b + a

4、函数类型参数

fun main() {
    sum(b = 1, a = 2, c = { println(it) })
}

fun sum(a: Int, b: Int, c: (Int) -> Unit): Int {
    val result = b + a // 计算 a 和 b 的和
    c(result)          // 调用传入的函数 c,传入结果值并执行其逻辑
    return result      // 返回计算结果
}

main 函数内部调用了名为 sum 的函数,传入了三个参数:

  • 参数 c 是一个类型为 (Int) -> Unit 的函数类型参数。该类型的含义是接收一个 Int 类型的输入参数并返回 Unit 类型(相当于 Java 中的 void)。传入的匿名函数 { println(it) } 实现了这一功能,其中 it 是对传入参数的隐式引用,println 函数用于打印传入的整数到标准输出。

定义了一个名为 sum 的函数,它接受以下三个参数:

  • a: Int:一个整数参数。
  • b: Int:另一个整数参数。
  • c: (Int) -> Unit:一个函数类型参数,如上所述,接收一个整数并返回无具体值(Unit)。

只有函数类型参数的简写:

fun main() {
    sum { //()圆括号可以省略
        println(it)
    }
}

fun sum(c: (Int) -> Unit) { //这里必须是lambda表达式
    c(3)          // 调用传入的函数 c,传入结果值并执行其逻辑
}

5、多参数–vararg

fun main() {
    sum("1", "2", "3")
}

//多参函数
fun sum(vararg list: String) {
    list.forEach {
        println(it)
    }
}

上述写法不太友好,换个写法:

fun main() {
    var arr = arrayOf("1", "2", "3")
    sum(*arr) //通过*展开参数
}

//多参函数
fun sum(vararg list: String) {
    list.forEach {
        println(it)
    }
}

与其他参数混合:(当然,写法不唯一)

fun main() {
    var arr = arrayOf("1", "2", "3")
    sum("1", list = arr)
}

//多参函数
fun sum(a: String, vararg list: String) {
    list.forEach {
        println(it)
    }
}

五、访问和属性修饰符

1、kotlin修饰符

kotlin在常见的访问修饰符private,protected,public中新增了internal这个修饰符

2、internal

  • 如果你声明为 internal,它会在相同模块内随处可见。

意思就是说这样设置就不能跨模块对其他类进行访问。

3、默认修饰符

在kotlin中,默认修饰符是public,并且还有final进行修饰

image-20240418164440727

其实,这就意味着kotlin中就默认没有继承。如果想要实现继承,那就使用open关键字。

4、open关键字开启继承并实现

fun main() {
    var b = B()
    b.print()
}

open class A {  //一定一定要添加open关键字
    var a = 1
}

class B : A() {
    var b = 2
    fun print() {
        println(a)
        println(b)
    }
}

一定一定要添加open关键字。

六、类与对象

1、声明和调用

fun main() {
    var a = A()  // 面相函数式,所以不用new关键字也可以
    a.print()
}

class A {
    val a = 2
    fun print() {
        println(a)
    }
}

2、get和set

在kotlin中,不能直接调用get和set方法,默认就是赋值就自动调用了set()方法,取值就自动调用get()方法。

就像下面这段示例,test.a = 5这个赋值操作调用set(),取值调用get():

fun main() {
    var test = A()
    test.a = 5
    test.print()

}

class A {
    var a = 2
        get() {
            return field - 1
        }
        set(value) {
            field = value + 1
        }
    fun print() {
        println(a)
    }
}

输出结果:

image-20240418153123854

3、init函数初始化

我们可以在类中添加初始化函数,每当创建一个类的对象之后,init方法就会自动调用,可以用于初始化数据

fun main() {
    var test = A()
}

class A {
    var a = 2
    init {
        println("A") //自动调用
    }
}

通过传入值初始化:

fun main() {
    var test = A(5)
}

class A(b: Int) {
    var a = 2

    init {
        a = b
    }
}

值得注意的是:在对对象传入值的之后可以同时对变量初始化,例如:class A(var b: Int) {},就代表了b就成为了A对象的成员变量,kotlin语法支持这样写。

但是只能在一级构造函数(主构造函数)上写这种,在二级构造函数中不支持这种语法,二级构造函数接下来有介绍。

4、constructor构造函数

在kotlin中奖构造函数分为主构造函数次构造函数

4.1、主构造函数
class  A constructor() {
    var a = 2
    init {
     	// 主构造函数的方法体
    }
}

其中,主构造函数关键字constructor可以省略不写,其构造的方法体就在init函数中。

4.2、二级构造函数
class A constructor(var b: Int = 1) {
    var a = 2

    init {
        println("init()...")
    }

    constructor() : this(3) { //代理主构造函数
        println("次构造函数...")
    }
}

注意:次构造函数需要主构造函数调用才能实现,即使用this()代理主构造函数,也可以传入参数this(3),可就是将b赋值为3。

这种方式就相当于java中的在构造方法中调用其他构造方法。

4.3、多个构造函数
class A constructor(var b: Int = 1) {
    var a = 2

    init {
        println("init()...")
    }

    constructor(c: Int, d: Int) : this(3) {
        println("次构造函数1...")
    }

    constructor() : this(1, 2) {
        println("次构造函数2...")
    }
}

image-20240418161321029

当然,次构2不一定要代理次构1,也可以直接代理主构,但是次构1就失效了。

4.4、省略主构造函数并写了次构造函数

例如这种,就直接成为主构造函数了:

class A  {
    constructor(c: Int, d: Int) {
        //此时识别为主构造函数
    }
}

多态

class A  {
    constructor(c: Int, d: Int) {
       
    }
    constructor(c: Int) {
        
    }
}

5、类的继承与重写

5.1、继承
fun main() {
    var b = B()
    b.print()
}

open class A {  //一定一定要添加open关键字
    var a = 1
}

class B : A() {
    var b = 2
    fun print() {
        println(a)
        println(b)
    }
}

一定一定要添加open关键字。

5.2、继承构造函数初始化

子类继承父类,子类要对父类进行构造方法的初始化:

1、主构造函数初始化

open class AParent {

}

class AChild() : AParent() {

} 

2、次构造函数初始化

open class AParent {

}

class AChild : AParent {
    constructor() : super(){}
}

3、父类多构造函数–子类主构造函数调用

当父类函数存在两个构造函数时,分别为主构造函数二级构造函数

open class AParent(name: String, age: Int) {
    constructor(name: String) : this(name, 30)
}

子类函数2:

class AChild() : AParent( "A") {}

子类函数1:

class AChild() : AParent( "A",15) {}

上述两种方式都可以实现父类构造函数的初始化,图解:

image-20240418172945118

3、父类多构造函数–子类副构造函数调用

image-20240418173640550

赋值输出:

fun main() {
    var aChild = AChild("rediaz")
    aChild.print()
}

open class AParent(var name: String, var age: Int) {
    constructor(name: String) : this(name, 30)
}

class AChild : AParent {
    constructor(name: String) : super(name)
    fun print(){
        println("name: ${super.name}")
    }
}

image-20240418174040391

5.3、函数的重写

正常继承,默认无法进行重写:

image-20240418174447191

它说需要override关键字,好嘛加上:

image-20240418174609562

然后又说基类的方法关键字是final修饰的。

正确重写方式,在父类的成员方法中加上open关键字:

open class AParent() {
    open  fun A() { println("A") }
    open fun B() { println("B") }
}

class AChild : AParent() {
    override fun A() {}
    override fun B() {}
}
5.4、属性的重写

这个是kotlin中特有的操作,Java中没有。

示例:

open class AParent() {
    open val a = 1
   open var b = 2
}

class AChild : AParent() {
    override val a: Int
        get() = super.a

    override var b: Int
        get() = super.b
        set(value) {}
}

上述这种方式就实现了重写属性成员a和b,其中a是常量,b是变量,所以重写之后有点区别。

重写的时候可以直接赋值进行覆盖:

open class AParent() {
    open val a = 1
    open var b = 2
}

class AChild : AParent() {
    override val a: Int = 0
    override var b: Int = 0
}

但是底层还是有get和set方法

重写时将a的val换成var,则其会将代码编译成新的变量,并生成对应的get和set方法:

image-20240418180434262

6、抽象类、嵌套类和内部类

6.1、抽象类

对于抽象类,其用法与Java无异

抽象是面向对象编程的特征之一,类本身,或类中的部分成员,都可以声明为abstract的。抽象成员在类中不存在具体的实现。

需要注意的是:不用对抽象类或抽象成员标注open注解。

abstract class AParent() { // 抽象类
    abstract fun sum() // 抽象方法
}

class AChild : AParent() {
    override fun sum() {
       
    }
}
6.2、嵌套类

先上代码:

fun main() {
    var aParent = AParent()
    aParent.print()
    var aChild = AParent.AChild()
    aChild.print()
}

class AParent() { // 抽象类
    fun print() {
        println("外部类")
    }

    class AChild() {
        fun print() {
            println("嵌套类")
        }
    }

}

注:在Java中,这是一个内部类的一个写法,但是在koltin中,这种事属于内部类的一个写法。

嵌套类是被final关键字修饰的

6.3、内部类

在嵌套类的基础上,在嵌套类的前面加上一个关键字inner就变成了内部类,并且可以访问外部类中的成员变量,使用this@类名的方式。示例:

fun main() {
    AParent().AChild().print()
}

class AParent() {
    val a = 1
    fun print() {
        println("外部类")
    }
 
    inner class AChild() { //加上inner关键字
        fun print() {
            println("嵌套类")
            println(this@AParent.a) //获取AParent中的a值
        }
    }
}

七、自定义注解

示例:

@Fancy("hello")
class A {
    var a = 1
}


annotation class Fancy(val name: String) //可以对注解进行初始化

注解的附加属性可以通过用元注解标注注解类来指定:

  • @Target 指定可以用该注解标注的元素的可能的类型(类、函数、属性与表达式);
  • @Retention 指定该注解是否存储在编译后的 class 文件中,以及它在运行时能否通过反射可见 (默认都是 true);
  • @Repeatable 允许在单个元素上多次使用相同的该注解;
  • @MustBeDocumented 指定该注解是公有 API 的一部分,并且应该包含在生成的 API 文档中显示的类或方法的签名中。
@Fancy()
class A {}

@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION,
    AnnotationTarget.TYPE_PARAMETER, AnnotationTarget.VALUE_PARAMETER,
    AnnotationTarget.EXPRESSION)
@Retention(AnnotationRetention.SOURCE)
@MustBeDocumented
annotation class Fancy()

上述部分内容来自官方文档解释。

八、接口与接口实现

1、接口定义与实现

在kotlin中语法与Java语法差不多,唯一区别就是没有使用implement关键字,用冒号,像继承一样。多个接口继承用逗号隔开。下面是接口定义与接口实现的一个例子:

class AParent() : A, B {  //接口实现

}

interface A {   //接口A

}

interface B {   //接口B

}

2、接口成员变量

2.1、单个接口函数复写
class AParent() : A{  //接口实现
    override fun print() {
        super.print()
    }
}

interface A {   //接口A
    fun print(){}
}

注意:当接口A函数中的print()方法存在方法体{}的时候,默认是可以不重写。没有方法体的时候强制重写,这点个Java一样。

2.2、多个接口同名函数复写

当多个接口具有同名函数的时候,需要使用super<A>指定复写的是哪一个接口中的函数:

class AParent() : A, B {  //接口实现
    override fun print() {
        super<A>.print() //指定父类A的print方法
        super<B>.print() //指定父类B的print方法
    }
}

interface A {   //接口A
    fun print() {}
}

interface B {   //接口A
    fun print() {}
}
2.2、接口成员存在变量和常量

变量重写,其实就是重新其get和set方法:

class AParent() : A {  //接口实现
    override var name: String
        get() = TODO("Not yet implemented")
        set(value) {}
}

interface A {   //接口A
   var name: String
}

常量重写,其实就是重新其get方法:

class AParent() : A {  //接口实现
    override val name: String
        get() = TODO("Not yet implemented")
}

interface A {   //接口A
   val name: String
}

也可以将复写的变量写在类的()里面:

class AParent(override var name: String) : A {  //接口实现
}

interface A {   //接口A
    var name: String
}

九、数据类、伴生类、枚举类

1、数据类

特点:数据类的主构造函数必须要有参数,还有添加data关键字

data class A(val name: String, val age: Int) 

使用:其中有个方法叫copy可以进行对象的复制(普通class对象是没有copy方法的)

打印的时候直接使用对象,因为底层实现了**toString()**方法,普通对象使用必须复写toString()方法。

fun main() {
    var a = A("刘德华", 17)
    var copy = a.copy("李建")
    println(a) //直接使用对象,因为底层实现了toString()方法
    println(copy)
}

data class A(val name: String, val age: Int){}

运行结果:

image-20240419154635477

通过var copy = a.copy(“李建”)这个函数,进行复制A对象这个数据对象,并且传入值“李建”,但是没有传入age值,这是因为其底层实现将变化的值进行修改,默认值就不变,copy方法实现源码如下:

image-20240419155102368

data class 常用于后端请求的响应之类的,常用于数据模型的使用。

2、伴生类

2.1、定义伴生类

伴生类(companion class)是Kotlin中的一个特殊类,它与普通类不同,可以包含类似Java中的静态成员和方法

在Kotlin中,类不能有静态成员,但是可以使用伴生类来模拟静态成员和方法的行为。伴生类可以访问其所属类的私有成员,并且可以通过类名直接访问其伴生对象的成员。

伴生类的成员可以通过类名直接访问,而不需要创建类的实例

fun main() {
  A.Companion.print()
}
 class A() {
    companion object {
        fun print() {
            println("A")
        }
    }
}

伴生类也支持接口实现,类的继承等。

2.2、@JvmStatic注解
  • @JvmStatic注解用于将伴生对象中的成员标记为静态成员,使得这些成员可以在Java代码中直接通过类名访问,而不需要通过实例化对象来访问。

  • 在Kotlin中,伴生对象的成员默认是在包含伴生对象的类的内部访问的,因此如果想要在Java代码中直接访问伴生对象的成员,就需要使用@JvmStatic注解来标记这些成员。

  • 这样可以更好地与Java代码进行互操作,使得Kotlin代码更加灵活和易于使用。

代码示例:

kotlin代码:

fun main() {

}
 class A() {
    companion object {
        @JvmStatic
        fun print1() {
            println("print1")
        }

        fun print2() {
            println("print2")
        }
    }
}

java代码:

public class JavaMain {
    public static void main(String[] args) {
        A.Companion.print1();
        A.print1();
        A.Companion.print2();
    }
}

输出,相当远只是不用多写一个Companion

image-20240419162553656

2.3、const关键字
  • 在Kotlin中,const关键字用于声明常量。

  • 在伴生类中,如果想要声明一个常量,可以使用const关键字来修饰伴生对象中的属性。

  • 被const修饰的属性必须是基本数据类型或String类型,并且必须在编译时就能确定其值。

例如:

class MyClass {
    companion object {
        const val PI = 3.14
    }
}

在上面的例子中,PI被声明为一个常量,其值为3.14。在使用时,可以通过类名直接访问这个常量,而不需要实例化对象

常量在编译时会被替换为其实际值,因此在运行时不会存在常量的实例。常量的值在编译时就已经确定,不会发生变化。

3、枚举类

3.1、定义

定义与Java没太大区别:

fun main() {
    Test.NAME
}

enum class Test {
    NAME,
    AGE
}

与Java不一样的地方:

3.2、传参
fun main() {
    println(Test.NAME)
    println(Test.NAME.value)
    println(Test.NAME.name)
}

enum class Test(val value: String) {
    NAME("name"),
    AGE("age")
}

image-20240419165703712

fun main() {
    println(Test.NAME.value)
    println(Test.AGE.value)
}

enum class Test(val value: String="value") {
    NAME(), //没有值则使用默认值
    AGE("age")
}
3.3、继承与实现
fun main() {
    Test.NAME.testInterfaceTest()
    Test.AGE.testInterfaceTest()
}

enum class Test() : TestInterface {
    NAME() {
        override fun testInterfaceTest() {
            println("NAME testInterfaceTest...")
        }
    },
    AGE() {
        override fun testInterfaceTest() {
            println("AGE testInterfaceTest...")
        }
    }
}

interface TestInterface {
    fun testInterfaceTest()
}

image-20240419170203056

可以理解为在一个类A中套用了许多静态类,然后就是调用静态类的过程。

本质上枚举成员是继承自枚举对象的。

当然,Java中也可以随便调用:

image-20240419170707251

十、单例和对象表达式

1、单例模式的创建

在Kotlin中,单例模式可以通过对象声明(object declaration)来实现。

对象声明是一种在声明时就创建单例对象的方式,确保整个应用程序中只有一个实例存在。

对象声明在Kotlin中是线程安全的,因此可以保证在多线程环境下也只有一个实例被创建。

fun main() {
    test.testFun()
}

object test {
    fun testFun() {
        println("单例模式创建")
    }
}

底层Java实现(饿汉模式):

image-20240419171900944

通过对象声明实现的单例模式具有以下特点:

  1. 懒加载:对象声明在首次访问时才会被初始化,因此可以实现懒加载。
  2. 线程安全:对象声明是线程安全的,可以在多线程环境下安全地使用。
  3. 简洁:对象声明提供了一种简洁的方式来实现单例模式,不需要编写复杂的代码。

总的来说,Kotlin中的单例模式通过对象声明实现,提供了一种简单、安全且线程安全的方式来创建单例对象。

2、对象表达式

在Kotlin中,对象表达式(Object Expression)是一种用于创建临时对象的方式,类似于Java中的匿名内部类。

对象表达式可以用来创建一个实现某个接口或继承自某个类的对象,并且可以在需要的地方直接使用这个对象,而不需要显式地定义一个类。

对象表达式的语法如下:

val obj = object : SomeInterface {
    override fun someFunction() {
        // 实现接口的方法
    }
}

在上面的例子中,通过object关键字创建了一个实现SomeInterface接口的临时对象,并实现了其中的someFunction方法。这个对象可以直接赋值给一个变量,然后在需要的地方使用。

对象表达式可以用于以下情况:

  1. 实现接口:可以通过对象表达式来实现接口中的方法。
  2. 继承类:可以通过对象表达式来继承某个类,并实现其中的方法。
  3. 匿名对象:对象表达式创建的对象是匿名的,不需要显式地命名。

对象表达式在需要创建一个临时对象并实现某些接口或方法时非常有用,可以简化代码并提高灵活性。

示例:

众所周知,接口是不能被new的,所以创建一个匿名接口可以如下方式:

fun main() {
    request(object : CallBack {
        override fun loading() {}
    })
}

interface CallBack {
    fun loading()
}

fun request(CallBack: CallBack) {
    CallBack.loading()
}

实现Java中的interface:

准备一个Java接口:

public interface JavaInterface {
    void loading();
}

当kotlin中使用这个对象的时候:

fun main() {
    request(object : JavaInterface {
        override fun loading() {}
    })
}

fun request(CallBack: JavaInterface) {
    CallBack.loading()
}

会提示你写成lambda表达式:

image-20240419175644075

即:

fun main() {
    request {

    }
}

fun request(CallBack: JavaInterface) {
    CallBack.loading()
}

这是因为对象表达式对Java接口和kotlin接口的识别方式有些区别。

十一、密封类和密封接口

1、密封类

  • 在Kotlin中,密封类(Sealed Class)是一种特殊的类,用于表示受限制的类继承结构

  • 密封类可以有子类,但是这些子类必须嵌套在密封类的内部同一个文件中,这样就限制了密封类的继承结构。

  • 密封类通常用于表示有限的类层次结构,例如表示状态的类或操作的类。

密封类的定义方式如下:

sealed class Result
data class Success(val data: String) : Result()
data class Error(val message: String) : Result()

在上面的例子中,Result是一个密封类,它有两个子类Success和Error。这两个子类都嵌套在Result类的内部。由于Result是一个密封类,因此它的子类是有限的,并且都是在同一个文件中定义的。

密封类的特点包括:

  1. 子类有限:密封类的子类是有限的,且必须嵌套在密封类内部或同一个文件中。
  2. 类型推断:使用密封类时,可以通过类型推断来处理所有可能的子类,而不需要使用else语句。
  3. 安全性:密封类提供了更严格的类继承结构,可以帮助开发者避免遗漏某些情况的处理。

使用密封类可以帮助我们更清晰地表示有限的类层次结构,提高代码的可读性和安全性。密封类在处理状态、操作等有限的情况时非常有用。

可以把密封类理解成枚举抽象的结合。

  • 密封类无法实例化,智能继承。

可以使用密封类来模拟登录和登出的操作:

/**
 * 主函数,程序的入口点。
 * 实现了用户登录和退出登录的处理。
 */
fun main() {
    // 处理用户登录请求
    handerMainIntent(MainIntent.Login("李明", "123456"))
    // 处理用户退出登录请求
    handerMainIntent(MainIntent.Logout)
}

/**
 * 主意图 sealed 类,用于封装所有主功能的操作。
 */
sealed class MainIntent {
    /**
     * 用户登录意图数据类。
     * @param username 用户名。
     * @param password 密码。
     */
    data class Login(val username: String, val password: String) : MainIntent()

    /**
     * 用户退出登录意图。
     */
    object Logout : MainIntent()
}

/**
 * 处理主意图的函数。
 * 根据传入的 MainIntent 对象执行相应的操作。
 * @param mainIntent 用户的主意图,可以是登录或退出登录。
 */
fun handerMainIntent(mainIntent: MainIntent) {
    when (mainIntent) {
        is MainIntent.Login -> userLoginRequest(mainIntent.username, mainIntent.password) // 处理用户登录请求
        MainIntent.Logout -> println("退出登录.....") // 处理用户退出登录请求
    }
}

/**
 * 处理用户登录请求的函数。
 * @param username 用户名。
 * @param password 密码。
 * 打印用户登录信息。
 */
fun userLoginRequest(username: String, password: String) {
    println("用户登录:${username} ${password}")
}

2、密封接口

与密封类枚举抽象的结合类似,密封接口就相当于是枚举接口的结合。

下面是一个游戏数据模拟的一段代码:

fun main() {

}

fun handerHealth(role: Weapon) {
    when (role) {
        is PlayerType1 -> println("玩家1")
        is PlayerType2 -> println("玩家2")
        is PlayerType3 -> println("玩家3")
        is EnemyType1 -> println("敌人1")
        is EnemyType2 -> println("敌人2")
        is EnemyType3 -> println("敌人3")
    }
}
fun handerWeapon(role: Weapon) {
    when (role) {
        is PlayerType1 -> println("玩家1")
        is PlayerType2 -> println("玩家2")
        is PlayerType3 -> println("玩家3")
        is EnemyType1 -> println("敌人1")
        is EnemyType2 -> println("敌人2")
        is EnemyType3 -> println("敌人3")
    }
}


sealed interface Health {}
sealed interface Weapon {}
class PlayerType1 : Health, Weapon
class PlayerType2 : Health, Weapon
class PlayerType3 : Health, Weapon
class EnemyType1 : Health, Weapon
class EnemyType2 : Health, Weapon
class EnemyType3 : Health, Weapon

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/609525.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

vue数据大屏并发请求

并发? 处理并发 因为js是单线程的&#xff0c;所以前端的并发指的是在极短时间内发送多个数据请求&#xff0c;比如说循环中发送 ajax , 轮询定时器中发送 ajax 请求. 然后还没有使用队列, 同时发送 的. 1. Promise.all 可以采用Promise.all处理并发&#xff0c; 当所有pro…

gjfjiv是什么意思

GJFJV-4B1&#xff0c;gjfjv-6a1a&#xff0c;gjfjv光缆 室内光缆型号命名 产品描述 多样的光缆结构选择&#xff0c;可在有限的空间内布设&#xff0c;且无缠绕效应 可于建设物间导管托盘和通道中使用 理想的网络光缆在保证对光纤的保护前提下易于布设&#xff0c;插接和识…

数据链路层之 以太网协议

以太网协议 这个协议即规定了数据链路层&#xff0c;同时也规定了物理层的内容。平时使用到的网线&#xff0c;其实也叫做“以太网线”&#xff08;遵守以太网协议的网线&#xff09;。 以太网帧格式 以太网数据帧 帧头 载荷 帧尾。 帧头&#xff1a;目的地址、源地址、类型…

django项目结构介绍

小白的django学习笔记 五一前的某天 文章目录 django项目结构介绍项目的基本配置templates项目模块manage.pyExternal Libraries django项目结构介绍 项目的基本配置 在这里配置&#xff0c;跟工程名是一样的 templates 放网页、js、css的地方 django 项目模块 项目开发时&…

《前端算法宝典:双指针问题解析与应用》

双指针 双指针&#xff0c;指的是在遍历对象的过程中使用两个相同方向&#xff08;快慢指针&#xff09;或者相反方向&#xff08;对撞指针&#xff09;的指针或者是两个指针构成一个滑动窗口进行扫描&#xff0c;从而达到相应的目的。 双指针方法在某些情况下可以对有序数组…

sbt安装

一、sbt介绍 在Spark中&#xff0c;sbt&#xff08;Scala Build Tool&#xff09;是一个用于构建Scala项目的工具。它是Spark项目的主要构建工具之一&#xff0c;用于编译Scala代码、管理依赖项、打包应用程序以及执行其他与项目构建相关的任务。 sbt的用途在Spark开发中主要…

『大模型笔记』Google CEO Sundar Pichai(桑达尔·皮查伊)谈人工智能的未来!

Google CEO Sundar Pichai(桑达尔皮查伊)谈人工智能的未来! 文章目录 一. Google CEO谈人工智能的未来总结摘要观点时间线二. 参考文献简短总结:一. Google CEO谈人工智能的未来 总结 主要介绍了Google CEO Sundar Pichai对人工智能未来的看法,以及Google在AI领域的战略…

JavaScript异步编程——06-Promise入门详解【万字长文,感谢支持】

前言 Promise 是 JavaScript 中特有的语法。可以毫不夸张得说&#xff0c;Promise 是ES6中最重要的语法&#xff0c;没有之一。初学者可能对 Promise 的概念有些陌生&#xff0c;但是不用担心。大多数情况下&#xff0c;使用 Promise 的语法是比较固定的。我们可以先把这些固定…

三月份饮料行业线上市场销售数据分析

2024年3月&#xff0c;中国饮料市场呈现出多样化和健康趋势的明显特征。从消费场景、消费端需求以及销售渠道来看&#xff0c;饮料市场正在经历多元化的发展&#xff0c;这不仅体现在产品种类上&#xff0c;也体现在消费者的购买行为和偏好上。 据鲸参谋数据统计&#xff0c;线…

LLM大语言模型(十五):LangChain的Agent中使用自定义的ChatGLM,且底层调用的是remote的ChatGLM3-6B的HTTP服务

背景 本文搭建了一个完整的LangChain的Agent&#xff0c;调用本地启动的ChatGLM3-6B的HTTP server。 为后续的RAG做好了准备。 增加服务端role&#xff1a;observation ChatGLM3的官方demo&#xff1a;openai_api_demo目录 api_server.py文件 class ChatMessage(BaseModel…

LeetCode HOT 100刷题总结

文章目录 1 哈希1.1 1-1.两数之和&#x1f7e2;1.2 2-49.字母异位词分组&#x1f7e1;1.3 3-128.最长连续序列&#x1f7e1; 2 双指针2.1 4-283.移动零&#x1f7e2;2.2 6-15.三数之和&#x1f7e1;2.3 7-11.盛最多水的容器&#x1f7e1;2.4 8-42.接雨水&#x1f534; 3 滑动窗…

传输层协议——UDP协议

目录 一、传输层 二、再谈端口号 端口号的划分 知名端口号 pidof netstat命令 三、UDP协议 1、UDP协议格式 2、UDP协议特点 3、UDP协议的缓冲区 四、基于UDP的应用层协议 一、传输层 上一篇文章我们所讲到的HTTP协议和HTTPS协议&#xff0c;是属于应用层协议。我们…

【小笔记】问答系统可视化实现的三种方式

下面三种方式都是基于Python的哈&#xff0c;从简单到复杂。 方式一&#xff1a;命令行交互问答 优点&#xff1a;原始简单直接 方式二&#xff1a;使用Python可视化框架 优点&#xff1a;无需学习前端技术栈即可搭建一个web。 streamlit&#xff1a;⭐️⭐️⭐️⭐️gra…

【服务器优化】LVS负载均衡

LVS负载均衡 LVS简介 ​ LVS&#xff08;Linux Virtual Server&#xff09;即Linux虚拟服务器&#xff0c;是由章文嵩博士主导的开源负载均衡项目&#xff0c;目前LVS已经被集成到Linux内核模块中。该项目在Linux内核中实现了基于IP的数据请求负载均衡调度方案&#xff0c;终…

nginx的应用部署nginx

这里写目录标题 nginxnginx的优点什么是集群常见的集群什么是正向代理、反向代理、透明代理常见的代理技术正向代理反向代理透明代理 nginx部署 nginx nginx&#xff08;发音同enginex&#xff09;是一款轻量级的Web服务器/反向代理服务器及电子邮件&#xff08;IMAP/POP3&…

Java设计模式 _结构型模式_外观模式

一、外观模式 1、外观模式 外观模式&#xff08;Facade Pattern&#xff09;是一种结构型模式。主要特点为隐藏系统的复杂性&#xff0c;并向客户端提供了一个客户端可以访问系统的接口。这有助于降低系统的复杂性&#xff0c;提高可维护性。当客户端与多个子系统之间存在大量…

FPGA+海思ARM方案,可同时接收HDMI/VGA 两种信号,远程控制

FPGA海思ARM方案&#xff0c;可同时接收HDMI/VGA 两种信号&#xff0c;通过配置输出任一图像或者拼接后的图像 客户应用&#xff1a;无线远程控制 主要特性&#xff1a; 1.支持2K以下任意分辨率格式 2.支持H264压缩图像 3.支持WIFI/4G无线传输 4.支持自适应输入图像分辨率 …

如何编辑百度百科里面的资料

编辑百度百科资料是一个相对简单的过程&#xff0c;但同时也需要遵循一定的规则和流程。以下是百科优化网yajje整理的编辑百度百科资料的步骤和注意事项。 登录账户 首先&#xff0c;编辑百度百科需要一个百度账号。如果没有&#xff0c;你需要先注册一个。登录后&#xff0c;…

西奥机电CLRT-01:重塑碳酸饮料质检新纪元

西奥机电CLRT-01&#xff1a;重塑碳酸饮料质检新纪元 在追求品质生活的今天&#xff0c;碳酸饮料的品质检测成为了行业内外关注的焦点。西奥机电&#xff0c;作为行业创新的领跑者&#xff0c;携其最新研发的CLRT-01二氧化碳气容量测试仪&#xff0c;为碳酸饮料行业带来了革命性…

一文详解|影响成长的关键思考(二)

之前写过一篇《一文详解&#xff5c;影响成长的关键思考》&#xff0c;里面对自己工作前几年的心法进行了总结&#xff0c;并分享了出来。现在又工作了一段时间后&#xff0c;有了一些新的体会&#xff0c;想进一步分析一下&#xff0c;于是便有了此文。的确&#xff0c;思考也…
最新文章