安卓开发

Kotlin总结分享

2018-08-23  本文已影响246人  YC_JS

集合创建

像创建一个数组一样初始化一个含有默认值的集合。避免了先创建,再赋值,这一点在java中是做不到的

//Java
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);

//Kotlin
val list = listOf(1,2,3)
val map = mapOf(1 to "one",2 to "two",3 to "three")

Kotlin 中双冒号 :: 使用

Kotlin 中 双冒号操作符 表示把一个方法当做一个参数,传递到另一个方法中进行使用,通俗的来讲就是引用一个方法。eg:

fun main(args: Array<String>) {
    println(lock("param1", "param2", ::getResult))
}

/**
 * @param str1 参数1
 * @param str2 参数2
 */
fun getResult(str1: String, str2: String): String = "result is {$str1 , $str2}"

/**
 * @param p1 参数1
 * @param p2 参数2
 * @param method 方法名称
 */
fun lock(p1: String, p2: String, method: (str1: String, str2: String) -> String): String {
    return method(p1, p2)
}

这里需要注意的是,lock 函数 的第三个参数传入 method 时,要确定参数个数、类型、返回值都和其形参一致。

输出结果:

result is {param1 , param2}

需要调用其他 Class 中的某一个方法时:

fun main(args: Array<String>) {
    var d = Test()
    println(lock("param1", "param2", d::getResult))
}

Class 的内部方法时调动方式为:

class Test1 {
    fun isOdd(x: Int) = x % 2 != 0

    fun test() {
        var list = listOf(1, 2, 3, 4, 5)
        println(list.filter(this::isOdd))
    }
}

一般情况,调用当前类的方法 this 都是可省略的

集合中的高阶函数

map

map通常用于集合的映射,例如:

val oldList = listOf(1, 2, 3, 4, 5)
val newList = ArrayList<Int>()

println("集合映射--传统写法")
oldList.forEach {
    val newElement = it * 2 + 3
    newList.add(newElement)
}

println("集合映射--Kotlin写法,使用map")
val newList1 = oldList.map {
    it * 2 + 3
}
flatMap

flatMap通常用于扁平化集合,就是把集合的集合扁平化成集合,例如:

println("扁平化集合")
val list = listOf(
        1..20,
        2..5,
        3..4
)
val newList3 = list.flatMap {
    it
}
newList3.forEach(::println)

flatMap结合map进行变换,例如:

println("扁平化集合+变换")
val newList4 = list.flatMap {
    it.map {
        "NO.$it"
    }
}
newList4.forEach(::println)
reduce

reduce通常用于求和,例如:

println("求和")
var sum = oldList.reduce { acc, i -> acc + i }
println(sum)

利用reduce求阶乘:

var res2 = (1..5).map(::factorial)
println(res2)

//求n的阶乘:n!=1×2×3×...×n
fun factorial(n: Int): Int {
    if (n == 0) {
        return 1
    } else {
        return (1..n).reduce { acc, i -> acc * i }
    }
}

fold

fold通常用于求和并且加上一个初始值,例如:

sum = (1..5).fold(5) { acc, i -> acc + i }
println(sum)

fold还可以进行变换,进行字符串的连接:

var res = (1..5).fold(StringBuilder()) { acc, i ->
    acc.append(i).append(",")
}
println(res)

//也可以这样写
var res1 = (1..5).joinToString(",")
println(res1)

filter

filter用于过滤,如果传入的表达式的值为true的时候就保留:

//表达式为true的时候保留元素
val newList5 = oldList.filter {
    it == 2 || it == 4
}
println(newList5)

包括filterIndexed 的用法也很类似:

oldList.filterIndexed { index, i ->
    index == 1
}

中缀函数

这val map = mapOf(1 to "one", 2 to "two")里的to就是一个infix function。其源码实现:
public infix fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that)

普通函数的使用方法都是通过对象.函数名实现的,在kotlin中为了方便我们的使用引入了另外一种函数类型——中缀函数。这种函数通过infix关键字定义,有且只有一个参数,通过放在对象之后直接使用,在函数名之后紧接着的是该函数传入的值。中间无需.或()等特殊符号链接。

两个关键要素:

//中缀函数
infix fun String.getChar(position: Int) = this.get(position)
val s = "Hello world"
val c = s getChar 1
//普通函数
fun String.getChar(position: Int) = this.get(position)
val s = "Hello world"
val c = s.getChar(1)

字符串比较

