Code前端首页关于Code前端联系我们

Kotlin优雅的语法糖——打开新世界之门

terry 2年前 (2023-09-22) 阅读数 73 #移动小程序

Kotlin最大的亮点就是可以与Java无缝对接(虽然它的兄弟Groovy和Scala on Jvm也可以做到这一点) 。这意味着我们不仅拥有了原生Java的所有资源,还可以体验到类似js的编程体验(雾)。

另外,它的很多功能都对应了Java中的一个漏洞。本文从两个方面来介绍:语法糖新特性

  • 语法糖
    • 由类隐含,er,接口的默认实现
    • lambda 和高阶函数
    • 空指针安全,编译时空指针检查
    • 流式集合操作map()、forEach()
    • 新扩展函数,新函数属性扩展
    • 属性代理

环境简要配置

根文件夹/build.gradle:

// external 全局变量
ext{
    kotlinVersion = "1.0.0-rc-1036"
}

app/build.gradle

// kotlin
buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
    }
}

apply plugin: 'kotlin-android'

android {
    // 建立一个与'src/main/java'同级的kotlin工作目录
    sourceSets {
        main.java.srcDirs += 'src/main/kotlin'
    }
}

dependencies {
    compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
}

Kotlin 优雅语法糖 – 打开了新世界的大门

// kotlin
buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
    }
}

apply plugin: 'kotlin-android'

android {
    // 建立一个与'src/main/java'同级的kotlin工作目录
    sourceSets {
        main.java.srcDirs += 'src/main/kotlin'
    }
}

dependencies {
    compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
}

Kotlin 优雅语法糖 – 打开了新世界的大门

❙❙❙❝官方资料文档❝文档详细介绍

各下面的例子在(根文件夹/应用程序)

1 下附带源代码。语法 Sugar

1.1 简化类

1.1.1 定义类

让我们从一个简单的 JavaBean 开始。

// java bean
class People{
    private String name;
    
    public People(String name){
        this.name = name;
    }
    
    public void setName(String name){
        this.name = name;
    }
    
    public String getName(){
        return this.name;
    }
}

// kotlin bean
class People(val name: String){}

自从学习Java以来​​,我就接触到了javabean的概念。我不明白为什么需要像 getter() 和 setter() 这样的东西。是为了kpi吗?您可以看到相同的数据单元,在 kotlin 中简化为第 1 行。变量定义和构造函数被合并,同时提供了隐式 getter() 和 setter()。

1.1.2 继承

下面是一个简单的继承关系:

// 所有类默认final,要显式指定为open才可被继承
open class People(val name: String){}

// Code: People 表示继承关系
class Coder(name: String, val language: String = "Kotlin"): People(name){

    override fun toString(): String {
        return "name: $name, language: $language"
    }
}

// 可执行的main()方法
fun main(args: Array<String>) {
    val coder = Coder("Tom")

    println(coder)
}

// 输出: name: Tom, language: Kotlin

1.1.3 Data object数据类

当我看Effective Java时,我总是想知道如何有 equal (Code(s)正确),clone() 很棘手。然而,它们是正确使用 Collection(无论是 HashMap 还是 ArrayList)的关键。 Kotlin专门提供了一个数据类来自动生成hashCode()、equals()、clone()。

data class People(val name: String){}

1.2 接口的默认实现

顾名思义,这意味着接口可以作为抽象类拥有方法体的默认实现。我将其归因于语法糖,因为 Java 8 中已经有完全相同的东西,相应的关键字称为 default

看起来抽象类是可以撤销的,但事实并非如此。接口仍然只是接口,不能有属性,但在方法定义上更加灵活。

interface A {
    fun foo() { println("A") }    // 默认实现, 打印"A"
    fun bar()
}

interface B {
    fun foo() { println("B") }
    fun bar() { println("bar") }
}

// 多继承时,显式指定 super<A>.foo() 以去冲突
class D : A, B {
    override fun foo() {
        super<A>.foo()
        super<B>.foo()
    }

    override fun bar() {
        super.bar()
    }
}

1.3 lambda 和高阶函数

1.3.1 lambda

lambda 并不是什么新鲜事。它在gradle、js等其他语言中早已被玩坏了。它在 Java 8 中也可用,但我们对此无能为力,因为正式版本尚未发布。 Lambda本身就是一个函数片段。作为一等公民,它可以用作高阶函数的参数或返回值。就我个人而言,我喜欢从匿名类的角度来考虑它。

// new 一个线程
// 匿名类写法
val runnable1 = object : Runnable{
    override fun run() {
        println("I'm an anonymous class")
    }
}

// 函数写法, 略像js
val runnable2 = fun (){
    println("I'm a function")
}

// lambda写法1
val runnable3 = Runnable { ->
    println("I'm a Lambda")
}

// lambda写法2
val runnable4 = { println("I'm a Lambda") }
Thread(runnable4).start()

平时习惯使用lambda类型方法2,省略匿名类Runnable的名称,无参数时省略->箭头。不过js中的函数写法也是相当不错的。

1.3.2 高阶函数

lambda 本身就是一等公民,它有一个类型。如上例所示,runnable4的类型为()->Unit。再比如,下面的加法表达式的类型是 sum (Int, Int) -> Int

val sum: (Int, Int) -> Int = { x, y -> x+y }

变量有类型是很自然的。由于高阶函数的输入和返回值都是lambda,所以其类型有点奇怪是正常的。

