kotlin语法总结2

参考:第一行代码第三版

目录

标准函数with,run,apply

静态方法

延迟初始化

密封类

扩展函数

 repeat函数和random函数

运算符重载

扩展函数 + 重载运算符

高阶函数

高阶函数的实现原理

内联函数

noinline

crossinline

高阶函数的应用

泛型

委托



标准函数with,run,apply

with:接受两个参数,第一个参数是对象,第二个参数是一个Lambda表达式,默认对第一个参数进行操作,并把最后一行作为返回值

 如下代码中,在{}中都是默认对StringBuilder进行操作

val fruitList = listOf("Apple", "banana", "Orange")
    val result = with(StringBuilder()) {
        append("Start eating fruits\n")
        for (fruit in fruitList) {
            append(fruit).append("\n")
        }
        append("finished")
        toString()
    }
    println(result)

run: 和with几乎一样

 val fruitList = listOf("Apple", "banana", "Orange")
    val result = StringBuilder().run {
        append("Start eating fruits\n")
        for (fruit in fruitList) {
            append(fruit).append("\n")
        }
        append("finished")
        toString()
    }
    println(result)

apply :与之前的差距在于,会返回对象本身,无法指定返回值

 val fruitList = listOf("Apple", "banana", "Orange")
    val result = StringBuilder().apply {
        append("Start eating fruits\n")
        for (fruit in fruitList) {
            append(fruit).append("\n")
        }
        append("finished")
    }
    println(result.toString())

静态方法

kotlin极度弱化了静态方法这个概念,提供了以下几种类似静态方法的方法

1.单例类

object Util {
    fun getInstance(){
        println("单例类")
    }
}

2.想让某一个方法变为静态方法,本质是伴生类中的实例方法,但作用和静态方法相同

class Util {
    companion object{
        fun func(){
            println("do something")
        }
    }
}

3.顶层方法

创建一个Kotlin的File类型的文件,写入一些方法,如

fun func1(){
    println("do something")
}

则被默认为是一个顶层方法,会被编译成静态方法

在kotlin中调用,直接调用方法名

func1()

在java中调用,开头必须是大写,且最后会加上Kt,如文件名为Helper.kt,在java中调用就变为下面

public class test {
    public static void main(String[] args) {
        HelperKt.func1();
    }
}

延迟初始化

lateinit关键字,使用这个就是告诉编译器说我会晚些对这个变量进行初始化,这样就不用一开始的时候将它赋值为null了

使用的风险是:如果忘记了对它进行初始化还是会报异常

 private lateinit var rvAdapter: RvAdapter;

另外如果不想对一个变量进行重复的初始化,可以这样写,这样在初始化之前会判断一下是否已经初始化了,如果初始化了就不执行

if (!::rvAdapter.isInitialized) {
            rvAdapter = RvAdapter(this, list)
        }

密封类

当我们写接口时,而在when语句中,必须加上else这条语句

interface Result
class Success(val msg: String) : Result
class Failure(val error: Exception) : Result

fun getResultMsg(result: Result) = when (result) {
    is Success -> result.msg
    is Failure -> result.error.message
    else -> throw IllegalArgumentException()
}

 而使用密封类后,就可以把else去掉了,而且不会漏掉一些条件分支

sealed class Result
class Success(val msg: String) : Result()
class Failure(val error: Exception) : Result()

fun getResultMsg(result: Result) = when (result) {
    is Success -> result.msg
    is Failure -> result.error.message
}

扩展函数

相当于在String类当中加入了一个统计字母个数的函数方法

fun String.lettersCount() :Int{
    var count = 0
    for (char in this){
        if (char.isLetter()){
            count ++
        }
    }
    return count
}

 然后直接调用就可以

fun main(){
    val count = "asd123".lettersCount()
    println(count)
}

 repeat函数和random函数

下面这个代码就是模拟王者荣耀中的水晶抽奖

repeat(30) {
        val n = (1..365).random()
        if (n <= 1) {
            println("恭喜你获奖!!! $n")
        } else {
            println("很遗憾,您没有获奖,就差一点就获奖了,继续加油呀$n")
        }
    }

运算符重载

+就对应了.plus函数,minus为-,times为*,div为/

class Money(val value: Int) {
    operator fun plus(money: Money): Money {
        val sum = value + money.value
        return Money(sum)
    }
    operator fun minus(money: Money):Money{
        val sum = value - money.value;
        return Money(sum)
    }
    operator fun times(money: Money):Money{
        var sum =  value * money.value
        return Money(sum)
    }
    operator fun div(money: Money):Money{
        var sum =  value / money.value
        return Money(sum)
    }
}

