Kotlin学习笔记

  |     |   本文总阅读量:

项目未来可能需要使用kotlin开发,所以特此记录一下学习笔记,仅供参考,方便后期查询。已同步到GitHub上:KotlinTest

Kotlin 简介

kotlin 的目标是成为一门全栈语言,主要有以下的特点:

  • 已经成为Android的官方推荐语言
  • 百分百的和java兼容,两者可以相互转换
  • JSJVMNative多平台开发

数据类型

1. 基本类型

Boolean true/false
Double 64
Float  32
Long   64
Int    32
Short  32
Byte   8

val aChar = '0'
val bChar = '我'
val cChar = '\u000f'

Char类型的转义字符

\t          制表符
\b          光标后退一个字符
\n          回车
\r          光标回到行首
\'          单引号
\"          双引号
\\          反斜杠
\$          美元符号,Kotlin 支持美元符号开头的字符串模板

2. 基本类型的转换

不可隐式转换

val anInt: Int = 5
val aLong: Long = anInt.toLong()

必须得通过.to类型的方式进行数据的转换

字符串

  • 一串Char
  • 用双引号””引起来

    val aString: String = “Hello World!”

  • 字符串比较

    a == b 表示比较内容 类似 Java 中的 equals
    a === b 表示比较对象是否相同

  • 字符串模板

    println(“hello, $name”) -> “hello, 小明”

3. Koltin 中的类和对象初始化

类的定义

  • 类,一个抽象的概念
  • 具有某些特征的事物的概括
  • 不特定指代任何一个具体的事物

一般写法:

/**
* 其中类参数如果加上 var 修饰,那么他便是成员变量,反之则是普通的参数
*/
class Student(var name: String, var age: Int){
    init {
        // ... 相当于构造函数中的代码
    }
}

对象

  • 是一个具体的概念,与类相对
  • 描述某一个类的具体个体
  • 举例:

    某些人、领导的车等等

类和对象的关系

  • 一个类通常可以有很多歌具体的对象
  • 一个对象本质上只能从属一个类
  • 某一个人,他是工程师,但本质上还是属于人这一类

一般写法:

val student: Student = Student("xiaweizi", 23)

类的继承

  • 提取多个类的共性得到一个更为抽象的类,即父类
  • 子类拥有父类的一切特征
  • 子类也可以定义自己的特征
  • 所有的类最终继承自Any,类似于java中的Object

4. 空类型和智能转换

空类型

// 定义
val notNull: String = null // 错误,不可能为空
val nullanle: String? = null // 正确,可以为空
// 使用
notNull.length // 正确,不可能为空所以可以直接使用
nullable.length // 有可能为空,不能直接获取长度
// 要想获取长度,可以通过以下两者方式
nullable!!.length // 正确,强制认定 nullable 不可能为空,如果为空则会抛出空指针异常
nullable?.length // 正确,若 nullable 为空,则返回 null

智能类型转换

val child: Child = parent as Child // 类似于 Java 的类型转换,失败则抛出异常
val child: Child = parent as? Child // 如果转换失败,返回 null

编译器智能识别转换:

val parent: Parent = Child()
if (parent is Child) {
    // parent 直接调用子类方法,不需要再进行强制转换
}

val string: String = null
if (string != null) {
    // string.length 可以直接调用length 方法
}

5. 区间

一个数学上的概念,表示范围, ClosedRange的子类,IntRange最常用

基本用法:

0..100 --> [0, 100]
0 until 100 --> [0, 100)
i in 0..100 表示 i 是否在区间[0, 100]中

6. 数组

基本写法:

val ints: IntArray = IntArrayOf(1,2,3,5)
var charArray: CharArray = charArrayOf('a', 'b', 'c', 'd', 'e')
var stringArray: Array<String> = arrayOf("aa", "bb", "cc", "dd", "e")

基本操作:

print(charArray[index])
ints[0] = 2
ints.length
cahrArray.joinToString("") // 讲 char 数组转换成字符串
stringArray.slice(1..4) // 取出区间里的值