fun main(args: Array<String>) {
    // 自定义高阶函数, lambda 表达式 作为入参
    listOf("1", "2", "3", "4").myForEach { println(it) }

    // 自定义高阶函数, lambda 表达式 作为返回值
//    getLogger()("I'm a Closure")
    var logger = getLogger()
    logger("I'm a Closure")
}

/**
 * 接受一个 lambda 表达式, 作为遍历任务
 */
fun <T> List<T>.myForEach(doTask: (T) -> Unit){
    for(item in this)
        doTask(item)
}

/**
 * 返回一个 lambda 表达式(闭包), 如: 日志输出工具 logger
 */
fun getLogger(): (String) -> Unit{
//    return { println(it) }
    return fun (it: String){
        println(it)
    }
}

PS:当你看到getLogger()的使用时,你可能意识到你可以把闭包写成js。

1.4 空指针安全

你可能会觉得空指针不是一个很简单的东西,只需添加一个 if (xxx != null) 语句即可。事实上,空指针是迄今为止最常见、最烦人的问题。而空安全性是kotlin的主要特性之一。在java8中我们很难使用Optional来做到这一点。

让我们看看会发生什么。

var mNullable: String? = null
var mNonNull: String = "mNonNull"

fun testNull(){
    println("testNull: ")
    println(mNullable?.length)
    println(mNonNull.length)
    println()
}

// 输出:
testNull: 
null
8

Kotlin 在定义变量时区分两种类型:

  • var mNullable: Any? = null 可空
  • var mNonNull:任何 = XXX 不可空

1。对于 mNullable,您可以像普通的 java 类一样使用它。相同用途:

// java 风格,判空
if(mNullable != null)
    mNullable.length
    
// kotlin 语法糖,判空(推荐)
mNullable?.length

2。 mNonNull 有严格的限制:

// 不必判空,因为必然非空
mNonNull.length
    
// 编译错误(试图给非空值赋予null)
mNonNull = null
// 编译错误(试图给非空值赋予可空值)
mNonNull = mNullable

我们可以体会到 mNonNull 永远不会是空指针的天然优势。还有 mNullable 加号?判断也能达到同样的效果。

1.5 流式集合操作map()、forEach()

我不知道该怎么称呼它,姑且称之为流式集合运算符吧。它很常见,可以在任何语言中找到。但如果不支持函数表达式,输入起来就会显得臃肿。下面的例子中,一些运算符的串联使得运算逻辑非常清晰。如果后面查询发生变化,比如降序改为升序,只需要用.sortedDescending()改一行即可,非常灵活。

fun main(args: Array<String>){
    val list = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
    list.filter { it%2==0 }             // 取偶数
            .map{ it*it }               // 平方
            .sortedDescending()         // 降序排序
            .take(3)                    // 取前 3 个
            .forEach { println(it) }    // 遍历, 打印
}

// 输出:
100
64
36

2。新功能

2.1 扩展

该项目的扩展似乎是在装饰模式下完成的。其作用是在不改变源代码的情况下添加功能。例如,如果我们想给Activity添加一个toast(),就不需要卸载基类。这减轻了很多工作,特别是对于一些已经打包在jar中的类。

fun Activity.toast(msg: String) {
    Toast.makeText(this, msg, Toast.LENGTH_SHORT).show()
}

其他例子:

/**
 * Created by apple on 17-5-31.
 *
 * 函数拓展, 属性拓展
 */
fun main(args: Array<String>) {
    val list = listOf("1", "2", "3", "4")

    // 函数拓展
    list.myForEach { println(it) }

    // 属性拓展
    println("last: ${list.lastItem}")
}

/**
 * 拓展 List 类, 加一个自定义的遍历方法
 */
fun <T> List<T>.myForEach(doTask: (T) -> Unit){
    for(item in this)
        doTask(item)
}

/**
 * 拓展 List 类, 加一个自定义的长度属性
 */
val <T> List<T>.lastItem: T
        get() = get(size - 1)

// 输出:
1
2
3
4
last: 4

2.2 属性代理

这个东西是用来做什么的?它将属性的 get() 和 set() 委托给一个类,以便它可以在 get() 和 set() 下执行一些附加操作。例如:

  • 延迟加载
  • 观察者(属性更改时自动通知)
  • 属性非零评级
  • ...

可能需要延迟加载作为示例。我们将其代理给lazy。可以看到,只计算第一个负载,直接取后续值,提高了效率。

val lazySum: Int by lazy {
    println("begin compute lazySum ...")
    var sum = 0
    for (i in 0..100)
        sum += i
    println("lazySum computed!\n")
    sum // 返回计算结果
}

fun main(args: Array<String>) {
    println(lazySum)
    println(lazySum)
}

// 输出:
begin compute lazySum ...
lazySum computed!

5050
5050

此外,我们还可以自定义属性代理。之前提到的Kotterknife就是一个很好的例子(bindview实现):

public class PersonView(context: Context, attrs: AttributeSet?) : LinearLayout(context, attrs) {
  val firstName: TextView by bindView(R.id.first_name)
  val lastName: TextView by bindView(R.id.last_name)

  // Optional binding.
  val details: TextView? by bindOptionalView(R.id.details)
}

Demo实践

使用kotlin结合一些流行的第三方库+知乎日报API做了一个简单的demo,在模块(根文件夹) /mydemo) :

  • 黄油刀
  • 改造
  • Gson
  • Rxjava
  • Glide
  • 个人图书馆

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门