fun main() {
    val money = Money(4)
    val money1 = Money(2)
    val money2 = money + money1
    println(money2.value)
    val money3 = money - money1
    println(money3.value)
    val money4 = money * money1
    println(money4.value)
    val money5 = money / money1
    println(money5.value)
}

扩展函数 + 重载运算符

operator fun String.times(n:Int):String{
    val builder = StringBuilder()
    repeat(n){
        builder.append(this)
    }
    return builder.toString()
}
val s = "abc" * 3
    println(s)

顺便说一下,kotlin中已经提供了一个重复的方法

 val repeat = "abc".repeat((1..20).random())
    println(repeat)

高阶函数

如果一个函数接收另一个函数作为参数,或者返回值的类型为另一个函数,那么该函数就是高阶函数

fun num1AndNum2(num1: Int, num2: Int, func: (Int, Int) -> Int): Int {
    var result = func(num1, num2)
    return result
}

fun add(num1: Int,num2: Int): Int {
    return num1 + num2
}

fun main() {
    val num1AndNum2 = num1AndNum2(1, 2, ::add)
    println(num1AndNum2)
}

如上:num1Andnum2的第三个参数接收一个函数类型,所以我们去定义一个函数与其接收的参数类型相同,并传入,且函数引用的写法为::函数名

也可以用Lambda写法代替add函数

可以看到lambda的可以完整地表达一个函数的参数声明和返回值声明,最后一行代码会自动作为返回值

fun num1AndNum2(num1: Int, num2: Int, func: (Int, Int) -> Int): Int {
    var result = func(num1, num2)
    return result
}

fun main() {
    val num1AndNum2 = num1AndNum2(2,3) { n1, n2 ->
        n1 + n2
    }
    println(num1AndNum2)
}

如下:又定义了一个高阶函数,StringBuilder.表示这个函数类型是定义在StringBuilder类中的
这样传入的Lambda表达式就会自动拥有StringBuilder的上下文

这样build就实现了和上面讲得apply函数一样的功能,只不过目前只适用于StringBuilder这个类


fun StringBuilder.build(func: StringBuilder.() -> Unit): StringBuilder {
    func()
    return this
}

fun main() {
    val list = listOf("Apple", "Pear", "Peach", "Banana")
    val string = StringBuilder().build {
        append("fruits list : \n")
        for (fruit in list) {
            append("$fruit ")
        }
    }
    println(string)
}

高阶函数的实现原理

第三个参数就是一个接口,Lambda表达式转换为一个匿名类的实现方式,每调用一次Lambda表达式,就会创建一个新的匿名类实例,这会造成额外的内存和性能开销,这时Kotlin引入了内联函数

内联函数

只需要在高阶函数之前加上inline即可,内联的函数类型参数在编译的时候会被进行代码替换,这样就不存在运行时的开销了

//.()表示在StringBuilder作用域下的函数
inline fun StringBuilder.build(func: StringBuilder.() -> Unit): StringBuilder {
    func()
    return this
}

noinline

如果我们一个高阶函数中有多个函数类型的参数,那么如果我们给函数加上了inline关键字,那么kotlin编译器会自动将所有引用的Lambda表达式全部进行内联,这时我们如果不想让某一个函数被内联,就可以在前面加上noinline

inline fun inlineTest(block1: () -> Unit, noinline block2: () -> Unit) {
}

内联函数和非内联函数的区别:

1.内联的函数类型参数在编译的时候会被进行代码替换,因此它没有真正的参数类型,非内联的函数类型参数可以自由地传递给其他任何函数,而内联的函数类型参数只允许传递给另外一个内联函数,这就是它最大的局限性.

2.内联函数可以用return直接返回,即退出main,而非内联函数只能局部返回,即从函数退回到main函数

当非内联函数:

fun printString(str: String, func: (String) -> Unit) {
    func(str)
}

fun main() {
    println("main start")
    val str = ""
    printString(str){
        println("lambda start")
        if (it.isEmpty()) return@printString
        println(it)
        println("lambda end")
    }
    println("main end")
}

运行结果: 

当为内联函数

inline fun printString(str: String, func: (String) -> Unit) {
    func(str)
}