程序结构

1. 常亮和变量

常量

val a = 2
类似 Java 中的 final
不可被重复赋值
运行时常量:val x = getX()
编译期常量:const val x = 2

变量

var a = 2
a = 3 // 可以被再次赋值

类型推导

val string = "Hello" // 推导出 String 类型
val int = 5 // 推导出 Int 类型
var x = getString() + 5 // String 类型

2. 函数 Function

以特定功能组织起来的代码块

// 最简单的打印信息,无返回的方法
fun printMessage(message: String):Unit{
    println("$message")
}
// 拥有返回值得方法
fun sum(first: Int, second: Int):Int {
    return first + second
}
// 可以简化成:
fun sum(first: Int, second: Int) = first + second
// 或者更简单的匿名函数
val result = fun(first: Int, second: Int) = first + second

3. Lambda 表达式

其实又是匿名函数

一般形式

{传入参数 -> 函数体,最后一行是返回值}
// 例如
val sum = {first: Int, second: Int -> first + second}
val printMessage = {message: String -> println(message)}

类型标识

() -> Unit // 无参,返回值为 null
(Int) -> Int // 传入整型,返回一个整型
(String, (String) -> String) -> Boolean // 传入字符串、Lambda 表达式,返回Boolean

Lambda 表达式的简化

  • 函数参数调用时最后一个Lambda可以移出去
  • 函数参数只有一个Lambda,调用时小括号可以省略
  • Lambda只有一个参数可默认为it
  • 入参、返回值与形参一致的函数可以用函数引用方式作为实参传入

4. 成员变量和成员方法

成员变量的声明

