数据类型与变量
常见数据类型
Byte
- 类型:整形
- 范围:1字节(-128 ~ 127)
Short
- 类型:整形
- 范围:2字节(-32768 ~ 32767)
Int
- 类型:整形
- 范围:4字节(-2147483648 ~ 2147483647)
Long
- 类型:整形
- 范围:8字节(-9223372036854775807 ~ 9223372036854775807)
Float
- 类型:浮点型
- 精确度:小数点后6位
Double
- 类型:整形
- 精确度:小数点后15-16位
String
- 类型:字符串
- 字符串模板
var name = "张三"
var template = """他的名字叫${name}"""
显示类型声明
var i: Int = 1
var和val
var
- 声明的变量必须能知道类型(即声明时赋值可以自动推断类型,声明时没有赋值的话则必须显示指定变量类型)
- 通常修饰的是变量
val
- 用于修饰常量
- 只有val修饰的变量,即使加上public,编译之后也是private
const val
const
只能由于修饰val修饰的变量- 只能放在object中的属性,不能直接放在class中
- 所修饰的变量的值,必须在编译期就确定下来,所以只能是String和基本类型
- 用const val修饰的变量,默认就是public
const val a: String = "a" //top-level属性
class A {
object AA {
const val aa: String = "aa"
}
}
函数
语法
fun 函数名(参数名: 参数类型): 返回值类型 {
// 函数体
}
/* 示例 */
fun foo(param: String): Unit{} //Unit表示没有返回值
main函数
fun main(args: Array<String>){
println("Hello World!")
}
匿名函数
两种写法
函数没有名字,即为匿名函数,可以有两种写法
// 写法一:指定函数参数类型,编译器反推函数实例类型
val f1 = fun(a: Int, b: String): Int { return a + b.toInt()}
// 写法二:指定函数实例类型,编译器推导出函数参数类型
val f2: (Int, String) -> Int = fun (a, b): Int { //返回值类型需要指定,不可推导
return a + b.toInt();
}
_
与lambda写法
去掉fun关键字、返回值类型、形参类型,即为lambda
写法
// 写法一
val fun1 = {a: Int, b: String -> a + b.toInt()}
// 写法二
val fun2: (Int, String) -> Int = {a, b -> a + b.toInt()}
val fun3: (Int, String) -> Int = {_, b -> b.toInt()} //不需要的参数,可以使用_下划线占位
it
与简化写法
val f = {x: Int, y: (Int, Int) -> Int -> x + y(x, x)}
f(1, {x, y -> x + y}) //lambda的参数类型可以被推导,所以这里可以省略
f(1) {x, y -> x + y} //lambda如果是最后一个参数,可以写到小括号外边
val f = {b: (Int, Int) -> Int -> b(1, 1)}
f {a, b -> a + b} //lambda是唯一的参数时,可以省略小括号
val f = {b: (Int) -> Int -> b(1)} //b的参数的小括号不可省略
f {v -> v + 9}
f { it + 9} //lambda自身只有一个参数时,可以直接用it
扩展函数
类似于python
的动态函数
可以为一个不能修改的或来自第三方库中的类编写一个新的函数。
这个新增的函数就像那个原始类本来就有的函数一样,可以用普通的方法调用,这种机制的函数称为扩展函数
如下,为A扩展除了print
方法,供A的实例使用
fun main(args: Array<String>) {
fun A.println(msg: String) = this.apply {
kotlin.io.println(msg)
}
//简写
fun A.print(msg: String) {
kotlin.io.print(msg)
}
A().print("aaa") //aaa
}
class A //没有内容时,中括号可以省略
实现分析
被扩展了方法的类,称为接收者类型,而该类的实例对象,也就是图中的this
,称之为接收者对象
而实际上,对应生成的java代码,就是一个静态函数,其参数与返回值都是接收者类型
public static final 接收者类型 扩展函数名(接收者类型 param) {
//...
return param;
}
函数特点
- 不可被重写
- 不可直接访问接收者类型的
private
属性或函数
匿名扩展函数
//具名
fun Int.p(): Int {
return this + 9
}
fun main(args: Array<String>) {
println(5.p()) //14
demo() //10
}
fun demo() {
print(1.p())
}
//匿名
val f:Int.() -> Int = fun Int.(): Int {
return this + 9
}
fun main(args: Array<String>) {
println(f(5)) //14
demo() //10
}
fun demo() {
print(f(1))
}
内置扩展函数
let
/**
* block参数为T,使用T的属性方法时,需要通过T来调用,无法直接调用
**/
public inline fun <T, R> T.let(block: (T) -> R): R {
return block(this) //block的lambda代码块中的最后一行作为返回值
}
使用示例:对象属性批量判空操作
//未使用let函数时,需要对person的每个属性进行判空
data class Person(var name:String, var age:Int, var sex:String)
val p: () -> Person? = { null }
fun main(args: Array<String>) {
val person = p()
person?.name = "张三"
person?.age = 1
person?.sex = "男"
println(person) //null
}
//使用了let函数,直接对整个person进行判空
fun main(args: Array<String>) {
val person = p()
person?.let { p -> //也可以直接使用it
p.name = "张三"
p.age = 1
p.sex = "男"
}
println(person) //null
}
//如果是只读操作,还可以对person进行解构,直接获得属性(只读)
fun main(args: Array<String>) {
val person = p()
person?.let { (name, age, sex) ->
println(name.uppercase())
println(age)
println(sex)
}
}
also
/**
* block参数为T,使用T的属性方法时,需要通过T来调用,无法直接调用
* 对比let,返回值是调用者本身
**/
public inline fun <T> T.also(block: (T) -> Unit): T {
block(this)
return this //返回调用者本身
}
data class Person(var name:String = "", var age:Int = 0, var sex:String = "")
val p: () -> Person = { Person() }
fun main(args: Array<String>) {
val person = p()
println((person.also {
it.name = "张三"
it.age = 10
it.sex = "男"
}).name) //张三。返回person自身,可以继续链式调用
println(person) //Person(name=张三, age=10, sex=男)
}
run
/**
* 和let类似,不过函数类型参数block也是T的扩展,所以在block函数当中可以直接使用T的方法
**/
public inline fun <T, R> T.run(block: T.() -> R): R {
return block() //返回lambda结果
}
data class Person(var name:String, var age:Int, var sex:String)
val p: () -> Person? = { Person("张三", 5, "男") }
fun main(args: Array<String>) {
val person = p()
person?.run { println("姓名:$name\n年龄:$age\n性别:$sex") }
}
apply
/**
* 和also类似,不过函数类型参数block也是T的扩展,所以在block函数当中可以直接使用T的方法
* 和run相比,返回的是调用者本身
**/
public inline fun <T> T.apply(block: T.() -> Unit): T {
block()
return this //返回调用者自身
}
data class Person(var name:String, var age:Int, var sex:String)
val p: () -> Person? = { Person("张三", 5, "男") }
fun main(args: Array<String>) {
val person = p()
person?.apply { name = "李四" }?.run { println(name) } //李四。可以链式调用
}
高阶函数
将函数作为参数或返回值的函数:f(g(x))
函数引用
fun main(args: Array<String>) {
/**
* 1. 得到类A的print方法的引用
* 2. 实际上除了print自身的message参数,还会有个类自身实例的参数,且作为第一个参数
* 3. 调用时需要传递实例参数
*/
val ap: (A, Any) -> Unit = A::print
ap(A(), 5) //5
}
class A {
fun print(message: Any) {
kotlin.io.print(message)
}
}
fun main(args: Array<String>) {
/**
* isNotEmpty函数定义:
* @kotlin.internal.InlineOnly
* public inline fun CharSequence.isNotEmpty(): Boolean = length > 0
*
* filter函数定义:
* public inline fun <T> Array<out T>.filter(predicate: (T) -> Boolean): List<T> {
* return filterTo(ArrayList<T>(), predicate)
* }
*
* 问题:高阶函数filter的函数参数:参数为T,返回值为Boolean。但是isNotEmpty是没有参数的,如何就能匹配上?
* 原因:这里String::isNotEmpty用的是类名::函数名的方式,他是有一个默认的类实例参数的,也就是String的实例,即arg中的元素
*/
args.filter(String::isNotEmpty)
}
fun main(args: Array<String>) {
/**
* 编译报错
* 使用A::print的方式虽然有一个默认的参数,但是这个参数时A实例类型,而args传递的是String类型
*/
args.forEach(A::print)
/**
* 编译正常
*/
val Aargs: Array<A> = arrayOf(A())
Aargs.forEach(A::print) //1
/**
* 编译报错
* forEach的函数参数只接收一个参数,而A::print2是两个参数
*/
args.forEach(A::print2)
/**
* 编译正常
* 实例没有默认参数,只有一个String类型的mes参数
*/
val a = A()
args.forEach(a::print2)
}
class A {
fun print() {
kotlin.io.print(1)
}
fun print2(mes: String): Unit {
kotlin.io.print(mes)
}
}
内置高阶函数
forEach
//遍历
fun main(args: Array<String>) {
val arr = arrayOf(1, 2, 3)
arr.forEach(::print)
}
map
//映射
fun main(args: Array<String>) {
val arr = arrayOf(1, 2, 3)
val doubleArr = arr.map(Int::toDouble)
}
flatMap
//拍平
fun main(args: Array<String>) {
val arr = arrayOf(1..5, 6..10, 11..15)
val a = arr.flatMap { it.map{"No.$it"} }
a.forEach(::print) //No.1No.2No.3No.4No.5No.6No.7No.8No.9No.10No.11No.12No.13No.14No.15
}
flod
//含初始值的reduce,且不限制计算值与元素值的类型
fun main(args: Array<String>) {
val arr: Array<Int> = arrayOf(1, 2, 3)
val str = arr.fold(StringBuilder()) {acc, i -> acc.append(i).append(if (i == arr[arr.size - 1]) "" else ",")}
println(str) //1,2,3
}
- takeWhild:遇到第一个不符合条件的数据时停止
fun main(args: Array<String>) {
val arr: Array<Int> = arrayOf(1, 1, 1, 2, 3)
/**
* 获取奇数,如果遇到偶数则停止
*/
val a = arr.takeWhile { it % 2 == 1 }
println(a) //[1, 1, 1]
}
内联函数
kotlin中,用inline
关键字来声明函数为内联函数
编译特点
当一个函数被内联 inline 标注后,编译时,会把这个函数方法体中的所有代码移动到调用的地方
inline fun f() {
println("ffffffff")
}
fun main(args: Array<String>) {
println("start...")
f()
println("end...")
}
编译成java代码后
public final class MainKt {
public static final void f() {
int $i$f$f = 0;
String var1 = "ffffffff";
System.out.println(var1);
}
public static final void main(@NotNull String[] args) {
Intrinsics.checkNotNullParameter(args, "args");
String var1 = "start...";
System.out.println(var1);
int $i$f$f = false;
String var2 = "ffffffff";
System.out.println(var2);
var1 = "end...";
System.out.println(var1);
}
}
这样能带来一个优点,在程序编译与解释器中,程序都是从顶向下编译的,执行也是,如果你的程序不在一个模块中,调用的话,cpu需要做额外的工作,在寻址的时候就需要花费时间,所以,将代码放到调用处,可以一定程度上提升执行性能。
但是,也可能因此导致目标文件变大,所以,并非是每个inline
都会将代码移动到调用处,这点和c++
一样。
适用场景
- 适用于包含
lambda
参数的函数 - 不适用于没有参数,或只有普通参数的函数,因为性能提升并不大
- 不适用于代码量庞大的函数
return
在普通的非内联函数中,无法对lambda
函数进行return
fun f1(a: Int) {
println("ffffffff")
f2 {
println(it + a)
return //编译直接报错
}
println("FFFFFFFF")
}
fun f2(a: (Int) -> Unit) {
a(10)
}
可以通过@
方式进行return
,但是只是跳出lambda
,后面的代码依旧会继续执行
fun f1(a: Int) {
println("ffffffff")
f2 {
println(it + a)
return@f2
}
println("FFFFFFFF")
}
fun f2(a: (Int) -> Unit) {
a(10)
}
fun main(args: Array<String>) {
/**
* 输出:
* ffffffff
* 15
* FFFFFFFF
*/
f1(5)
}
而如果将f2
函数设置成内联函数,f2
中的代码编译时就会移动调用它的f1
中,这时就可以支持return
并跳出整个f1
函数了
fun f1(a: Int) {
println("ffffffff")
f2 {
println(it + a)
return
}
println("FFFFFFFF")
}
inline fun f2(a: (Int) -> Unit) {
a(10)
}
fun main(args: Array<String>) {
/**
* 输出:
* ffffffff
* 15
*/
f1(5)
}
noinline
内联函数的「函数参数」 不允许作为参数传递给非内联的函数
inline fun f1(a: (Int) -> Unit) {
f2(a) //编译直接报错
}
fun f2(a: (Int) -> Unit) {
a(10)
}
使用noinline
关键字,可以将内联函数的函数参数设为非内联,这样就能传递了
inline fun f1(noinline a: (Int) -> Unit, b: (Int) -> Unit) {
f2(a)
}
fun f2(a: (Int) -> Unit) {
a(10)
}
crossinline
由上可知,只有inline
的函数内部可以使用return
,普通函数或者noinline
中不允许,那如果普通函数中含有inline
呢,这时候普通函数是不知道inline
中是否有return
的,所以结果就是,普通函数中不允许有inline
函数,如下,编译直接报错
inline fun f1(b: () -> Unit) {
val f: () -> Unit = {
b() //编译报错
}
}
这时候,就可以用到crossinline
了,使用后编译通过,并且和inline
一样会将代码复制到调用处,不同的是,此时的内联函数b
将不允许有return
存在
inline fun f1(crossinline b: () -> Unit) {
val f: () -> Unit = {
b()
}
}
fun f2() {
f1 {
println(11111111)
return //编译报错
}
println("f2222222222")
}
内置内联函数
with
/**
* 两个参数:第一个是接收者,第二个是lambda内联函数
* 和run类似,函数类型参数block也是T的扩展,所以在block函数当中可以直接使用T的方法,不过不是扩展函数,无法进行判空
**/
public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
return receiver.block() //返回接收者调用结果,也就是Lambda返回值
}
data class Person(var name:String, var age:Int, var sex:String)
val p: () -> Person? = { null }
fun main(args: Array<String>) {
val person = p()
with(person) {
/**
* 1. with中可以直接使用属性,并进行读写操作
* 2. 需要判空,因为person可能为空
* 3. with使用this,而非it
*/
this?.sex = "男"
}
}
读取文件
fun main(args: Array<String>) {
val br = BufferedReader(FileReader("gradle.properties"))
with(br) { //br不可能为null
var line: String?
while(true) {
line = readLine()?:break
println(line)
}
close() //需要手动关闭
}
}
fun main(args: Array<String>) {
val br = BufferedReader(FileReader("gradle.properties"))
br.use { //use会自动close
var line: String?
while(true) {
line = it.readLine()?:break //注意是it.readLine()
println(line)
}
}
}
repeat
/**
* 重复执行action函数times次
**/
public inline fun repeat(times: Int, action: (Int) -> Unit) {
for (index in 0 until times) {
action(index)
}
}
data class Person(var name:String = "", var age:Int = 0, val order: Int)
fun main(args: Array<String>) {
val todayNewBaby = mutableListOf<Person>()
repeat(5) { todayNewBaby.add(Person(order = it + 1)) }
/*
[Person(name=, age=0, order=1),
Person(name=, age=0, order=2),
Person(name=, age=0, order=3),
Person(name=, age=0, order=4),
Person(name=, age=0, order=5)]
*/
println(todayNewBaby)
}
中缀函数
在不使用括号和点号的情况下调用函数,那么这种函数被称为中缀函数,kotlin中用infix
关键字来声明中缀函数
map(
1 to "one",
2 to "two",
3 to "three"
)
这里的 to 就是一个infix函数,看起来像是一个关键字,实际是一个to()方法
/**
* Creates a tuple of type [Pair] from this and [that].
*
* This can be useful for creating [Map] literals with less noise, for example:
* @sample samples.collections.Maps.Instantiation.mapFromPairs
*/
public infix fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that)
使用条件
- 函数必须是成员函数或扩展函数;
- 函数必须只有一个参数;
- 函数参数不得接受可变数量的参数且不能有默认值
- 进行复合的两个函数需要为函数引用,而非函数名
使用示例
fun main(args: Array<String>) {
A().test(9)
}
class A {
private infix fun add1(n: Int) { println(n + 1) }
fun test(n: Int) {
this add1 n //10 正确
add1(n) //10 正确
}
}
复合函数
如:f(g(x))
,这种函数嵌套函数的函数,即为复合函数
fun add1(i: Int): Int = i + 1
val mul5:(Int) -> Int = {it * 5}
fun main(args: Array<String>) {
println(add1(mul5(5))) //26
}
注意:中缀函数需要使用函数的引用,而非函数名
fun add1(i: Int): Int {return i + 1}
val mul5:(Int) -> Int = {it * 5}
/**
* infix:中缀函数
* fun<P1, P2, R>:指定泛型P1,P2,R
* Function1<P1, P2>.andThen:给Function1(入参P1,返回值P2)添加扩展函数addThen
* function: Function1<P2, R>:扩展函数addThen的入参也是个Function1
* Function1<P1, R>:扩展函数addThen的返回值也是个Function1
*/
infix fun<P1, P2, R> Function1<P1, P2>.andThen(function: Function1<P2, R>): Function1<P1, R> {
return fun(p1: P1): R {
return function.invoke(this.invoke(p1))
}
}
fun main(args: Array<String>) {
println(add1(mul5(5))) //26
/**
* 1. 相当于add1.andThen(mul5)
* 2. 需要使用函数的引用,而非函数名
*/
val add1AndMul5 = mul5 andThen ::add1
println(add1AndMul5(5)) //26
}
柯里化与偏函数
柯里化
指一个拥有多参数的函数转为多个单参数函数的函数链,这就是柯里化
柯理化前
fun desc(name:String, age:Int, sex:String) { println("[$name]: $sex - $age\n") } //调用 desc("张三", 15, "男") //[张三]: 男 - 15
柯里化
fun desc(name: String): (Int) -> (String) -> Unit { return { age -> { sex -> println("[$name]: $sex - $age\n") } } } //调用 desc("张三")(15)("男") //[张三]: 男 - 15
柯里化 · 简写
fun desc(name: String) = fun(age: Int) = fun(sex: String) { println("[$name]: $sex - $age\n") } //调用 desc("张三")(15)("男") //[张三]: 男 - 15
柯里化 · 扩展函数
fun desc(name: String, age: Int, sex: String) { println("[$name]: $sex - $age\n") } fun <P1, P2, P3, R> Function3<P1, P2, P3, R>.curried() = fun(p1: P1) = fun(p2: P2) = fun(p3: P3) = this(p1, p2, p3) fun main(args: Array<String>) { /** * 通过::desc获得desc函数的引用 */ ::desc.curried()("张三")(15)("男") //[张三]: 男 - 15 }
偏函数
对于一个多参数函数,通过指定了一些参数值后,依旧返回一个函数,那么这个函数就是原函数的一个偏函数
如,desc中的性别参数,可以设置默认值为"男"和"女"
fun desc(name: String, age: Int, sex: String) { println("[$name]: $sex - $age\n") } fun <P1, P2, P3, R> Function3<P1, P2, P3, R>.partial3Man(p3: P3) = fun(p1: P1) = fun (p2: P2) = this(p1, p2, p3) val descMan = ::desc.partial3Man("男") fun descWoman():(String) -> (Int) -> Unit { return ::desc.partial3Man("女") } fun main(args: Array<String>) { descMan("张三")(15) //[张三]: 男 - 15 descWoman()("李四")(21) //[李四]: 女 - 21 }
几个常用内置函数的对比
条件与分支
if else
同
java
三目表达式
类似python a if ... else b
if (...) a else b
when
相当于
java
的switch case
fun main(args: Array<String>) {
gender(1)
}
fun gender(flag: Int) {
when(flag) {
1 -> {
print("男")
}
0 -> {
print("女")
}
else -> {
print("?")
}
}
}
循环与遍历
for in
val nums = 1 .. 10 //[1-10]
for(num in nums) {
println(num)
}
val nums = 1 until 10 //[1-10)
for(num in nums step 2) { //step步长,默认1
println(num) //1 3 5 7 9
}
list和map
val li = listOf<String>("a", "b", "c")
for (e in li) print(e + " ") //a b c
for ((i, e) in li.withIndex()) {
println("$i $e")
/*
0 a
1 b
2 c
*/
}
val map = TreeMap<String, Any>()
map["name"] = "张三"
map["age"] = 24
val map2 = mapOf<String, String>("name" to "李四", "age" to "21")
Class
类声明
class A constructor() {}
/**
* 默认公共且不可继承的,相当于java的 public final class A {}
* 如果类中没有内容,可以简写为:class A
**/
A() //kotlin中无new关键字
lateinit
Kotlin中,类的属性都是需要初始化值,但使用lateinit关键字后可不用直接赋值
class A {
var a: Int = 0
lateinit var b: String
lateinit var c: Int //报错。不能用于基础数据类型
}
getter
和setter
在kotlin中,一个类的属性的完整定义语法为:
var <propertyName>[: <PropertyType>] [= <property_initializer>]
[<getter>]
[<setter>]
class Foo {
var bar: Int = 0 //编译成java代码后,为private
/**
* field:表示字段自身,即这里的bar
* 注意:val修饰的变量没有set方法, 也不允许重写set方法
*/
get() {return field} //默认实现,无需手动书写,编译成java后为public
set(value) {field = value} //默认实现,无需手动书写,编译成java后为public
}
class Bar{
private var bar: Int = 0
get() {return field} //默认实现,无需手写,编译成java后为private,且编译器不许手动指定为public,即限定范围不许超出字段的修饰符
set(value) {field = value} //默认实现,无需手写,编译成java后为private,且编译器不许手动指定为public,即限定范围不许超出字段的修饰符
}
访问修饰符
- public(默认):成员对所有其他代码可见
- private:成员只在声明它的文件中可见,且如果成员也是private,则只对它自身的类文件可见
- protected:在声明它的类及其子类可见
- internal:成员对本模块中其他代码可见
构造函数
主构造函数
class A constructor(name: String) {
init {
print(111)
}
init {
print(900)
}
/**
* 1. 有且只有一个主构造函数,主构造函数中不能包含任何代码,初始化代码可以放到init代码块中
* 2. 可以有多个init代码块,按照出现的顺序执行
* 3. 主构造函数式类定义的一部分,括号中可包含参数列表,且如果主构造没有其他修饰符,那么括号可以省略
**/
}
次构造函数
class CustomView: View {
constructor(context: Context) this(context, null, 0)
constructor(context: Context, attrs: AttributeSet?): this(context, attrs, 0)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int): super(context, attrs, defStyleAttr) {
initialize(context, attrs, defStyleAttr)
}
private fun initialize(context: Context, attrs: AttributeSet?, defStyleAttr: Int) {
//...
}
/**
* 1. 可以有0个或多个次构造函数
**/
}
- 错误示例
class CustomView(context: Context) : View(context) {
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0) {
initialize(context, attrs, 0)
}
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
initialize(context, attrs, defStyleAttr)
}
private fun initialize(context: Context, attrs: AttributeSet?, defStyleAttr: Int) {
//...
}
}
-
分析
在Kotlin中,如果存在主构造函数,那么所有的次构造函数必须通过this()
来调用主构造函数 -
修改后
class CustomView(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : View(context, attrs, defStyleAttr) {
constructor(context: Context) : this(context, null, 0)
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
init {
initialize(context, attrs, defStyleAttr)
}
private fun initialize(context: Context, attrs: AttributeSet?, defStyleAttr: Int) {
//...
}
}
@JvmOverloads
该注解可以将主构造函数中的参数生成所有可能的组合,以简化构造函数的使用
fun main(args: Array<String>) {
println(B(1).getA()) //可能组合1
B(1, "") //可能组合2
B(1, "", 0L) //可能组合3
}
open class A(a: Int, b:String?, c: Long) {
}
class B @JvmOverloads constructor(
a: Int,
b: String? = null,
c: Long = 0L
): A(a, b, c) {
private var aa: Int = a
fun getA(): Int {
return aa
}
}
继承
class A: B() {
override fun haha() {
println(111)
}
}
/**
* 1. 子类冒号父类括号
* 2. 如果父类有一个无参构造,或者所有参数都有默认值的构造时,可以省略括号
**/
默认父类
默认父类为Any
,它是Kotlin中的根类,所有Kotlin都直接或间接地继承了它,并且和java的Object
一样,定义了一些通用的方法,如equals, hashCode, toString
等
接口
class A: AA(), View.OnClickListener {
override fun aaFun() { //... }
override fun onClick(v: View?) {
when (v?.id) {
R.id.button -> { //... }
}
}
}
/**
* 1. 类冒号一个或多个接口(无需显示输入implements关键字)
* 2. 在Kotlin中,可以先写接口再写父类,也可以先写父类再写接口
**/
open
类默认是不可继承的,如果希望能够被继承,就可以使用open
关键字
open class A {}
执行顺序
- 父类的
init
块 - 父类的构造函数(主构造函数或者被次构造函数调用的构造函数)
- 当前类的
init
块 - 当前类的次构造函数
数据类(data class
)
data class A(var name:String)
data class A(var name:String, val sex:String = "female", var age: Int) //这里的female是默认值的意思,而非常量值
fun main(args: Array<String>) {
val a = A("zhangsan", "", 9)
//a.sex = "male" val类型不可修改
println(a) //A(name=zhangsan, sex=, age=9)
}
数据类特点
- 主构造至少要有一个参数
- 主构造参数必须指明
var
或val
关键字 - 数据类不能是抽象、open、sealed、inner的
copy
data class A(var name:String, val sex:String = "female", var age: Int) //这里的female是默认值的意思,而非常量值
fun main(args: Array<String>) {
val a1 = A("zhangsan", age = 9)
val a2 = a1.copy("lisi") //如果不指定参数名,默认按形参顺序
a1.age = 10 //深拷贝,a1的改变不影响a2
println(a2) //A(name=lisi, sex=female, age=9)
}
密封类(sealed class
)
密封类特点
- 一个有特定数量子类的类,和枚举有点类似,不同的是,在枚举中,每个类型只有一个对象(实例);而在密封类中,同一个类可以拥有几个对象
- 密封类的所有子类都必须与密封类在同一文件中
- 密封类的子类的子类可以在其他文件中
- 密封类没有构造函数,不可以实例化,只能实例化其中的子类
sealed class A {
class A1():A() {}
class A2():A() {}
val f = { println("hello ~") }
}
fun main(args: Array<String>) {
//val a = A() 编译报错,不可创建密封类的实例
val a = A.A1()
a.f() //hello ~
}
object
关键字
该关键字用于定义一个类并同时创建一个实例对象,可以有以下几个场景
- 静态内部类(对象声明):定义单例对象(单例推荐使用伴生对象的方式实现)
- 伴生对象:静态类
- 匿名内部类(对象表达式)
对象声明
object DemoClass: A { //可继承类或实现接口
fun getData(): Int { //可以有自己的方法
return this.data
}
private const val data = 1 //可以拥有自己的属性
}
class A {
/**
* 单例对象
*/
object SingleObject {
var num = 0
fun getNum():Int { return num + 1 }
}
}
伴生对象
和scala
一样,在kotlin
中,每个类都可以有一个伴生对象,它是该类的一个特殊对象实例,具有以下几个特点
- 伴生对象的成员可以像java静态成员一样在类级别上访问
- 通常用于创建类级别的静态成员、工厂方法、单例模式等
- kotlin中没有static关键字,可以通过伴生对象实现
- Java
public class A {
public static final String EMPTY = "";
public static boolean isEmpty(CharSequence cs) {
return null == cs || cs.length() == 0;
}
}
- Kotlin
class A {
companion object {
const val EMPTY = ""
fun isEmpty(cs: CharSequence): Boolean {
return cs.isNullOrEmpty()
}
}
}
对象表达式
指一个对象,继承父类、抽象类或实现接口的匿名类的对象
object[: 父类/抽象类/接口] {}
如果不需要继承或实现,可简写
fun foo() {
val aaa = object {
var x: Int = 0
var y: Int = 0
fun add(): Int {
return x + y
}
}
print(aaa.x)
}
内部类
嵌套类
在某个类中像普通类一样声明即可
class A {
private val a:Int = 1;
class A1 { //编译成java代码后,也是static静态内部类
fun f() {print(A().a)}
}
}
fun main(args: Array<String>) {
A.A1().f() //1
}
静态内部类
kotlin使用object
关键字指明静态内部类
class A {
private val a:Int = 1;
object class A1 {
fun f() {print(A().a)}
}
}
fun main(args: Array<String>) {
A.A1.f() //1
}
内部类
kotlin使用inner
关键字指明内部类
class A {
private val a:Int = 1;
inner class A1 {
/**
* 内部类会带有一个外部类的对象的引用(嵌套类和静态内部类都没有),可使用```this@[外部类名]```来持有外部类对象的引用
**/
fun f() {print(this@A.a)}
}
}
fun main(args: Array<String>) {
A().A1().f() //1
}
匿名内部类
kotlin使用object
关键字来表示匿名内部类
interface F {
fun ff(p: Int): Int
}
fun f(p1: F, p2: Int):Int { return p1.ff(p2) }
fun main(args: Array<String>) {
val r = f(object : F {
override fun ff(p: Int): Int {
return p * p
}
}, 5)
println(r) //25
}
其他
?,?:,!!
?
修饰在变量的类型后面,表示这个变量可以为null。该变量如果为null时,不会执行该变量后面的逻辑,也不会抛出空指针异常,俗称空安全;如果不为null,会正常执行该变量后面的内容。
var name: String?
var sex: String? = "男"
sex = "女"
fun demo1(): String { //编译器报错,String不允许返回null
return null
}
fun demo2(): String? { //编译器不报错,String?表示允许返回null
return null
}
?:
fun demo(): String? {
return null
}
fun main(args: Array<String>) {
val r = demo()?: "567" //?:表示为null的会执行?:后面的代码
val a = demo()?.length //?表示如果不为null的话继续.length的计算
}
!!
!! 加在变量后面,变量如果为null,会抛出空指针异常,像java语法一样空指针不安全;如果不为null,才会正常执行该变量后面的内容。
var name: String? = "abc"
var people: String = name!!
var a: String? = "a"
println(a.length) //编译器报错,因为a可以为null
println(a!!.length) //表示断定不可能为null,此时编译器通过,但是如果真的为null会报错
比较
- 比较两个对象的引用是否一致
===
- 比较两个对象的值是否一致
==
或者equals
,其中==
包含了空值的处理,其源代码为:
所以相当于public static boolean areEqual(Object first, Object second) { return first == null ? second == null : first.equals(second); }
first?.equals(second)?:second == null
,此外equals
有第二个参数,即是否忽略大小写var name1: String = "abc" var name2: String = "Abc" print(name1.equals(name2, true)) //true(可设置是否忽略大小写,默认false)
类型转换
val parent:Parent = Child()
if (parent is Child) {
parent.child方法() //无需进行强转
}
val parent:Parent = Parent()
// 报错
val child:Child = parent as Child //相当于java的强转:(Child)parent,这是会抛出类型转换异常
// 正常
val child:Child = parent as? Child //表示如果转换异常,则赋值null
print(child) //null
DSL
即领域特定语言,只在某些特定领域使用的语言,如Gradle、Html、MySql等
tailrec · 尾递归优化
什么是尾递归
有且只有一个递归,该递归位于代码最后一行,且除了递归外无其他任何操作,这样的递归称为尾递归
- 属于尾递归
data class ListNode(var value:Int, var nextNode:ListNode?)
//查找链表节点
fun findListNode(rootNode:ListNode?, value:Int):ListNode? {
rootNode?: return null
if (value == rootNode.value) return rootNode
return findListNode(rootNode.nextNode, value)
}
- 不属于尾递归示例
//计算阶乘
fun factorial(num: Long):Long {
return num * factorial(num - 1)
}
- 不属于尾递归示例
data class TreeNode(var value:Int) {
var left: TreeNode? = null
var right: TreeNode? = null
}
//查找树节点
fun findTreeNode(rootNode: TreeNode?, value: Int):TreeNode? {
rootNode?: return null
if (value == rootNode.value) return rootNode
return findTreeNode(rootNode.left, value)?: findTreeNode(rootNode.right, value)
}
尾递归优化
对尾递归进行优化:转为普通迭代
- 未优化前:java.lang.StackOverflowError
data class ListNode(var value:Int, var nextNode:ListNode? = null)
fun findListNode(rootNode:ListNode?, value:Int):ListNode? {
rootNode?: return null
if (value == rootNode.value) return rootNode
return findListNode(rootNode.nextNode, value)
}
fun main(args: Array<String>) {
val MAX_COUNT = 100000
val root = ListNode(0)
var p = root
repeat(MAX_COUNT) {
p.nextNode = ListNode(it + 1)
p = p.nextNode!!
}
val targetNode = findListNode(root, MAX_COUNT - 1) //获取倒数第二个节点
println(targetNode)
}
- 优化后:正常执行
data class ListNode(var value:Int, var nextNode:ListNode? = null)
tailrec fun findListNode(rootNode:ListNode?, value:Int):ListNode? {
rootNode?: return null
if (value == rootNode.value) return rootNode
return findListNode(rootNode.nextNode, value)
}
fun main(args: Array<String>) {
val MAX_COUNT = 100000
val root = ListNode(0)
var p = root
repeat(MAX_COUNT) {
p.nextNode = ListNode(it + 1)
p = p.nextNode!!
}
val targetNode = findListNode(root, MAX_COUNT - 1) //获取倒数第二个节点
println(targetNode) //ListNode(value=99999, nextNode=ListNode(value=100000, nextNode=null))
}
- 优化后的JAVA代码
@Nullable
public static final ListNode findListNode(@Nullable ListNode rootNode, int value) {
while(rootNode != null) {
if (value == rootNode.getValue()) {
return rootNode;
}
rootNode = rootNode.getNextNode();
}
return null;
}
闭包
示例一
fun foo():() -> Unit { var count = 0 return fun(){ println(++count) } } fun main(args: Array<String>) { val bar = foo() bar() //1 bar() //2 bar() //3 }
示例二:斐波那契
fun 斐波那契(): () -> Long { var first = 0L var second = 1L return { val result = second second += first first = result result } } fun main(args: Array<String>) { val f = 斐波那契() println(f()) //1 println(f()) //1 println(f()) //2 println(f()) //3 println(f()) //5 println(f()) //8 }
示例三:斐波那契
fun 斐波那契(): Iterable<Long> { var first = 0L var second = 1L return Iterable { object: LongIterator() { override fun nextLong(): Long { val result = second second += first first = result return result } override fun hasNext(): Boolean = true } } } fun main(args: Array<String>) { for (i in 斐波那契()) { if (i > 100L) break println(i) } }
委托
什么是委托
委托,即委托模式,亦称代理、代理模式
在委托模式中,有三个组成部分:接口/抽象类、委托对象、被委托对象
一句话概述就是:委托对象将自身对接口/抽象类的实现,交给被委托对象来完成
和java不同的是,kotlin进行了原生支持,并将其分为类委托和属性委托
接口委托
/**
* 接口:房子
*/
interface House {
fun rent()
}
/**
* 被委托对象:中介
*/
class Agency(private val tenant: String): House {
override fun rent() {
println("中介帮【$tenant】租赁了房子")
}
}
/**
* 委托对象:租户
* 使用by关键字,让agency实现自己的接口方法,实现委托关系
*/
class Tenant(agency: Agency): House by agency
fun main(args: Array<String>) {
val agency = Agency("张三")
Tenant(agency).rent() //中介帮【张三】租赁了房子
}
属性委托
什么是属性委托
参考类委托,在类委托中,被委托对象替委托对象实现接口/抽象类的方法。
属性委托则可以看做是类委托的一种特殊场景,需要被委托对象实现的接口/方法,就是属性的getter/setter方法。
委托接口
-
ReadWriteProperty
适用于var
修饰的属性,需要实现getter和setter方法 -
ReadOnlyProperty
适用于val
修饰的属性,只需要实现getter方法
委托语法
val/var <属性名>: <类型> by <委托代理类>
其中内置的委托代理类有:
- 映射委托(Map delegation):用map中对应的k-v对属性赋值
- 延迟属性(lazy properties):懒加载
- 可观察属性(observable properties):属性值的监听器
- 非空属性(Delegates.notNull):可用于基础数据类型的
lateinit
映射委托
val map: MutableMap<String, Any?> = mutableMapOf("a" to "aaa")
class A {
var a: String by map //map中的key必须包含属性名,否则会报错
}
fun main(args: Array<String>) {
println(A().a) //aaa
}
延迟属性
实际就是懒加载,即只在首次使用到该属性时才对该属性进行赋值,用于val
修饰的属性
class B {
val b: String by lazy {
println("hello")
"bbb"
}
}
fun main(args: Array<String>) {
val b = B()
println(b.b) //hello\nbbb
println(b.b) //bbb
}
lazy
是可以有参数的,是个枚举,共三个枚举值:
- LazyThreadSafetyMode.SYNCHRONIZED
添加同步锁,使lazy延迟初始化线程安全(默认) - LazyThreadSafetyMode. PUBLICATION
初始化的lambda表达式可以在同一时间被多次调用,但是只有第一个返回的值作为初始化的值。 - LazyThreadSafetyMode.NONE
没有同步锁,多线程访问时候,初始化的值是未知的,非线程安全,一般情况下,不推荐使用这种方式,除非你能保证初始化和属性始终在同一个线程
可观察属性
监听属性值的变化,用于var
修饰的属性
class C {
lateinit var a: String
var c: String by Delegates.observable("初始值") {
property, oldValue, newValue ->
println("property: $property") //property: property c (Kotlin reflection is not available)
println("oldValue: $oldValue") //oldValue: 初始值
println("newValue: $newValue") //newValue: ccc
}
//vetoable和observable类似,不过可以控制是否让值的变化生效
var d: Int by Delegates.vetoable(0){
_, oldValue, newValue ->
newValue > oldValue //如果新的值大于旧值,则生效
}
}
fun main(args: Array<String>) {
val c = C()
c.c = "ccc"
}
非空属性
和latainit
一样,适用于无法确定初始值的属性,不过它可以用于基础数据类型
class D {
var d: Int by Delegates.notNull<Int>()
}
fun main(args: Array<String>) {
val d = D()
//println(d.d) 必须赋值后才能get
d.d = 1
println(d.d) //1
}
操作符重载
和c++
一样,kotlin
也允许对操作符进行重载
操作符与对应函数名
- 一元操作符
表达式 | 实际调用函数 |
---|---|
+a | a.unaryPlus() |
-a | a.unaryMinus() |
!a | a.not() |
- 递增与递减
表达式 | 实际调用函数 |
---|---|
a++ | a.inc() |
a-- | a.dec() |
- 二元操作符
表达式 | 实际调用函数 |
---|---|
a + b | a.plus(b) |
a - b | a.minus(b) |
a * b | a.times(b) |
a / b | a.div(b) |
a % b | a.rem(b) |
a..b | a.rangeTo(b) |
- in操作符
表达式 | 实际调用函数 |
---|---|
a in b | b.constains(a) |
a !in b | !b.contains(a) |
- 索引访问操作符
表达式 | 实际调用函数 |
---|---|
a[i] | a.get(i) |
a[i, j] | a.get(i, j) |
a[i_1, ……, i_n] | a.get(i_1, ……, i_n) |
a[i] = b | a.set(i, b) |
a[i, j] = b | a.set(i, j, b) |
a[i_1, ……, i_n] = b | a.set(i_1, ……, i_n, b) |
- 调用操作符
表达式 | 实际调用函数 |
---|---|
a() | a.invoke() |
a(i) | a.invoke(i) |
a(i, j) | a.invoke(i, j) |
a(i_1, ……, i_n) | a.invoke(i_1, ……, i_n) |
代码示例
class Score(private var value:Int) {
/**
* 必须使用operator关键字,以及操作符对应的(固定的)函数名
*/
operator fun plus(score: Score): Int = value + score.value
}
fun main(args: Array<String>) {
val 小明 = Score(79)
val 小红 = Score(89)
println(小明 + 小红)
}