//普通使用字符串对比调用StringUtils.equals(strA, strB)
fun main(args: Array<string>) {
    val strA = "A"
    val strB = "B"
    if (StringUtils.equals(strA, strB)) {//这里对比字符串是了apache中的StringUtils
        println("str is the same")
    } else {
        println("str is the different")
    }
}
 
//利用中缀调用sameAs对比两个字符串
fun main(args: Array<string>) {
    val strA = "A"
    val strB = "B"
    if (strA sameAs strB) {//中缀调用 sameAs
        println("str is the same")
    } else {
        println("str is the different")
    }
}

判断一个元素是否在集合中

//普通调用集合contains方法判断元素是否在集合中
fun main(args: Array<string>) {
    val list = listOf(1, 3, 5, 7, 9)
    val element = 2
    if (list.contains(element)) {
        println("element: $element is into list")
    } else {
        println("element: $element is not into list")
    }
}
 
//利用中缀调用into判断元素是否在集合中
fun main(args: Array<string>) {
    val list = listOf(1, 3, 5, 7, 9)
    val element = 2
    if (element into list) {//中缀调用,这样的写法,会更加接近我们自然语言的表达,更容易理解
        println("element: $element is into list")
    } else {
        println("element: $element is not into list")
    }
}

顶层函数和属性

java ----- 万物皆为对象
kotlin ----- 打破了这种思维定式

Java中本着”万物皆为对象“的设计原理,所有的函数和属性都是依附于class来实现的,因此很多独立的工具方法我们不得不为其创建一个类来进行管理,也就是我们常说的工具类。这样的实现其实是有些奇怪的,但是在Kotlin编程里为我们解决了这样的烦恼。

- 顶层函数

我们可以在任何一个普通的.kt文件中声明我们想要的函数,并且我们不需要为该函数创建包装类,该函数就是函数本身,就像C语言一样,以函数为基本单位而不是类。

package com.dy.comm.kotlinapplication

fun log(message: String?) {
    Log.i("TAG", message)
}

最终会编译成Java字节码,所以实现代码其实是这样的:

package com.dy.comm.kotlinapplication

public class XXX() {
    public static void log(String message) {Log.i("TAG", message)}
}

优点:方便了编写代码

- 顶层属性

和顶层函数类似,可以用于存储一些固定常用量,eg:URL等。

Kotlin中的解构声明

把一个对象的成员解构成多个变量,称为解构声明

componentN是操作符(类似加减乘除的运算符),重载操作符必需要用operator修饰以允许使用!
解构声明componentN函数的定义如下:
    class User(val first: String, val second: String) {
        //componentN是操作符,重载它,必须添加operator修饰符
        operator fun component1(): String {
            return first
        }
        operator fun component2(): String {
            return second
        }
    }

    fun main(args: Array<String>) {
        //解构声明会创建多个变量,可独立使用
        val (f, s) = User("lioil", "win")
        println("$f, $s") //输出"lioil", "win"
    }

数据类(data class)

编译器会为数据类(data class)自动声明/定义componentN()函数,可直接用解构声明!    
    data class User(val name: String, val id: Int)

    fun main(args: Array<String>) {
        val u = User("lioil.win", 1)

        //传统用法
        println("${u.name}, ${u.id}")//输出: lioil.win, 1

        //解构声明
        val (n, i) = u
        println("$n, $i")//输出: lioil.win, 1

        //直接调用componentN函数
        println("${u.component1()}, ${u.component2()}")
    }

函数返回多个变量(Return Values)

如果需要一个函数返回多个变量,Kotlin最简洁的实现是声明一个数据类并返回其实例对象,
数据类(data class)自动声明/定义componentN()函数,无需我们定义!
    data class Result(val result: Int, val status: Status)

    fun deFun(...): Result {
        return Result(result, status)
    }

    //函数直接返回多个变量,非常方便使用
    val (result, status) = deFun(...)

for循环-解构声明

collection的元素类必须要声明component1()、component2()等函数
    for ((a, b) in collection) {
        print(a)  
        ...                
    }

映射Map-解构声明

在kotlin标准库(standard library)提供以下扩展:
    //iterator()用于map迭代遍历(循环)
    operator fun <K, V> Map<K, V>.iterator(): Iterator<Map.Entry<K, V>> = entrySet().iterator()

    //component1、component2用于解构Map.Entr对象,获取键值对(key,value)
    operator fun <K, V> Map.Entry<K, V>.component1() = getKey()
    operator fun <K, V> Map.Entry<K, V>.component2() = getValue()

