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是可以省略的。
val常量:
2、字符型
定义就与上面一样,调用的方法也与java差不多,equals等。
占位符:
fun main() {
var string: String = "hello world !"
println("hello ${string}") //输出
}
输出:
多模版输出:
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()
}
3.4、Set集合–不重复添加元素
不可变集合的方式:
fun main() {
setOf(1, 2, 3, 3, 3, 3).forEach{ //遍历集合
println(it)
}
}
可变集合的方式:
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) //数据长度
}
4、kotlin特有的数据类型和集合
4.1、Any、Nothing
Any不再介绍,类似于Java中的Object。
Nothing:没有什么是没有实例的。您可以使用Nothing来表示“一个永远不存在的值”:例如,如果函数的返回类型为Nothing,则意味着它永远不会返回(总是抛出异常)。
可以看kotlin中的null就是这样的一个数据类型:
kotlin中最具有特色的是:不会有空指针存在
例如:Null不能是非Null类型Int的值法一:
法一:
官方推荐写法:
这样后续在调用b的时候就进行了为空的判定,例如:
解释:b在拼接3的时候,?就会进行判定b是否为空,如果b为空那就不会调用plus方法,这样就避免了空指针异常。
(上述输出为null,即调用b.toString()方法输出。)
法二:
还有一点就是b在一定不为空的情况下,则可以使用!!强制赋值,例如:
并且后续使用b调用方法都不用再使用?判定b值是否为空。
法三:
使用lateinit关键字(只能对复杂类型初始化)进行初始化延后,但是不推荐这种写法,类似于欺骗编译器我等一会初始化,例如:
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关键字只能用于以下情况:
- 伴生对象中的属性:const关键字可以用于伴生对象中的属性,将其声明为常量。
- 顶层属性:const关键字可以用于顶层属性,将其声明为常量。
- 原生数据类型和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分支。
多分支已是如此:
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循环:
3.3、迭代器
先看迭代器的方法:
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方法
从上图可以看到,withIndex()方法要返回一个迭代器,其会返回一个对象,有下标index,和值i,可以通过kotlin的一个解构的方法获取:
fun main() {
var list = ('a'..<'z').toList()
for ((index,i)in list.withIndex()) {
println("$index $i")
}
}
输出:
3.5、【扩展】forEachIndexed()方法
fun main() {
var list = ('a'..<'z').toList()
list.forEachIndexed{index, value->
println("$index $value")
}
}
forEachIndexed()方法:
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函数,所以有下面几种写法:
-
寻找最近forEach,这样就不会打印5这个数字,就像continue一样。
fun main() { (1..<10).forEach { if (it == 5) { return@forEach // 执行跳过本次循环 } println(it) } }
-
a@…@a方式与return@forEach一样,跳过本次循环。
fun main() { (1..<10).forEach a@{ if (it == 5) { return@a // 执行跳过本次循环 } println(it) } }
-
run函数结束循环,类似于return函数那种感觉。
fun main() { run { (1..<10).forEach{ if (it == 5) { return@run// 执行结束循环(run函数内的结束) } println(it) } } }
-
同理上述方法可以写成
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)。
加法函数:
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进行修饰
其实,这就意味着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)
}
}
输出结果:
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...")
}
}
当然,次构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) {}
上述两种方式都可以实现父类构造函数的初始化,图解:
3、父类多构造函数–子类副构造函数调用 :
赋值输出:
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}")
}
}
5.3、函数的重写
正常继承,默认无法进行重写:
它说需要override关键字,好嘛加上:
然后又说基类的方法关键字是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方法:
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){}
运行结果:
通过var copy = a.copy(“李建”)这个函数,进行复制A对象这个数据对象,并且传入值“李建”,但是没有传入age值,这是因为其底层实现将变化的值进行修改,默认值就不变,copy方法实现源码如下:
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
:
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")
}
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()
}
可以理解为在一个类A中套用了许多静态类,然后就是调用静态类的过程。
本质上枚举成员是继承自枚举对象的。
当然,Java中也可以随便调用:
十、单例和对象表达式
1、单例模式的创建
在Kotlin中,单例模式可以通过对象声明(object declaration)来实现。
对象声明是一种在声明时就创建单例对象的方式,确保整个应用程序中只有一个实例存在。
对象声明在Kotlin中是线程安全的,因此可以保证在多线程环境下也只有一个实例被创建。
fun main() {
test.testFun()
}
object test {
fun testFun() {
println("单例模式创建")
}
}
底层Java实现(饿汉模式):
通过对象声明实现的单例模式具有以下特点:
- 懒加载:对象声明在首次访问时才会被初始化,因此可以实现懒加载。
- 线程安全:对象声明是线程安全的,可以在多线程环境下安全地使用。
- 简洁:对象声明提供了一种简洁的方式来实现单例模式,不需要编写复杂的代码。
总的来说,Kotlin中的单例模式通过对象声明实现,提供了一种简单、安全且线程安全的方式来创建单例对象。
2、对象表达式
在Kotlin中,对象表达式(Object Expression)是一种用于创建临时对象的方式,类似于Java中的匿名内部类。
对象表达式可以用来创建一个实现某个接口或继承自某个类的对象,并且可以在需要的地方直接使用这个对象,而不需要显式地定义一个类。
对象表达式的语法如下:
val obj = object : SomeInterface {
override fun someFunction() {
// 实现接口的方法
}
}
在上面的例子中,通过object关键字创建了一个实现SomeInterface接口的临时对象,并实现了其中的someFunction方法。这个对象可以直接赋值给一个变量,然后在需要的地方使用。
对象表达式可以用于以下情况:
- 实现接口:可以通过对象表达式来实现接口中的方法。
- 继承类:可以通过对象表达式来继承某个类,并实现其中的方法。
- 匿名对象:对象表达式创建的对象是匿名的,不需要显式地命名。
对象表达式在需要创建一个临时对象并实现某些接口或方法时非常有用,可以简化代码并提高灵活性。
示例:
众所周知,接口是不能被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表达式:
即:
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是一个密封类,因此它的子类是有限的,并且都是在同一个文件中定义的。
密封类的特点包括:
- 子类有限:密封类的子类是有限的,且必须嵌套在密封类内部或同一个文件中。
- 类型推断:使用密封类时,可以通过类型推断来处理所有可能的子类,而不需要使用else语句。
- 安全性:密封类提供了更严格的类继承结构,可以帮助开发者避免遗漏某些情况的处理。
使用密封类可以帮助我们更清晰地表示有限的类层次结构,提高代码的可读性和安全性。密封类在处理状态、操作等有限的情况时非常有用。
可以把密封类理解成枚举和抽象的结合。
- 密封类无法实例化,智能继承。
可以使用密封类来模拟登录和登出的操作:
/**
* 主函数,程序的入口点。
* 实现了用户登录和退出登录的处理。
*/
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