fun main() {
    println("main start")
    val str = ""
    printString(str){
        println("lambda start")
        if (it.isEmpty()) return
        println(it)
        println("lambda end")
    }
    println("main end")
}

 运行结果

从两次的结果来看,return可以直接返回main函数,这是因为lambda中的代码会被替代为函数中的实现,所以相当于在main写了实现方法

crossinline

有一种情况 : 当我们在内联的高阶函数中写Lambda函数时,需要在参数前加上crossinline,

如果不加的话,因为我们知道lambda表达式是一个匿名内部类,而匿名类只能对函数进行返回,无法对外层进行函数返回,所以内联函数的Lambda表达式中允许使用return关键字和高阶函数的匿名类中不允许使用return关键字造成了冲突,所以crossinline就像是一个保证,保证内联函数中Lambda表达式一定不会使用return关键字

inline fun printString(str: String, crossinline func: (String) -> Unit) {
    val runnable = Runnable {
        func(str)
    }
    runnable.run()
}

高阶函数的应用

1.简化SharePreferences

普通的SharePreferences的写法

val editor = getSharedPreferences("data", Context.MODE_PRIVATE).edit()
        editor.putString("key1", "value1")
        editor.putString("key2", "value2")
        editor.apply()

用高阶函数去写一个扩展函数

 fun SharedPreferences.build(block: SharedPreferences.Editor.() -> Unit){
        val editor = edit()
        editor.block()
        editor.apply()
    }

这个是高阶函数标准写法,如果之前对高阶函数的定义清楚了,这个其实也好理解,解释如下:

1.首先我们知道SharePreferences.edit()可以获取一个Editor对象
2.我们是通过Editor的实例editor来存储值
3.可以看到build是在SharePreferences的一个方法,所以在函数里面的上下文是SharedPreferences的
4.参数是一个SharePreferences.Editor类型的,所以传入putString这些返回值为Editor的方法

我们去使用它

getSharedPreferences("data", Context.MODE_PRIVATE).build {
            putString("key1","aaa")
            putInt("key2",123)
        }

解释:首先getSharePreferences这个方法是返回一个SharedPreferences,所以可以用build这个扩展函数,传入一些返回值为Editor的方法,然后最后apply已经在高阶函数中写过了,这里就不用写了

验证一些数据是否成功存储进去

 val sharedPreferences = getSharedPreferences("data", Context.MODE_PRIVATE)
        val string = sharedPreferences.getString("key1", "")
        Log.e(TAG, "initListener: " + string)

 可以看到数据成功获取到了

顺便一说,其实在KTX扩展库中已经有了这个简化写法

getSharedPreferences("data", Context.MODE_PRIVATE).edit {
            putString("key1","aaa")
            putInt("key2",123)
        }



2.简化ContentValues

在写SQLite数据库时,我们很容易想到这么写

  val values1 = ContentValues().apply {
                put("name", "Tian Memoir")
                put("author", "Tian")
                put("pages", 123)
                put("price", 199.99)
            }
db.insert("Book", null, values1)

但其实还可以更简单

我们知道kotlin有一个mapOf方法,里面的参数可以这么写

val mapOf = mapOf("key1" to "value1")

.这个里面的这个参数,其实是一个pair类型的数据

然后我们回到简化,我们先创建一个ContentValues.kt,里面一个顶层方法

fun cvOf(vararg pairs : Pair<String,Any?>) : ContentValues {
    val cv = ContentValues()
    for (pair in pairs) {
        val key = pair.first
        val value = pair.second
        when(value){
            is Int -> cv.put(key,value)
            is Short -> cv.put(key,value)
            is Byte -> cv.put(key,value)
            is ByteArray -> cv.put(key,value)
            is String -> cv.put(key,value)
            is Long -> cv.put(key,value)
            is Float -> cv.put(key,value)
            is Double -> cv.put(key,value)
            is Boolean -> cv.put(key,value)
            null -> cv.putNull(value)
        }
    }
    return cv
}

解释:参数中的vararg是一个可变的参数个数,也就是允许传入多个参数都为pair类型,然后因为我们的contentValues的key都为String,所以我们只需要对第二个类型做判断就可以了,最后返回值是一个ContentValues

接下里我们就可以使用了,顶层方法可以直接写方法名就可以

 val values3 = cvOf("name" to "aaa", "page" to 123, "price" to 19.99)
            db.insert("Book",null,values3)

当然我们也可以进一步简化一下cvOf