Map解构声明的实例:
    fun main(args: Array<String>) {
        val map = hashMapOf<String, Int>()
        map.put("one", 1)
        map.put("two", 2)

        //(key, value) in map
        for ((key, value) in map) {
            println("key = $key, value = $value")
        }
    }

函数默认值

在Java中经常会因为某个函数需要传入的参数数量不同而需要写很多同名的函数,即方法的重载
在Kotlin的函数规则中,可以为每个参数设置一个默认值,在没有传入该参数时启用默认值

注意:可以省略的只有排在末尾的参数。

fun addUser(name: String = "无名", age: Int = 100, sex: Char = '男', id: Int = -1) {}
  addUser()
  addUser("张三")
  addUser("李四", 10)
  addUser("王五", 10, '男', 1)

如果使用命名参数就可以指定你要设置的任何参数。

addUser(age = 5, id = 10)
addUser("赵六",sex = '妖')

命名参数

命名参数可以在我们调用某个方法时显式的写出我们传入的参数名以及对应值,这样能提高我们代码的可阅读性。

建议多使用命名参数来增加代码的可读性

fun test(key:Int,value:String)
test(key = 1,value = "one")

既然借助编辑器已经能够实现优化阅读的效果,那为什么还要引入命名参数呢?鸡肋?

当使用函数默认值时,本来是只能通过参数顺序进行参数传入和缺省,但是有了命名参数后可以完全忽略参数位置,直接赋值给你想要的任何参数。

fun addUser(name: String = "无名", age: Int = 100, sex: Char = '男', id: Int = -1) {}
  addUser(age = 5, id = 10)
  addUser("赵六",sex = '妖')

赋值为空

Java中有一个判断是

if((str=bufferedReader.readLine())!= null)

这句话到了kotlin转不过来
最初我是这么写的

var str=bufferedReader.readLine()
while(str != null){
执行代码
}

可是读到bufferedReader.readLine()=null时报错了
java.lang.IllegalStateException: bufferedReader.readLine() must not be null

问题在哪呢?问题在str的声明上
改成

var str: String ?=null
str=bufferedReader.readLine()
while (str != null) {
buffer.append(str)
str = bufferedReader.readLine()
}

就不再报错了,因为在后一种声明方式中str允许为空了

return

在kotlin线程中return时会报错,正确的方式是
return@Runnable

领域特定语言(DSL)

DSL是什么

编程语言,比如Java、kotlin都属于编程语言,而DSL全称为“领域特定语言”。以下为两者的对比:

- 编程语言:有一系列足够完善的能力来解决几乎所有能被计算机解决的问题。
- DSL:专注在特定任务,或者说领域上,并放弃与该领域无关的功能。

可以看出,专门针对某一特殊功能所存在的语言并且仅能完成这部分功能的语言我们称之为领域特定语言。常见的有DSL有SQL和正则表达式。

内嵌DSL

内嵌DSL作为kotlin的一种新特性,可以方便我们的开发。那为什么要使用内嵌DSL而不是直接使用呢,以SQL为例。

这些语言为了更有效的完成它们的目标,通过会减少它们提供的功能,因此当你需要执行一个SQL语句的时候,不用声明一个类或者方法,每一个关键字就代表了需要执行的操作和类型,每种类型的操作都有自己独特的语法和针对特定任务的关键字集合。

并且往往DSL语言更趋向于声明式,和通用编程语言相反,大部分是命令。

这导致直接使用DSL有个缺点:它们很难与使用通用编程语言的宿主应用程序结合起来使用。简单来说就是语法不同,需要以字符串等其他形式传入,不便于及时纠错。

示例

Anko SQLite(SQL)

fun getUsers(db: ManagedSQLiteOpenHelper): List<User> = db.use {
    db.select("Users")
            .whereSimple("family_name = ?", "John")
            .doExec()
            .parseList(UserParser)
}

Anko Layouts(XML)

verticalLayout {
    val name = editText()
    button("Say Hello") {
        onClick { toast("Hello, ${name.text}!") }
    }
}
总结

kotlin用内嵌DSL的方式让一些特殊领域语言的编写变得更加容易和统一。当熟练后甚至可以不用再写XML文件来布局,不用再写难记又不方便调试的SQL语句。

其实现的原理也很简单,实际上就是普通的方法基于lambda表达式的形式完成高度简洁的代码风格。说白了就是以代码形式完成以上的功能,不过这些代码是经过kotlin深度封装和优化的。

上一篇下一篇

猜你喜欢

热点阅读