// 第一种是在构造函数中声明
class Student(var age: Int, name: String){
    // age 是成员变量 name 是局部变量
}
// 第二种是在函数体内声明
var a = 0
    get() {
        field += 1
        return field
    }
    set(value) {
        println("set)
        field = value + 1
    }
// 可以进行对 get 和 set 方法的重新定义

// 属性的初始化尽量在构造方法中完成
// var 用 lateinit 延迟初始化, val 用 lazy

lateinit var sex: String
val person: Person by lazy {
    Person()
}

成员方法

在类中直接声明方法可以直接调用,包括lambda表达式

// 方法的声明
fun sum(a: Int, b: Int) = a + b
val sum1 = {a: Int, b: Int -> a + b}
// 方法的调用
println(person.sum(1,2))
println(person.sum1(3,5))

5. 运算符

java中运算符是不能重新定义重载的,只能按照原先的逻辑进行计算

Kotlin则可以重新定义运算符,使用operator关键字,举了例子:

// 定义一个用于计算复数的类
class Complex(var real: Double, var imaginary: Double) {
    operator fun plus(other: Complex): Complex{
        return Complex(real+other.real, imaginary+other.imaginary)
    }

    // 重新 toString 方法
    overrride fun toString(): String {
        return "$real + ${imaginary}i"
    }
}
// 使用
val complex1 = Complex(1, 2)
val complex2 = Complex(2, 3)
println(complex1 + complex2)
// 输出结果为
"3 + 5i"

关键就是这个方法,方法名必须是plus或者其他官方定义的运算符,参数有且仅有一个,类型自定义,返回值意识可以自定义的.

operator fun plus(other: Complex): Complex{
        return Complex(real+other.real, imaginary+other.imaginary)
}

6. 表达式

中缀表达式

通过infix关键字修复方法,那么就可以不用通过 对象.方法() 的方式调用,而是直接 对象 方法名 参数的方式调用。举了例子

class Student(var age: Int){
    infix fun big(student: Student): Boolean {
        return age > student.age
    }
}
// 如果没有 infix 的调用方式:
println(Student(23).big(Student)(12))
// 如果使用 infix 修饰的调用方式:
println(Student(23) big Student(12))

if表达式

直接来个例子

val a = 20
val b = 30
val flag: Int = if(a > b) a else b

When 表达式

加强版的 switch,支持任意类型, 支持纯粹表达式条件分支(类似if),举个栗子:

val a = 5
when(a) {
    is Int -> println("$a is Int")
    in 1..6 -> println("$a is in 1..6")
    !in 1..4 -> println("$a is not in 1..4")
    else -> {
        println("null")
    }
}

for循环

基本写法

for (element in elements)

while循环

基本写法

while() {
}
do {
} while()

跳过和终止循环

跳过当前循环用 continue
终止循环用 break

6. 异常捕获

同样也是表达式,可以用来赋值,举个例子

return try{
            x/y
        }
        catch(e: Exception) {
            0
        } finally {
            //...
        }

如果没有异常则返回x/y,否则返回0,finally中的代码无论如何还是要执行的。

7. 具名参数、变长参数和默认参数

具名参数:给函数的实参附上形参

fun sum(first: Int, second: Int) = first + second
sum(second = 2, first = 1)

变长参数:varary修饰,使用起来是和数组一样,某个参数可以接收多个值,可以不作为最后一个参数,如果传参时有歧义,需要使用具名参数。

fun hello(vararg ints: Int, string: String) = ints.forEach(println(it))
hello(1,3,4,5,string = "hello")
// 如果最后一个参数也是 Int
fun hello(varary ints: Int, anInt: Int)
// 创建数组
val arrayInt: IntArray = intArrayOf(1, 2, 3, 4)
hello(ints = *arrayInt, anInt = 2)

默认参数:就是给参数传入一个默认的值

fun hello(anInt: Int = 1, string: String)
hello(string = "aaa")

面向对象

1. 继承

继承语法要点:

  • 父类需要open才可以被继承
  • 父类方法、属性需要open才可以被覆写
  • 接口、接口方法、抽象类默认为open
  • 覆写父类(接口)成员需要override关键字

语法要点:

  • class A: B(), C, D
  • 继承类时实际上调用了父类的构造方法
  • 类只能单继承,接口可以多实现

接口代理:

一个类可以直接将自己的任务委托给接口的方法实现,举个例子:

interface Drive{
    fun drive()
}

interface Sing{
    fun sing()
}

class CarDrive: Drive{
    override fun drive() {
        println("我会开车呦")
    }
}

class LoveSing: Sing{
    override fun sing() {
        println("我会唱歌呦")
    }
}

class Manager(drive: Drive, sing: Sing): Drive by drive, Sing by sing

fun main(args: Array<String>) {
    val carDrive = CarDrive()
    val loveSing = LoveSing()
    val manager = Manager(carDrive, loveSing)
    manager.drive()
    manager.sing()
}

这样,manager不用做任何事情,完全交付给接口实现.

接口方法冲突:

接口方法可以有默认实现,通过super<父类名>.方法名

interface A{
    fun a() = 0
}

interface B{
    fun a() = 1
}

interface C{
    fun a() = 2
}

class D(var aInt: Int): A,B,C{
    override fun a(): Int {
        return when(aInt){
            in 1..10 ->{
                super<A>.a()
            }
            in 11..100 ->{
                 super<B>.a()
             }
            else -> {
                println("dd")
                super<C>.a()
            }
        }
    }
}

2. 类及成员的可见性

java类似,private、protected、public,其中internal代表的是模块内可见

3. Object

相当于Java中的单例模式,有以下特点

  • 只有一个实例的类
  • 不能自定义构造方法
  • 可以实现接口、继承父类
  • 本质上就是单例模式最基本的实现

    interface getDataSuccess{
        fun success()
    }
    
    abstract class getDataField{
        abstract fun failed()
    }
    
    object NetUtil: getDataField(), getDataSuccess{
        override fun success() {
            println("success")
        }
    
        override fun failed() {
            println("failed")
        }
    
        val state: Int = 0
        fun getData(): String = "请求成功"
    }
    

3. 伴生对象和静态成员

相当于java中的静态方法

  • 每个类可以对应一个伴生对象
  • 伴生对象的成员全局独一份
  • 如果java中想直接调用kotlin中的静态方法或者静态变量,可以考虑使用JvmField JvmStatic.

    open class Util private constructor(var anInt: Int) {
        companion object {
            @JvmStatic
            fun plus(first: Int, second: Int) = first + second
    
            fun copy(util: Util) = Util(util.anInt)
            @JvmField
            val tag = "tag"
        }
    }
    

4. 方法的重载

通过给方法的参数配置默认值,即可实现方法的重载,按理说,一切可以拥有默认值的方法重载才是合理的方法重载。

名称形同、参数不同,跟返回值没有关系

class OverLoadTest {
    @JvmOverLoads
    fun a(anInt: Int = 0, string: String="") = 1
}

val test = OverLoadTest()
test.a(1, "")
test.a()
test.a(anInt = 2)
test.a(string = "")

使用JvmOverLoads是为了方便Java中调用方法的重载.

5. 扩展方法

kotlin中的扩展方法,我认为相当于java中的代理模式,拿到被代理的对象,然后进行一系列的操作。

fun String.add(anInt: Int): String {
    var sb = StringBuilder()
    for (i in 0 until anInt) {
        sb.append(this)
    }
    return sb.toString()
}

operator fun String.times(anInt: Int): String {
    var sb = StringBuilder()
    for (i in 0 until anInt) {
        sb.append(this)
    }
    return sb.toString()
}

// 使用
var string = "xiaweizi"
println(string.add(5))
println(string * (3))

6. 属性代理

类似之前说的var anInt: Int by lazy{2},懒赋值就是使用的属性代理,来看个例子:

fun main(args: Array<String>) {
    val a: Int by DelegatesTest()
    println(a)

    var b: Int by DelegatesTest()
    b = 3
    println(b)
}

class DelegatesTest {
    private var anInt: Int? = null
    operator fun getValue(thisRef: Any?, property: KProperty<*>): Int {
        println("getValue")
        return anInt?:0
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Int): Unit {
        println("setValue")
        this.anInt = value
    }
}

val 对应 getValuevar对应getValue和setValue方法,这个时候声明的属性就全权交付给DelegatesTest类中的anInt代理,当anInt为空的时候返回0,否则返回anInt.

7. JavaBean

使用data修饰类,类似java中的javaBean,默认实现了set get toString等方法,并拥有componentN方法.

不过有个缺点就是,无法被继承,没有无参构造函数,可以通过安装allOpennoArg插件解决这个问题.

data class UserBean(var name: String, var age: Int)

val userBean: UserBean = UserBean("小芳", 23)
println(userBean.name)
println(userBean.toString())

println(userBean.component1())
println(userBean.component2())

val (name, age) = userBean
println("name: $name")
println("age: $age")

至于这种写法val (name, age) = userBean,是因为定义了component1的运算符

class Complex{
    operator fun component1() = "你好呀"
    operator fun component2() = 2
    operator fun component3() = 'a'
}

val complex = Complex()
val (a, b, c) = complex
println(a + b + c)

使用起来也是很简单的

8. 内部类

  • 定义在类内部的类
  • 与类成员有相似的访问控制
  • 默认是静态内部类,非静态用 inner 关键字
  • this@Outter this@Inner 的用法
  • 匿名内部类
    • 没有定义名字的内部类
    • 类名编译时生成,类似Outter$1.class
    • 可继承父类,实现多个接口,与Java注意区别

举个例子:

class Outer{
    var string: String = "outer"
    class Inner1{
        var string: String = "inner1"
        fun sum(first: Int, second: Int) = first + second
    }

    inner class Inner2{
        var string: String = "inner2"
        fun cha(first: Int, second: Int) = first - second
        fun getInnerField() = this.string
        fun getOuterField() = this@Outer.string
    }
}

fun main(args: Array<String>) {
    val inner1 = Outer.Inner1()
    val inner2 = Outer().Inner2()

    println(inner1.sum(1, 2))

    println(inner2.cha(2, 1))
    println(inner2.getInnerField())
    println(inner2.getOuterField())
}

匿名内部类:

val listener: onClickListener = object : Father(), Mother, onClickListener{
    override fun sing() {
        println("mother sing")
    }

    override fun teach() {
        println("father teach")
    }

    override fun onClick() {
        println("匿名内部类")
    }
}

使用Object实现匿名内部类

9. 枚举和密封类

枚举是对象可数,每个状态相当于每个对象,是可以传构造参数的

密封类时子类可数,在kotlin大于1.1子类只需要与密封类在同一个文件加,保护子类的位置

sealed class SealedClassTest{
    class sum(first: Int, seocnd: Int): SealedClassTest()
    class cha(first: Int, seocnd: Int): SealedClassTest()

    object Bean: SealedClassTest()
}

enum class HttpStatus(val anInt: Int){
    SUCCESS(0), FAILED(1), LOADING(2)
}

fun main(args: Array<String>) {
    val class1 = SealedClassTest.cha(1, 2)
    println(HttpStatus.SUCCESS)
}

高阶函数

1. 基本概念

  • 传入或者返回函数的函数
  • 函数引用 ::println
  • 带有Receiver的引用 pdfPrinter::println

有三种显示

// 1. 包级函数
intArray.forEach(::print)

// 2. 类.方法
intArray.forEach(Int::addOne)
fun Int.addOne(): Unit {
    println("addOne:$this")
}

// 3. 对象.方法
intArray.forEach(AddTwo()::addTwo)
class AddTwo {
    fun addTwo(anInt: Int): Unit {
        println("addTwo:$anInt")
    }
}

2. 常用的高阶函数

常用的高阶函数还是有很多的,会简单的使用例子即可:

// 遍历
fun forEachTest() {
    val strings: Array<String> = arrayOf("aa", "ee", "bb", "ll")

    strings.forEach { println(it) } // 遍历每一个值
    strings.forEachIndexed { index, s -> println("index:$index,String:$s") } // 遍历 下标和值一一对应

}

// 重新拷贝一个值
fun mapTest() {
    val strings: Array<String> = arrayOf("aa", "ee", "bb", "ll")
    var map = strings.map { "$it-test" }
    map.forEach { print("$it\t") }
}

// 将集合合体
fun flatMapTest() {
    val lists = listOf(1..10,
            2..11,
            3..12)

    var flatMap = lists.flatMap {
        it.map {
            "No.$it"
        }
    }
    flatMap.forEach(::println)
}

fun reduceTest() {
    val ints = listOf(2, 3, 4, 5)
    println(ints.reduce { acc, i ->
        acc + i
    })
}

// 字符串连接
fun foldTest(){
    val ints = listOf(2, 3, 4, 5)
    println(ints.fold(StringBuffer(), { acc, i -> acc.append("$i,") }))
    println(ints.joinToString(","))
}

fun filterTest() {
    val ints = listOf(1, 2, 3, 4, 5, 6)
    println(ints.filter { element -> element % 2 == 0 })
}

// 当值不是奇数就去,遇到偶数就停止了
fun takeWhileTest() {
    val ints = listOf(1, 3, 3, 4, 5, 6)
    println(ints.takeWhile { it % 2 != 0 })
}

fun letTest() {
    findPerson()?.let { (name, age) -> println("name:$name, age:$age") }
    findPerson()?.apply { println("name:$name, age:$age") }
    with(findPerson()!!) { println("name:$name, age:$age") }
}

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

fun findPerson(): Person? {
    return Person("aa", 23)
}

3. 复合函数

有点类似数据中的f(g(x))

fun main(args: Array<String>) {
    val add1 = {int: Int ->
        println("add1")
        int + 1}
    val add2 = {int : Int ->
        println("add2")
        int + 2}
    var add3 = add1 addThen (add2)
    println(add3(4))
}


infix fun <P1, P2, R> Function1<P1, P2>.addThen(function: Function1<P2, R>): Function1<P1, R> {
    return fun(p: P1): R{
        return function.invoke(this.invoke(p))
    }
}

4. Currying

简单来说就是多元函数变换成一元函数调用链式,举个简单的例子,这是优化之前:

fun log(tag: String, out: OutputStream, message: String){
    out.write("[$tag], $message".toByteArray())
}

优化之后

fun log(tag: String)
    = fun(out: OutputStream)
    = fun(message: String)
    = out.write("[$tag], $message".toByteArray())

5. 计算文件字符串个数的小例子

首先将字符串转换成字符串数组:

val map: HashMap<Char, Int> = HashMap()
var toCharArray = File("build.gradle").readText().toCharArray()

通过分组的方式,统计每个字符串的个数,并打印:

toCharArray.groupBy { it }.map { it.key to  it.value.size }.forEach { println(it) }

kotlinjava的混合开发

1. 基本的交互操作

属性读写

  • Kotlin自动识别 Java Getter/Setter
  • Java操作Kotlin属性通过Getter/Setter

空安全类型

  • Kotlin空安全类型的原理
  • 平台类型Platform Type
  • Java可以通过@Nullable、@NotNull

几类函数的调用

  • 包级函数:静态方法
  • 扩展方法:带Receiver的静态方法
  • 运算符重载:带Receiver的对应名称的静态方法

几个常用的注解

  • @JvmField:将属性编译为Java变量
  • @JvmStatic:将对象的方法编译成功Java静态方法
  • @JvmOverloads:默认参数生成重载方法
  • @JvmName:制定Kotlin文件编译后的类名

NoArg 和 AllOpen

  • NoArg为被标注的类生成无参构造
  • AllOpen为被标注的类去掉final,允许被继承

正则表达式

  • Raw字符串定义正则表达式
  • JavaPattern
  • KotlinRegex

举个例子:

val source = "Hello This my phone number: 010-12345678."
val pattern = """.*(\d{3}-\d{8}).*"""

Regex(pattern).findAll(source).toList().flatMap(MatchResult::groupValues).forEach(::print)

赏我 e(=2.72) 元咖啡钱吧,您的支持将鼓励我继续创作!



文章目录
  1. 1. Kotlin 简介
  2. 2. 数据类型
    1. 2.0.1. 1. 基本类型
    2. 2.0.2. 2. 基本类型的转换
    3. 2.0.3. 3. Koltin 中的类和对象初始化
    4. 2.0.4. 4. 空类型和智能转换
    5. 2.0.5. 5. 区间
    6. 2.0.6. 6. 数组
  • 3. 程序结构
    1. 3.0.1. 1. 常亮和变量
    2. 3.0.2. 2. 函数 Function
    3. 3.0.3. 3. Lambda 表达式
    4. 3.0.4. 4. 成员变量和成员方法
    5. 3.0.5. 5. 运算符
    6. 3.0.6. 6. 表达式
    7. 3.0.7. 6. 异常捕获
    8. 3.0.8. 7. 具名参数、变长参数和默认参数
  • 4. 面向对象
    1. 4.0.1. 1. 继承
    2. 4.0.2. 2. 类及成员的可见性
    3. 4.0.3. 3. Object
    4. 4.0.4. 3. 伴生对象和静态成员
    5. 4.0.5. 4. 方法的重载
    6. 4.0.6. 5. 扩展方法
    7. 4.0.7. 6. 属性代理
    8. 4.0.8. 7. JavaBean
    9. 4.0.9. 8. 内部类
    10. 4.0.10. 9. 枚举和密封类
  • 5. 高阶函数
    1. 5.0.1. 1. 基本概念
    2. 5.0.2. 2. 常用的高阶函数
    3. 5.0.3. 3. 复合函数
    4. 5.0.4. 4. Currying
    5. 5.0.5. 5. 计算文件字符串个数的小例子
  • 6. kotlin和java的混合开发
    1. 6.0.1. 1. 基本的交互操作
  • 您是第 位小伙伴 | 本站总访问量 | 已经写了 77.9k 字啦

    载入天数...载入时分秒...