fun cvOf(vararg pairs : Pair<String,Any?>) = ContentValues().apply {
    for (pair in pairs) {
        val key = pair.first
        val value = pair.second
        when(value){
            is Int -> put(key,value)
            is Short -> put(key,value)
            is Byte -> put(key,value)
            is ByteArray -> put(key,value)
            is String -> put(key,value)
            is Long -> put(key,value)
            is Float -> put(key,value)
            is Double -> put(key,value)
            is Boolean -> put(key,value)
            null -> putNull(value)
        }
    }
}

而且其实KTX也给我们写好了

val values3 = contentValuesOf("name" to "aaa", "page" to 123, "price" to 19.99)
            db.insert("Book",null,values3)

泛型

1.定义泛型类:

class MyClass<T> {
        fun method(param: T) = param
    }

调用

 val param = MyClass<String>().method("123x")

 2.定义泛型方法

 class MyClass {
        fun <T> method(param: T) = param
    }

调用

val param = MyClass().method("123")

 还可以对泛型的类型进行限制

 class MyClass {
        fun <T : Number> method(param: T) = param
    }

 这样我们就可以自己去实现高阶函数中的apply了

fun <T> T.myapply(block :T.() -> Unit) : T{
        block()
        return this
    }

 调用

val stringBuilder = StringBuilder().myapply {
            append("123")
            append("456")
        }

这部分我们把高阶函数和泛型结合起来,就适用更多的场景了 

委托

委托是一种设计模式,操作对象自己不会去处理某段逻辑,而是会把工作委托给另外一个辅助对象去处理,分为类委托和委托属性

class MySet<T>(val helperSet: HashSet<T>) : Set<T> {
    override val size: Int
        get() = helperSet.size

    override fun contains(element: T): Boolean  = helperSet.contains(element)

    override fun containsAll(elements: Collection<T>): Boolean = helperSet.containsAll(elements)

    override fun isEmpty(): Boolean  = helperSet.isEmpty()
    
    override fun iterator(): Iterator<T>  = helperSet.iterator()
}

这里的helperSet就相当于一个辅助对象,我们所有的方法实现,都调用了辅助对象中的相应的方法

这个例子体现不了委托的好处,但是如果我们只是让大部分的方法实现辅助对象中的方法,少部分的方法由自己来重写,或者加入一些自己的方法,而且不需要我们自己去写,只需要一个by关键字即可

其中的两个方法我们自己去重写一下

class MySet<T>(val helperSet: HashSet<T>) : Set<T> by helperSet {
    fun hello() = println("Hello World")
    override fun isEmpty(): Boolean = true
}

然后去调用

val hashSet = HashSet<String>()
        hashSet.add("123")
        hashSet.add("436")
        val mySet = MySet<String>(hashSet)
        Log.e(TAG, "initView: " + mySet.hello())
        val empty = mySet.isEmpty()
        Log.e(TAG, "initView: $empty")

这里返回true是因为我们已经对方法进行了重写,所以一直为true

2.委托属性

将一个属性的具体实现委托给另一个类去完成

class MyClass {
    //委托属性,将p属性的具体实现委托给了Delegate类去实现
    var p by Delegate()
}

以上代码意味着将p属性的具体实现委托给了Delegate类去完成,当调用p属性的时候会自动调用Delegate类中的getValue方法

class Delegate {
    var propValue: Any? = null
   
    operator fun getValue(myClass: MyClass, prop: KProperty<*>): Any? {
        return propValue
    }

    operator fun setValue(myClass: MyClass, prop: KProperty<*>, value: Any?) {
        propValue = value
    }
}

在Delegate类中我们必须实现getValue和setValue方法,且都要使用operator这个关键字

其中的参数说明 :

第一个参数:该Delegate类的委托功能可以在什么类中使用

第二个参数:是一个属性操作类,用于获取各种属性的值(kotlin自带的),其中<*>是说我们不关心什么类型

用委托属性实现一个简单的lazy,创建一个Later.kt

class Later<T>(val block: () -> T) {
    var value: Any? = null
    operator fun getValue(any: Any?,prop:KProperty<*>) : T{
        if (value == null){
            value = block()
        }
        return value as T
    }
}

//定义为顶层方法
fun <T> later(block: () -> T) = Later(block)

这样就实现了当使用的时候才去实现它

val uriMatcher by later { 
    val matcher = UriMatcher(UriMatcher.NO_MATCH)
    matcher.addURI("com.example.databasetest.provider","book",0)
    matcher
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值