Kotlin学习笔记

数据类型与变量

常见数据类型

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

  1. 声明的变量必须能知道类型(即声明时赋值可以自动推断类型,声明时没有赋值的话则必须显示指定变量类型)
  2. 通常修饰的是变量

val

  1. 用于修饰常量
  2. 只有val修饰的变量,即使加上public,编译之后也是private

const val

  1. const只能由于修饰val修饰的变量
  2. 只能放在object中的属性,不能直接放在class中
  3. 所修饰的变量的值,必须在编译期就确定下来,所以只能是String和基本类型
  4. 用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;
}

函数特点

  1. 不可被重写
  2. 不可直接访问接收者类型的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++一样。

适用场景

  1. 适用于包含lambda参数的函数
  2. 不适用于没有参数,或只有普通参数的函数,因为性能提升并不大
  3. 不适用于代码量庞大的函数

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

相当于javaswitch 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 //报错。不能用于基础数据类型
}

gettersetter

在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,即限定范围不许超出字段的修饰符
}

访问修饰符

  1. public(默认):成员对所有其他代码可见
  2. private:成员只在声明它的文件中可见,且如果成员也是private,则只对它自身的类文件可见
  3. protected:在声明它的类及其子类可见
  4. 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 {}

执行顺序

  1. 父类的init
  2. 父类的构造函数(主构造函数或者被次构造函数调用的构造函数)
  3. 当前类的init
  4. 当前类的次构造函数

数据类(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)
}

数据类特点

  1. 主构造至少要有一个参数
  2. 主构造参数必须指明varval关键字
  3. 数据类不能是抽象、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

密封类特点

  1. 一个有特定数量子类的类,和枚举有点类似,不同的是,在枚举中,每个类型只有一个对象(实例);而在密封类中,同一个类可以拥有几个对象
  2. 密封类的所有子类都必须与密封类在同一文件中
  3. 密封类的子类的子类可以在其他文件中
  4. 密封类没有构造函数,不可以实例化,只能实例化其中的子类
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关键字

该关键字用于定义一个类并同时创建一个实例对象,可以有以下几个场景

  1. 静态内部类(对象声明):定义单例对象(单例推荐使用伴生对象的方式实现)
  2. 伴生对象:静态类
  3. 匿名内部类(对象表达式)

对象声明

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中,每个类都可以有一个伴生对象,它是该类的一个特殊对象实例,具有以下几个特点

  1. 伴生对象的成员可以像java静态成员一样在类级别上访问
  2. 通常用于创建类级别的静态成员、工厂方法、单例模式等
  3. 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也允许对操作符进行重载

操作符与对应函数名

  • 一元操作符
表达式实际调用函数
+aa.unaryPlus()
-aa.unaryMinus()
!aa.not()
  • 递增与递减
表达式实际调用函数
a++a.inc()
a--a.dec()
  • 二元操作符
表达式实际调用函数
a + ba.plus(b)
a - ba.minus(b)
a * ba.times(b)
a / ba.div(b)
a % ba.rem(b)
a..ba.rangeTo(b)
  • in操作符
表达式实际调用函数
a in bb.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] = ba.set(i, b)
a[i, j] = ba.set(i, j, b)
a[i_1, ……, i_n] = ba.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(小明 + 小红)
}
  • 17
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值