在 build.gradle.kts
中配置阿里源
repositories {
maven {
url = uri("https://maven.aliyun.com/repository/public/")
}
mavenLocal()
mavenCentral()
}
只能用于 Int
和 Long
。
// 有符号左移,中缀表示法
println(1 shl 2) // 或者 1.shl(2)
// 有符号右移
println(4 shr 1) // 或者 4.shr(1)
// 无符号右移
println(4 ushr 1) // 或者 4.ushr(1)
// 位与
println(4 and 1) // 或者 4.and(1)
// 位或
println(4 or 1) // 或者 4.or(1)
// 位异或
println(4 xor 1) // 或者 4.xor(1)
// 位非
println(4.inv())
使用 is
检测符合类型,!is
检测不符合类型,该检测是在运行时进行。
val str:Any = "hello"
if (str is String) {
println(str.uppercase())
}
if (str !is Number) {
println("不是数字类型")
}
智能转换。
fun fn(x: Any) {
// 在 && 之后 x 已被转化成 String 类型
if (x is String && x.length > 0) {
// x 转为 String 类型,只在该 if 范围有效
println(x.uppercase())
}
// 报错,x 不是 String 类型
// println(x.uppercase())
}
fun main() {
fn("hello")
}
使用 as
实现类型转换,转换不成功会抛出异常。
fun fn(x: Any?) {
// 报错,x 可能为 null,而 String 不为 null
// val y = x as String
val y = x as String?
println(y)
}
fun main() {
fn(null)
}
使用 as?
实现类型转换,转换不成功返回 null
,as
和 as?
都是在运行时转化。
fun fn(x: Any?) {
val y = x as? String
println(y)
}
fun main() {
fn(null)
}
val a = 1
val b = 2
// a 不为空 c 为 a,否则 c 为 b
val c = a ?: b
println(c)
if
用于判断,是一个代码块。
val a = 1
val b = 2
if (a > b) {
println("${a} 大于 ${b}")
} else if (a < b) {
println("${a} 小于 ${b}")
} else {
println("${a} 等于 ${b}")
}
if
可以是一个表达式,它将返回语句中的最后一个值,此时必须有 else
。
val a = 1
val b = 2
val max = if (a > b){
println("大于")
a
} else {
b
}
println(max)
val min = if (a < b) a else b
println(min)
与 c
语言中的 switch
类似,但功能更强大,,它的条件可以是任意表达式。
val a = 1
when(a) {
1 -> println("x == 1")
// 多行需要 {}
2 -> {
println("x == 2")
println("ok")
}
else -> {
println("x != 1 and x != 2")
println("fail")
}
}
可以作为一个表达式使用,但此时必须有 else
。
val a = 2
val msg = when(a) {
1 -> "x == 1"
2 -> {
println("x == 2")
"ok"
}
else -> {
println("x != 1 and x != 2")
"fail"
}
}
println(msg)
使用 ,
合并多个条件。
val a = 1
when(a) {
1,2 -> println("x == 1 or x == 2")
else -> println("other")
}
可以与 in
和 !in
使用。
val x = 2
when(x) {
in 1..3 -> println("1 <= x <= 3")
// 条件匹配,便不会继续之后的匹配
!in 4..6 -> println("x > 6 or x < 4")
else -> println("other")
}
可以与 is
和 !is
使用。
val x: Any = 8
when (x) {
is String -> println(x.uppercase())
is Int -> println(x * 2)
else -> println("false")
}
when
可以不提供参数。
val x: Any = "tom"
when {
x is String -> println(x.startsWith("a"))
x is Int -> println(x * 2)
else -> println("fail")
}
when
可以定义变量,该变量的作用域为 when
主体。
fun fn(x: String?): String? {
return x?.lowercase()
}
fun main(args: Array<String>) {
when (val x = fn("hEllo")) {
is String -> println(x.uppercase())
else -> println("nil")
}
// 错误
// println(x)
}
对任何提供迭代器(iterator
)的对象进行遍历。
val x = arrayOf(1,2,3)
for (it:Int in x) {
print("$it ")
}
println()
通过索引遍历。
val x = arrayOf(1,2,3)
for (i:Int in x.indices) {
print("${x[i]} ")
}
println()
使用 withIndex
获取索引和值。
val x = arrayOf(1,10,3,4)
for ((k,v) in x.withIndex()) {
println("k: ${k}, v: $v ")
}
println()
与 c
语言中用法相同。
var x = 1
while (x < 4) {
println(x)
x++
}
do {
println(x)
x++
} while (x < 5)
跳出直接包围它的循环,用于 for
和 while
。
fun main() {
for (i in 1..10) {
if (i == 4) {
break
}
println(i)
}
println("over")
}
可以跳转到指定的标签处,标签是由标识符后紧跟 @
符号构成。
fun main() {
loop@ for (i in 1..10) {
for (j in 1..10) {
if (j == 3 && i == 2) {
break@loop
}
}
println(i)
}
println("over")
}
终止本次循环,进行下一次的循环,用于 for
和 while
。
fun main() {
for (i in 1..10) {
if (i % 2 == 0) {
continue
}
print("$i ")
}
println("over")
}
continue
同样可以使用标签,只不过不会直接终止循环。
fun main() {
loop@ for (i in 1..10) {
print("$i ")
for (j in 1..10) {
if (j % 2 == 0) {
continue@loop
}
}
}
println()
println("over")
}
默认直接从包围它的函数或匿名函数处返回。
fun fn() {
val a = arrayOf(1,4,2,3)
a.forEach {
if (it == 2) {
// return 返回到 fn()
return
}
println(it)
}
}
fun main() {
fn()
println("main")
}
同样可以通过标签实现限定返回。
fun fn() {
val a = arrayOf(1,4,2,3)
a.forEach f@{
if (it == 2) {
// 与 continue 作用类似
return@f
}
println(it)
}
println("fn over")
}
fun main() {
fn() // 1 4 3
println("main")
}
可以通过隐式标签达到上面的效果,隐式标签与接受该 lambda
的函数同名。
fun fn() {
val a = arrayOf(1,4,2,3)
a.forEach {
if (it == 2) {
// 与 continue 作用类似
return@forEach
}
println(it)
}
println("fn over")
}
fun main() {
fn() // 1 4 3
println("main")
}
可以使用匿名函数来替代 lambda
表达式完成上述功能,此时不需要标签。
fun fn() {
val a = arrayOf(1,4,2,3)
a.forEach(fun (it){
if (it == 2) {
return
}
println(it)
})
println("fn over")
}
fun main() {
fn() // 1 4 3
println("main")
}
为现有类型提供别名。
语法如下:
typealias 别名=类型
一个简单的示例:
typealias MyInt = Int
fun main() {
val a: MyInt = 1
}
使用 fun
定义函数。函数可以在文件顶层声明,也可以声明在局部作用域、作为成员函数以及扩展函数。
// fun 函数名(参数列表):返回类型
// Unit 表示无返回,可以省略
// 返回类型需要显示声明,除非返回的类型是 Unit 或单表达式函数
fun fn():Unit {
println("这是一个函数")
}
使用 =
提供默认参数。
fun fn(a:Int = 1, b:Int=2):Int {
return a + b
}
通过提供具名参数,摆脱函数调用时传参位置的限制。
fun fn(a: Int, b: Int) {
println("$a + $b = ${a + b}")
}
fun main() {
fn(1,2)
fn(b=2,a=1)
}
当函数返回单个表达式时,可以使用如下的方式:
// 单表达式函数,返回类型可以自动推导
fun add(a: Int, b: Int) = a + b
vararg
修饰的参数称之为可变参数。可变参数只能有一个,且通常放在所有形参之后。
fun fn(a: Int,b: Int, vararg c: Int) {
var sum = a + b
println(c is IntArray) // true
c.forEach {
sum += it
}
println(sum)
}
fun main() {
fn(1,2)
fn(1,2,4,5)
}
通过展开符 *
将一个数组传给可变参数。
fun fn(vararg c: Int) {
var sum = 0
c.forEach {
sum += it
}
println(sum)
}
fun main() {
fn(1, 2)
// * 会将 IntArray 数组内容展开,每个元素是 Int 类型
// IntArray 数组使用 toTypedArray() 转为 Array 数组
fn(1, 2, *intArrayOf(1, 2, 3))
// Array 数组需要先转为 IntArray 数组,然后使用 *
val c: Array<Int> = arrayOf(1,2,3)
fn(1,2,*c.toIntArray())
}
infix
修饰的函数可以使用中缀表示法,这种方式在之前位运算时有所体现。
中缀函数需要满足以下要求:
infix fun Int.add(a: Int): Int {
// 调用的对象
return this + a
}
fun main() {
val a = 1
val b = 2
val c = a.add(b)
println(c)
val d = a add b
println(d)
}
语法如下:
([[参数名:]参数类型,...,[参数名:]参数类型])->返回类型
一个简单的示例:
fun fn() {
println("fn")
}
fun main() {
// ::fn 函数引用
val f:()->Unit = ::fn
f()
}
箭头表示法是右结合的。
(Int)->((Int)->Int) 等价于 (Int) -> (Int) -> Int
如果函数类型可空,需要在外层添加圆括号。
((Int)->Int)?
可以使用类型别名给函数类型起个别名。
typealias fn = (Int)->Int
通过 ::
获取函数引用。
fun fn() {
println("a")
}
fun main() {
// 此时 f 指向了函数 fn 的内存空间,f 可以像函数 fn 一样使用
val f = ::fn
f()
}
用于范围检查和空检查。
fun fn(msg: String?) {
println(msg?.uppercase())
println(msg?.lowercase())
println(msg?.length)
}
可以看出每次调用字符串的方法或属性都要进行非空判断,这样就很麻烦,可以使用 let
解决。let
接收一个函数,该函数将调用对象作为参数传入,并将 let
体中的最后一个表达式作为结果返回。
fun fn(msg: String?): String? {
return msg?.let {
println(it)
println(it.uppercase())
println(it.lowercase())
println(it.length)
it.uppercase()
}
}
与 let
功能类似,不同的是 run
接收的是调用对象扩展的无参且无返回的函数,run
块内部的 this
指向调用对象。
fun fn(msg: String?): String? {
return msg?.run {
println(this)
println(uppercase())
println(lowercase())
println(length)
uppercase()
}
}
with
是一个非扩展函数,可以简单地访问其参数的成员,同时在引用其成员时可以省略实例名。
在使用 let
时,还需要每次都写 it
,可以使用 with
来解决。
fun fn(msg: String?) {
msg?.let {
println(it)
with(it) {
// this 指向 it
println(this)
println(uppercase())
println(lowercase())
println(length)
}
}
}
在对象上执行代码块并返回对象本身,块内部的 this
指向对象本身,通常用于初始化对象。apply
接收的是调用对象扩展的无参且无返回的函数。
fun main(args: Array<String>) {
val arr = mutableListOf<Int>()
val t = arr.apply {
// this 指向调用 apply 的对象
println(this)
this.add(6)
// 调用对象方法可以省略 this
add(4)
add(2)
add(1)
}
println(arr)
println(arr === t) // true
}
与 apply
功能类似,不同的是 also
接收的是一个函数,该函数把调用对象作为参数传入。
fun main(args: Array<String>) {
val arr = mutableListOf<Int>()
val t = arr.also {
println(it === arr)
// it 不可以省略
it.add(6)
it.add(4)
it.add(2)
it.add(1)
}
println(arr)
println(arr === t) // true
}
==
使用 equals()
检测,表示结构相等;===
两个引用指向同一个对象,表示引用相等。
// 类 A
class A
fun main() {
val a = "abc hello"
val b = "abc hello"
println(a == b) // 翻译成 a?.equals(b) ?: b === null
println(a != b)
println("-".repeat(5))
val o1 = A()
val o2 = A()
println(o1 === o2) // false
println(o1 !== o2)
}
使用 class
声明,类默认是 public final
进行修饰的。
class Person {}
如果一个类没有类体,可以省略 {}
。
class Person
kotlin
不使用 new
关键字创建实例,像调用函数一样调用类就可以创建实例。
class Person{}
fun main() {
val p = Person()
}
紧跟类名使用 constructor
定义的构造是主构造。
class Person constructor(var name: String, var age: Int) {
}
如果主构造没有任何注解或可见性修饰符,则可以省略 constructor
。
class Person(var name: String, var age: age) {
}
// 此时不能省略
class PersonFactory private constructor(){
}
默认提供一个空参的主构造。
// class Person(){}
// 与上面的等价
class Person{
}
主构造不能包含代码,可以在 init
块中进行初始化工作。
// var 和 val 会向外部暴露该属性(提供 get 和 set 方法),不同的是 val 之后不能在修改
// 可以使用 private 进行修饰,这样属性就不会暴露
// class Rectangle(private var length:Double, var height:Double)
class Rectangle(var length:Double, var height:Double) {
init{
if (length < 0) {
length = 0.0
}
if (height < 0) {
height = 0.0
}
println("初始化...")
}
}
val rec = Rectangle(10.0, 4.0)
println("length:${rec.length},height:${rec.height}")
// 修改
rec.length = 9.0
init
可以有多个,根据在类中出现的顺序依次完成初始化。
class Person {
val beforeA = "初始化 beforeA".apply { println("before A") }
init {
println("A")
}
val beforeB = "初始化 beforeB".apply { println("before B") }
init {
println("B")
}
}
fun main() {
Person()
}
在类体中使用 constructor
定义的构造是次构造,可以有多个次构造,但每个次构造都将隐式或显示的调用(委托给)主构造。
class Person(var name: String, var age: Int) {
// 委托给主构造
constructor():this("", 0) {
println("次构造")
}
}
fun main() {
Person()
}
对于 JVM
,如果主构造上所有参数都有默认值,则编译器将会提供一个额外的无参构造。
class Person(var name: String = "", var age: Int = 0) {
}
fun main() {
Person()
Person("tom", 20)
}
一个类无主构造是指该类只提供次构造。
// 未提供 () 且有次构造,则此时无主构造
class Person{
// 次构造
constructor(name: String) {}
}
/*
// 有主构造
class Person() {
constructor(name: String): this(){}
}
*/
类、对象、接口、构造函数、方法(或函数)与属性及其 setter
都可以有可见性修饰符,getter
的可见性总是与属性的可见性相同。kotlin
有四种可见性修饰符:private
、protected
、internal
与 public
,默认的可见性是 public
。不同类型的声明作用域有不同的可见性修饰符。
函数、属性、类、对象与接口可以直接在包内的顶层声明。
package com.example.main
/*
public:默认可见性,声明随处可见
private:只在声明的文件内可见
internal:与声明所在的同一模块内随处可见
protected:不能用在顶层声明中
*/
// 这就是顶层声明
private val a = 1
internal var b = 2
var c = 3
// setter 方法只在当前文件可见
private set
// 错误
// protected var c = 3
private fun f() {}
internal class A{}
private interface B{}
private
:成员只在该类内部可见,外部类同样不能访问内部类的 private
成员,但是内部类可以访问外部类的 private
成员。
class A {
private var a = 1
inner class B {
private var b = 2
init {
// 可以访问
println(a)
}
}
init {
// 错误
// println(b)
}
}
fun main() {
// 错误,a 只在类 A 的内部可见
// A().a
}
protected
:成员只在该类内部和子类中可见。
// 如果使用了 protected,需要使用 open,不然 protected 作用等价于 private
open class A {
protected var a = 1
}
class B: A() {
init {
println(a)
}
}
fun main() {
// 错误
// A().a
B()
}
internal
:当前模块内都可见。
public
:随处可见。
/*
public: 默认
private:构造私有,只在本类可见
internal:同一模块可见
*/
class A private constructor() {
init {
println("1")
}
constructor(a: Int): this() {
println(a)
}
}
fun main() {
// 错误
// A()
A(2)
}
局部变量、函数与类不能有可见性修饰符。
与函数一样使用 ::
。类引用是 KClass
对象,参看反射。
class A {
init {
println("init...")
}
}
fun main() {
val B = ::A
val b = B()
}
如果类中的属性和方法使用 public
和 internal
修饰,可以获取它的引用。
class A {
var a: Int = 1
protected var b: Int = 2
private var c: Int = 3
internal var d: Int = 4
}
fun main() {
val a = A::a
println(a.isFinal)
// 错误
// A::b
// 错误
// A::c
val d = A::d
println(d.visibility)
}
var
声明的属性是可变的,且提供默认的 getter
和 setter
方法;val
声明的属性是不可变的,且只提供默认的 getter
方法。
class A {
var a: Int = 1
val b: Int = 2
}
fun main() {
val obj = A()
println(obj.a)
obj.a = 10
println(obj.a)
println(obj.b)
// 报错,未提供 setter 方法
// obj.b = 20
}
声明一个类的属性的完整语法如下:
var [: ] [= ]
[]
[]
或者
val [: ] [= ]
[]
一般类体中的属性声明需要提供初始值。
class A {
var a: Int = 1
// 报错,需要初始化
var b: Int
}
对于 val
声明的属性可以不需要初值,但需要 getter
函数。
class A {
val a: Int
// 该函数特殊,不需要指定返回类型
get() {
return 10
}
}
fun main() {
val obj = A()
println(obj.a)
}
对于 var
声明的属性,必须给出初值,无论是否给出 getter
和 setter
函数。
class A {
// 报错
var a: Int
get() {
return 10
}
}
通过 getter
和 setter
方法来限制属性的值。
class A(val a: Int, val b: Int) {
var c: Int = 0
get() {
return a * b
}
set(value) {
if (value > 0) {
println("ok")
} else {
println("no")
}
}
}
fun main() {
val obj = A(1,2)
println(obj.c)
obj.c = 10
}
在 getter
和 setter
中可以使用 field
(只能在这两个函数中使用)引用幕后字段(可以理解成属性初值所在的内存) 。
class A{
// a 的 getter 和 setter 默认提供的方式
var a: Int = 0
get() = field
set(value) {
// 不能使用 a = value 的方式,这样 a 只是调用了 set 方法,从而无限递归
// field 引用了初值的内存地址
field = value
}
var b: Int = 0
set(value) {
if (b < 0) {
field = 0
} else {
field = value
}
}
}
fun main() {
val obj = A()
println(obj.b)
obj.b = -2
println(obj.b)
}
幕后属性:帮助属性初始化和设置值,幕后属性使用 private
修饰,kotlin
不会为该属性提供 getter
和 setter
方法。
class A {
// 通常将对应的属性名前加上 _ 作为幕后属性名
private var _a = 1
// 此时 _a 充当 field,不需要给出初始值,否则报错
var a: Int
get() = _a
set(value) {
if (value < 0) {
_a = 0
} else {
_a = value
}
}
}
fun main() {
val obj = A()
println(obj.a)
obj.a = 10
println(obj.a)
}
使用 lateinit
可以延迟初始化属性。lateinit
的使用有以下的限制:
var
的属性。getter
和 setter
方法。Int
、Byte
、 Short
、 Long
、 Float
、 Double
、 Char
、 Boolean
类型的属性。class A {
lateinit var a: String
// 报错
// lateinit var b: String?
// 判断是否已经初始化
fun testAHasInitialized() = ::a.isInitialized
}
fun main() {
val obj = A()
// 报错,需要先初始化
// println(obj.a)
obj.a = "ok"
println(obj.a)
// 检测是否延迟初始化,需要 kotlin-reflect.jar
// build.gradle.kts 中 implementation(kotlin("reflect"))
// 类名::属性.isLateinit 的方式
println(A::a.isLateinit)
// 或者 实例名::属性.isLateinit 的方式
println(obj::c.isLateinit)
}
所有类都直接或间接继承一个超类 Any
,Any
中只有三个方法:equals()
、hashCode()
和 toString()
。
// 隐式继承 Any
class Person
如果一个类需要被继承,使用 open
修饰该类,被 open
修饰的类默认是 public
。
open class Person {}
使用 :
实现类的继承,我们称需要被继承的为父类,实现继承的类为子类,子类将继承父类中非 private
修饰的属性和方法。
// Person 为父类,Student 为子类
open class Person(var name: String, var age: Int)
// : 后面跟的是类的实例化语法
class Student:Person("",0) {
// 无次构造
}
fun main() {
Student()
}
如果子类没有主构造,那么次构造需要通过 super
来显示初始化其父类,或者次构造通过次构造间接初始化父类。
open class Person(var name: String="", var age: Int=0)
// 无主构造
class Student: Person {
// 显示 super
constructor(name: String): super(name){}
// 委托给次构造
constructor(name: String, age: Int): this(name) {}
}
如果父类的成员和方法需要被子类重写,则父类的属性和方法需要使用 open
修饰,子类的方法需要使用 override
修饰。
open class Animal(name: String, age: Int) {
open var version: String = "1.0"
open fun run() {
println("run...${version}")
}
}
class Dog: Animal("tom", 3) {
override var version: String = "2.0"
// 使用 final 修饰, 则该方法将无法再被其子类重写
final fun run() {
println("${name} run...${version}")
}
}
fun main() {
val d = Dog()
d.run()
}
在子类中使用 super
来访问父类的成员。
open class A(var a: Int, var b: Int) {
open fun c() {
println("$a + $b = ${a + b}")
}
}
class B() : A(1, 2) {
override fun c() {
println("super.a=${super.a},super.b=${super.b}")
super.c()
}
}
fun main(args: Array<String>) {
val b = B()
b.c()
}
kotlin
不支持多继承
open class A {
}
open class B {
}
// 错误
class C : A(), B() {
}
通过 super<类名>.
的方式获取指定父类的方法。
open class A() {
open fun c() {
println("A")
}
}
interface B {
fun c() {
println("B")
}
}
// C 继承 A 并实现接口 B
class C : A(), B {
override fun c() {
// 调用 A 类的 c 方法
super<A>.c()
super<B>.c()
println("C")
}
}
fun main(args: Array<String>) {
val c = C()
c.c()
}
使用 abstract
修饰的类叫抽象类,使用 abstract
修饰的方法叫抽象方法。
// 默认是 public 修饰
abstract class Animal {
// 抽象属性
abstract var version: Int
// 一般属性
var name = "Animal"
// 抽象方法,需要子类实现
abstract fun run()
// 可以添加一般方法
fun eat(food: String) {
println(food)
}
}
fun main() {
// 错误,抽象类不能实例化
// Animal()
}
抽象类可以有主构造和次构造。
abstract class A(a: String) {
constructor():this("") {
println("A 次构造")
}
}
class B:A() {
}
fun main() {
val b = B()
}
接口可以包含抽象方法和非抽象方法,与抽象类不同的是,接口无法保存状态。接口中的属性必须声明为抽象或提供访问器实现。
interface A {
// 只声明的方法是抽象方法
fun a()
// 非抽象方法默认是 open 修饰
fun b() {
println("非抽象方法")
}
// 可以有私有的非抽象方法,但不能有私有的抽象方法
private fun c() {
println("私有方法")
}
}
fun main() {
// 报错,接口不能实例化
// A()
}
接口不存在构造。
// 报错
interface A() {
}
一个类可以实现多个接口,多个接口用 ,
隔开;一个接口可以继承多个接口。
interface A {}
interface B {}
interface C:A,B{}
class D:A,B{}
接口中的属性要么是抽象的,要么提供访问器的实现,但是不能进行初始化。
interface A {
// 报错
// var name: String = "A"
// 对于 var 的属性,如果提供访问器,则 getter 和 setter 都要提供,同时不能使用 field
// 因为没有初始化,因此一般不提供访问器
var name: String
val version: Int
get() = 1
fun a()
fun b() {
println("接口 A 中的 b 方法")
}
}
一个类实现了一个或多个接口,则该类需要重写所有接口中的抽象属性和抽象方法。
interface A {
var name: String
val version: Int
get() = 1
fun a()
fun b() {
println("接口 A 中的 b 方法")
}
}
class B: A {
override var name: String = "name"
override val version: Int = 1
override fun a() {
super.b()
println()
}
}
只有一个抽象方法的接口称之为函数式接口或单一抽象方法(SAM
)接口,SAM
是 Single Abstract Method
的缩写。函数式接口可以有多个非抽象成员,但只能有一个抽象成员。
// 使用 fun 修饰接口
fun interface A {
fun a()
}
对于非函数式接口的实现有一定的麻烦。
interface A {
fun a()
}
fun main() {
val obj = object:A{
override fun a() {
println("这是一个接口的实现")
}
}
obj.a()
}
使用函数式接口可以将之简化。
fun interface A {
fun a()
}
fun main() {
val obj = A {
println("这是一个函数式接口的实现")
}
obj.a()
}
可以为类或接口扩展。
class A {
fun a() {
println("a...")
}
}
fun A.b() {
// 类的扩展方法提供了 this,该 this 是调用该方法的对象
println(this)
println("b...")
}
fun main() {
val obj = A()
obj.a()
obj.b()
}
子类可以拥有父类的扩展。
open class A {
fun a() {
println("a...")
}
}
fun A.b() {
println("b...")
println(this)
}
class B: A() {
}
fun main() {
val obj = B()
obj.a()
obj.b()
}
可以为可空类型扩展。
fun String?.fn() {
println(this?.length ?: 0)
}
fun main() {
var s: String? = "hello"
s.fn()
s = null
s.fn()
}
可以为属性进行扩展,但有着以下限制:
field
显示访问幕后字段。val
的属性必须提供 getter
方法,var
的属性必须提供 getter
与 setter
方法。class A(var a: Int, var b: Int) {
}
val A.sum: Int
get() = this.a + this.b
var A.c: Int
get() = a + b
set(value) {
a = value - b
}
fun main() {
val obj = A(1,2)
println(obj.sum) // 3
println(obj.c) // 3
obj.c = 10
println(obj.a) // 8
println(obj.b) // 2
println(obj.c) // 10
}
一般的类需要为属性提供 componentN()
(N
指的是解构的个数)方法,需要该方法使用 operator
进行修饰。
class Person(var name: String, var age: Int) {
operator fun component1(): String {
return this.name
}
operator fun component2(): Int {
return this.age
}
}
fun main() {
val p = Person("tom", 18)
// component1 对应 name,component2 对应 age
val (name,age) = p
println("name=${name},age=${age}")
}
可以使用 _
忽略解构中某个不需要的变量。
class Person(var name: String, var age: Int) {
operator fun component1(): String {
return this.name
}
operator fun component2(): Int {
return this.age
}
}
fun main() {
val p = Person("tom", 18)
val (_,age) = p
println("age=${age}")
}
语法格式如下:
object[:0-N个父类型]{}
对象表达式不能定义构造,但可以有初始化块;可以包含 inner
修饰的类,但不能包含嵌套类。
open class A(var a: Int, var b: Int) {}
fun main() {
val obj = object: A(1,2) {
init {
println("可以有初始化块")
}
inner class B {}
// 错误
// class C {}
}
println(obj.a)
println(obj.b)
}
语法格式如下:
object objectName [:0-N个父类型]{}
对象声明可以包含嵌套类,但不能包含 inner
修饰的类;不能定义在函数和方法内;不能有构造,但可以有初始化块。
interface A {
fun a()
}
object B: A {
init {
println("b...")
}
override fun a() {
println("a...")
}
class C {
init {
println("c...")
}
}
// 错误
// inner class D {}
}
fun main() {
B.a()
B.C()
}
在类中使用 companion
修饰的对象声明称之为伴生对象。一个类至多定义一个伴生对象,可以通过 类名.
的方式访问伴生对象的内容。
class A {
companion object B {
var a = "b"
fun c() {
println("c...")
}
}
}
fun main() {
println(A.B === A) // true
println(A.B)
println(A)
println(A.a)
println(A.B.a)
A.c()
}
从上面的例子可以看出伴生对象名称有无都可以,对于无名称的伴生对象可以使用 类名.Companion
的方式。
class A {
companion object {
var a = "Companion"
fun c() {
println("c...")
}
}
}
fun main() {
println(A.Companion === A) // true
println(A.Companion)
println(A)
println(A.a)
println(A.Companion.a)
A.c()
}
可以为伴生对象进行扩展。
class A {
companion object {
var a = "Companion"
fun c() {
println("c...")
}
}
}
// Companion 不能省略,省略是为类 A 的对象添加扩展
fun A.Companion.fn() {
println("伴生对象的扩展")
}
fun main() {
A.fn()
}
类可以嵌套在其他类中,通过 类名.
的方式访问嵌套类,嵌套类不能访问外部类的成员,只能访问外部类中的其他嵌套类。
class A {
var a = 1
init {
println("init...A")
}
class B {
init {
// 报错
// println(a)
println("init...B")
}
}
class C {
init {
B()
println("init...C")
}
}
}
fun main() {
val a = A()
// 实例 a 无法访问嵌套类
val c = A.C()
}
类可以嵌套接口,接口也可以嵌套接口,接口也可以嵌套类。
class A {
init {
println("init...A")
}
interface B {
fun fn()
}
}
interface C {
class D {
init {
println("init...D")
}
}
interface E {
fun fn()
}
}
fun main() {
val a = A()
val b = object: A.B {
override fun fn() {
println("类A中的接口B")
}
}
b.fn()
val d = C.D()
val e = object: C.E {
override fun fn() {
println("接口C中的接口E")
}
}
e.fn()
}
使用 inner
修饰的类,内部类可以访问外部类的成员,通过外部类的实例访问内部类。
class A {
var a = 1
init {
println("init...A")
}
class B {
init {
println("init...B")
}
}
// 内部类
inner class C {
init {
println(a)
B()
println("init...C")
}
}
}
fun main() {
val a = A()
// 只能通过实例访问内部类,A.C() 是错误的
val c = a.C()
}
使用 data
修饰的类叫数据类,该类默认提供了以下功能
所有属性的 getter
(对于 var
还提供 setter
) 方法。
equals()
和 hasCode()
方法。
toString()
方法。
copy()
方法。
提供所有属性的 component1()
、component2()
等。
data class Person(var name:String, var age: Int)
fun main() {
val (name,age) = Person("tom", 18)
println("name=${name},age=${age}")
val obj = Person("mike", 20)
println(obj)
}
密封的类与接口提供了对继承的更多控制。密封类的所有直接子类在编译时是已知的,任何其他子类都不能出现在定义密封类的模块之外,密封接口也是如此。
使用 sealed
修饰类或接口。
sealed class A {}
sealed interface B {}
密封类和密封接口不能使用 open
修饰。
/*
// 错误
sealed open class A {}
*/
sealed class A {}
sealed interface B {}
// 继承密封类 A
class C: A() {}
// 实现密封接口 B
class D: B {}
密封类是不能实例化的,但可以有构造。构造只有两种可见性修饰:protected
(默认)与 private
。
// 主构造默认 protected
sealed class A(a: Int) {
constructor(): this(0) {
}
private constructor(a: Int, b: Int): this(a) {}
}
fun main() {
// 错误
// A()
}
密封类可以有抽象方法和属性。
sealed class A {
abstract var a: Int
abstract fun f()
}
密封类和其子类只能在同一模块的同一包内,密封接口也是一样的。
// package one
sealed class A{}
//-----------------
// package two
// 错误
class B: A(){}
间接子类不受此限制,除非其直接子类使用类 sealed
。
// package one
sealed class A{}
sealed class B: A(){}
open class C: A(){}
//-------------
// package two
class D: C() {}
/*
// 错误
class E:B(){}
*/
使用密封类的好处是在使用 when
作为表达式时,不需要 else
子句。
sealed class A {}
class B: A(){}
class C: A(){}
fun fn(a: A) = when(a) {
is B -> println("B 类型")
is C -> println("C 类型")
}
fun main() {
fn(B())
}
使用 enum
修饰,有着以下特点:
kotlin.Enum
类,故不能再显示继承其他父类。open
,故枚举类没有子类。private
修饰,默认 private
。valueOf(value: String)
获取指定枚举值,使用 values()
获取所有枚举值组成的数组。一个简单的示例:
enum class Season {
// 如果实例之后没有其他代码 ; 可以不写
SPRING,SUMMER,AUTUMN,WINTER;
}
或者
enum class Season {
// 调用的是构造 SPRING()
SPRING,
SUMMER(),
AUTUMN,
WINTER;
}
使用有参构造,构造默认是 private
。
enum class Season(name: String = "春天") {
SPRING,
SUMMER("夏天"),
AUTUMN("秋天"),
// ; 必须
WINTER("冬天");
init {
println("init...$name")
}
}
可以有抽象方法和抽象属性。
enum class Season {
SPRING {
override val no: Int = 1
override fun fn() {
println("春天")
}
},
SUMMER() {
override val no: Int = 2
override fun fn() {
println("夏天")
}
},
AUTUMN {
override val no: Int = 3
override fun fn() {
println("秋天")
}
},
WINTER {
override val no: Int = 4
override fun fn() {
println("冬天")
}
};
abstract val no: Int
abstract fun fn()
}
fun main() {
val spring = Season.SPRING
spring.fn()
println(spring.no)
// 表示第一个实例
println(Season.WINTER.ordinal)
// 实例名
println(Season.WINTER.name)
}
可以实现接口。
interface Base {
fun fn()
}
enum class Season : Base {
// 调用的是构造 SPRING()
SPRING {
override fun fn() {
println("spring")
}
},
SUMMER {
override fun fn() {
println("summer")
}
},
AUTUMN {
override fun fn() {
println("autumn")
}
},
WINTER {
override fun fn() {
println("winter")
}
};
}
fun main() {
Season.AUTUMN.fn()
}
类在实现接口时,将实现委托给其他已经实现该接口的类。委托对象的成员只能访问其自身对接口成员实现。
interface A {
abstract var a: Int
abstract fun f()
}
class B : A {
override var a = 1
override fun f() {
println("a = $a")
}
}
//使用 by 实现委托,类 C 通过 a 实现为没有重写的成员的重写
// C 没有重写 f(),则使用 a 的重写 f()
class C(a: A) : A by a {
// B 中的 f 不会访问
override var a = 2
}
fun main() {
val b = B()
val c = C(b)
c.f() // a = 1
// C 中的 a 值
println(c.a)
}
var
属性需要实现 ReadWriteProperty
接口,T
是拥有委托属性的对象类型,V
是属性类型。
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
class A : ReadWriteProperty<B, Int> {
private var _v = 1
// operator 可以省略, thisRef 是属性所在的对象,property 是属性的引用
override operator fun getValue(thisRef: B, property: KProperty<*>): Int {
println("getValue")
return _v
}
// operator 可以省略,value 是新值
override operator fun setValue(thisRef: B, property: KProperty<*>, value: Int) {
println("setValue")
_v = value
}
}
class B {
var a: Int by A()
}
fun main() {
val b = B()
// 调用 getValue
println(b.a)
// 调用 setValue
b.a = 10
println(b.a)
}
val
属性需要实现 ReadOnlyProperty
接口,T
是拥有委托属性的对象类型,V
是属性类型。
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty
class A : ReadOnlyProperty<B, Int> {
private var _v = 1
override operator fun getValue(thisRef: B, property: KProperty<*>): Int {
println("getValue")
return _v
}
}
class B {
val a: Int by A()
}
fun main() {
val b = B()
println(b.a)
}
属性委托也可以不实现上述接口,但对于 val
的属性需要提供 getValue()
函数,对于 var
的属性还需要提供 setValue()
函数。
import kotlin.reflect.KProperty
class A {
private var _v = 1
operator fun getValue(b: B, property: KProperty<*>): Int {
return _v
}
operator fun setValue(b: B, property: KProperty<*>, value: Int) {
_v = value
}
}
class B {
var a: Int by A()
}
fun main() {
val b = B()
println(b.a)
b.a = 11
println(b.a)
}
T
拥有委托属性的对象类型可以为空。
import kotlin.reflect.KProperty
class A {
private var _v = 1
operator fun getValue(nothing: Nothing?, property: KProperty<*>): Int {
println("$nothing - $property")
return _v
}
operator fun setValue(nothing: Nothing?, property: KProperty<*>, v: Int) {
_v = v;
}
}
var a: Int by A()
fun main() {
println(a)
}
kotlin
提供了一个 lazy
函数,该函数接受一个 Lambda
表达式作为参数,并返回一个 Lazy
对象,该对象可作为实现延迟属性的委托,只能用于 val
属性。
val a: Int by lazy {
println("lazy...")
10
}
fun main() {
println(a)
}
默认情况,lazy
属性求值是同步锁的:该值只在一个线程中计算,但所有线程都会看到相同的值。如果需要多个线程同时执行,提供 LazyThreadSafetyMode.PUBLICATION
参数。
// LazyThreadSafetyMode.PUBLICATION 初始化将总是发生在与属性使用位于相同的线程
val a: Int by lazy(LazyThreadSafetyMode.PUBLICATION) {
println("lazy...")
1
}
fun main() {
println(a)
}
通过 kotlin
提供的 Delegates.observable()
实现属性的监听。该函数接受两个参数:第一个参数是初始值;第二个参数是修改时的处理程序。处理程序有三个参数:被赋值的属性、旧值与新值。
import kotlin.properties.Delegates
var a: Int by Delegates.observable(3) { property, oldValue, newValue ->
println("$property - $oldValue - $newValue")
}
fun main() {
println(a)
// 每次赋值都将调用处理程序
a = 2
}
通过 kotlin
提供的 Delegates.vetoable()
来决定是否允许赋值。
import kotlin.properties.Delegates
var a: Int by Delegates.vetoable(2) { property, oldValue, newValue ->
when (newValue) {
in 1..10 -> {
println("$property - $oldValue - $newValue")
// true 表示允许赋值
true
}
else -> {
println("值范围是 1-10")
false
}
}
}
fun main() {
println(a)
a = 2
println(a)
}
一个属性可以把它的 getter
与 setter
委托给另一个属性。这种委托对于顶层和类的属性(成员和扩展)都可用。
var a = 1
var b: Int by ::a
class A(var c: Int) {
var d: Int by ::c
}
fun main() {
println(b)
val obj = A(2)
println(obj.d)
}
可以使用映射实例自身作为委托来实现委托属性。
class A(map: Map<String, Int>) {
val a: Int by map
val b: Int by map
}
fun main() {
val obj = A(mapOf("a" to 2, "b" to 3))
println(obj.a)
println(obj.b)
}
Map
只适用于 val
属性,对于 var
可以使用 MutableMap
。
class A(map: MutableMap<String, Int>) {
var a: Int by map
var b: Int by map
}
fun main() {
val obj = A(mutableMapOf("a" to 2, "b" to 3))
println(obj.a)
println(obj.b)
}
元素是有序的。
fun main() {
// 读写
val a: MutableList<Int> = mutableListOf(1, 2, 3)
// 只读
val b: List<Int> = listOf(1, 2, 3)
a.add(4)
println(a)
println(b[0])
}
元素是唯一且无序的。
fun main() {
// 只读
val a: Set<Int> = setOf(1, 1, 2, 3)
// 读写
val b = mutableSetOf<Int>(1, 1, 2, 3)
println(a)
b.add(3)
println(b)
}
存储的是键值对,键是唯一的。
fun main() {
// 只读
val a = mapOf(Pair("name", "tom"), Pair("age", 18))
// to 是中缀函数,返回 Pair
val b = mutableMapOf<String, Any>("name" to "tom", "age" to 18)
println(a)
// 添加键值对
b["address"] = "earth"
println(b)
println(b["name"])
for ((k, v) in a) {
println("key=$k,value=$v")
}
b.forEach { (k, v) ->
println("key=$k,value=$v")
}
}
过滤元素,并返回一个新的集合。
fun main() {
val a = listOf(1, 2, 3)
val b = setOf<Int>(4, 5, 6)
val c = mapOf<Int, Int>(1 to 2, 2 to 3, 3 to 4)
// 返回 List
println(a.filter { it % 2 == 0 })
// 返回 List
println(b.filter { it % 2 == 1 })
// 返回 Map
println(c.filter { (k, v) -> (k + v) % 2 == 1 })
}
可以对元素进行新的映射。
fun main() {
val a = listOf(1, 2, 3)
val b = setOf<Int>(4, 5, 6)
val c = mapOf<Int, Int>(1 to 2, 2 to 3, 3 to 4)
// 返回 List
println(a.map { if (it % 2 == 0) it + 1 else it - 1 })
// 返回 List
println(b.map { if (it % 2 == 0) "${it - 1}" else "${it + 1}" })
// 返回 List
println(c.map { (k, v) -> "${k + v}" })
}
对元素进行收集。
fun main() {
val a = listOf(1, 2, 3)
println(a.reduce { acc, i ->
println("$acc,$i")
acc + i
})
}
与 reduce
不同的是,fold
提供了一个初始值。
fun main() {
val a = listOf(1, 2, 3)
println(a.fold(-1) { acc, i ->
println("$acc,$i")
acc + i
})
}
中缀函数,将两个集合进行配对。
fun main() {
val a = listOf(1, 2, 3)
val b = setOf<Int>(4, 5, 6)
val c = a zip b
println(c)
}
只要有一个满足条件。
fun main() {
val a = listOf(1, 2, 3)
val b = setOf<Int>(4, 5, 6)
val c = mapOf<Int, Int>(1 to 2, 2 to 3, 3 to 4)
// 返回 Boolean
println(a.any { it % 2 == 0 })
// 返回 Boolean
println(b.any { it % 2 == 0 })
// 返回 Boolean
println(c.any { (k, v) -> (k + v) % 2 == 0 })
}
所有都要满足条件。
fun main() {
val a = listOf(1, 2, 3)
val b = setOf<Int>(4, 5, 6)
val c = mapOf<Int, Int>(1 to 2, 2 to 3, 3 to 4)
// 返回 Boolean
println(a.all { it % 2 == 0 })
// 返回 Boolean
println(b.all { it % 2 == 0 })
// 返回 Boolean
println(c.all { (k, v) -> (k + v) % 2 == 0 })
}
没有一个满足条件,与 all
相反。
fun main() {
val a = listOf(1, 2, 3)
val b = setOf<Int>(4, 5, 6)
val c = mapOf<Int, Int>(1 to 2, 2 to 3, 3 to 4)
// 返回 Boolean
println(a.none { it % 2 == 0 })
// 返回 Boolean
println(b.none { it % 2 == 0 })
// 返回 Boolean
println(c.none { (k, v) -> (k + v) % 2 == 0 })
}
符合条件则返回元素,否则返回 null
。
返回第一个匹配条件的元素。
fun main() {
val a = listOf(1, 2, 3)
val b = setOf<Int>(4, 5, 6)
println(a.find { it % 2 == 1 })
println(b.find { it % 2 == 0 })
}
返回最后一个匹配条件的元素。
fun main() {
val a = listOf(1, 2, 3)
val b = setOf<Int>(4, 5, 6)
println(a.findLast { it % 2 == 1 })
println(b.findLast { it % 2 == 0 })
}
与 find
类似,不符合条件抛出 NoSuchElementException
异常。
与 find
类似。
fun main() {
val a = listOf(1, 2, 3)
val b = setOf<Int>(4, 5, 6)
val c = mapOf<Int, Int>(1 to 2, 2 to 3, 3 to 4)
// 返回 size
println(a.count())
// 指定条件的元素个数
println(b.count {
it % 2 == 0
})
println(c.count())
}
将元素进行关联,返回 Map
。
fun main() {
val a = listOf(1, 2, 3)
val b = setOf<Int>(4, 5, 6)
println(a.associateBy { "key:$it" })
println(b.associateBy({ "key:$it" }, { it + 1 }))
val c = mutableMapOf<String, Int>()
println(b.associateByTo(c) { "key:$it" })
val d = mutableMapOf<String, Int>()
println(b.associateByTo(d, { "key:$it" }, { it + 1 }))
}
根据条件分组,返回 Map
。
fun main() {
val a = listOf(1, 2, 3)
val b = setOf<Int>(4, 5, 6)
println(a.groupBy { if (it % 2 == 0) "偶" else "奇" })
println(b.groupBy({ if (it % 2 == 0) "偶" else "奇" }, { it + 2 }))
val c = mutableMapOf<String, MutableList<Int>>()
println(a.groupByTo(c) { if (it % 2 == 0) "偶" else "奇" })
val d = mutableMapOf<String, MutableList<Int>>()
println(a.groupByTo(d, { if (it % 2 == 0) "偶" else "奇" }) {
it + 2
})
// 返回 Grouping
val e = a.groupingBy { if (it % 2 == 0) "偶" else "奇" }
// 获取 key(奇或偶)
println(e.keyOf(1))
// 输出 a 中元素
e.sourceIterator().forEach(::println)
}
根据条件划分。
fun main() {
val a = listOf(1, 2, 3)
// Set 也可以
val (odd, even) = a.partition { it % 2 != 0 }
println(odd)
println(even)
}
减少集合嵌套的层数。
fun main() {
val a = listOf(1, 2, 3)
val b = listOf(4, 5, 6)
val c = listOf(listOf(a,b))
println(c)
println(c.flatMap {
it.map {
it
}
})
println(c.flatten())
}
所有异常类继承自 Throwable
。
使用 throw
抛出异常。
// Nothing 与函数结合表示永不返回,总是抛出异常
fun fn(): Nothing {
throw Exception("error")
}
fun main() {
fn()
}
通过 try-catch
来捕获异常。
fun main() {
var a = 1
try {
a = a / 0
} catch (e: ArithmeticException) {
println(e.message)
}
}
不管有无异常都将执行 finally
,该语句块是可选的。
fun main() {
var a = 1
try {
a = a / 0
} catch (e: ArithmeticException) {
println(e.message)
} finally {
println("over")
}
}
try
不能单独存在,catch
和 finally
至少存在一个。
fun main(args: Array<String>) {
var a = 1
try {
a = a / 0
} finally {
println("over")
}
// finally 仍会执行,但由于没有 catch,此句会因异常而不会执行
println("kk")
}
需要提供 kotlin-reflect.jar
。
dependencies {
implementation(kotlin("reflect"))
}