• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    迪恩网络公众号

heimashi/kotlin_tips: [DEPRECATED] 用Kotlin去提高生产力:汇总Kotlin相对于Java的优 ...

原作者: [db:作者] 来自: 网络 收藏 邀请

开源软件名称(OpenSource Name):

heimashi/kotlin_tips

开源软件地址(OpenSource Url):

https://github.com/heimashi/kotlin_tips

开源编程语言(OpenSource Language):

Kotlin 79.8%

开源软件介绍(OpenSource Introduction):

怎么用Kotlin去提高生产力:Kotlin Tips

汇总Kotlin相对于Java的优势,以及怎么用Kotlin去简洁、务实、高效、安全的开发,每个tip都有详细的说明和案例代码,争取把每个tip分析得清楚易懂,会不断的更新维护tips,欢迎fork进来加入我们一起来维护,有问题的话欢迎提Issues。

  • 推荐:Android模块化通信项目module-service-manager,支持模块间功能服务/View/Fragment的通信调用等,通过注解标示模块内需要暴露出来的服务和View,然后gradle插件会通过transform来hook编译过程,扫描出注解信息后再利用asm生成代码来向服务管理中心注册对应的服务和View,之后模块间就可以利用框架这个桥梁来调用和通信了
  • 推荐:Kotlin的实践项目debug_view_kotlin,用Kotlin实现的Android浮层调试控制台,实时的显示内存、FPS、App启动时间、Activity启动时间、文字Log
  • 推荐:数据预加载项目and-load-aot,通过提前加载数据来提高页面启动速度,利用编译时注解生成加载方法的路由,在Activity启动前就去加载数据

目录


Tip1-更简洁的字符串

回到目录

三个引号

详见案例代码KotlinTip1

Kotlin中的字符串基本Java中的类似,有一点区别是加入了三个引号"""来方便长篇字符的编写。 而在Java中,这些都需要转义,先看看java中的式例

    public void testString1() {
        String str1 = "abc";
        String str2 = "line1\n" +
                "line2\n" +
                "line3";
        String js = "function myFunction()\n" +
                "{\n" +
                "    document.getElementById(\"demo\").innerHTML=\"My First JavaScript Function\";\n" +
                "}";
        System.out.println(str1);
        System.out.println(str2);
        System.out.println(js);
    }

kotlin除了有单个双引号的字符串,还对字符串的加强,引入了三个引号,"""中可以包含换行、反斜杠等等特殊字符:

/*
* kotlin对字符串的加强,三个引号"""中可以包含换行、反斜杠等等特殊字符
* */
fun testString() {
    val str1 = "abc"
    val str2 = """line1\n
        line2
        line3
        """
    val js = """
        function myFunction()
        {
            document.getElementById("demo").innerHTML="My First JavaScript Function";
        }
        """.trimIndent()
    println(str1)
    println(str2)
    println(js)
}

字符串模版

同时,Kotlin中引入了字符串模版,方便字符串的拼接,可以用$符号拼接变量和表达式

/*
* kotlin字符串模版,可以用$符号拼接变量和表达式
* */
fun testString2() {
    val strings = arrayListOf("abc", "efd", "gfg")
    println("First content is $strings")
    println("First content is ${strings[0]}")
    println("First content is ${if (strings.size > 0) strings[0] else "null"}")
}

值得注意的是,在Kotlin中,美元符号$是特殊字符,在字符串中不能直接显示,必须经过转义,方法1是用反斜杠,方法二是${'$'}

/*
* Kotlin中,美元符号$是特殊字符,在字符串中不能直接显示,必须经过转义,方法1是用反斜杠,方法二是${'$'}
* */
fun testString3() {
    println("First content is \$strings")
    println("First content is ${'$'}strings")
}

Tip2-Kotlin中大多数控制结构都是表达式

回到目录

首先,需要弄清楚一个概念语句和表达式,然后会介绍控制结构表达式的优点:简洁

语句和表达式是什么?

  • 表达式有值,并且能作为另一个表达式的一部分使用
  • 语句总是包围着它的代码块中的顶层元素,并且没有自己的值

Kotlin与Java的区别

  • Java中,所有的控制结构都是语句,也就是控制结构都没有值
  • Kotlin中,除了循环(for、do和do/while)以外,大多数控制结构都是表达式(if/when等)

详见案例代码tip2

Example1:if语句

java中,if 是语句,没有值,必须显式的return

/*
* java中的if语句
* */
public int max(int a, int b) {
    if (a > b) {
        return a;
    } else {
        return b;
    }
}

kotlin中,if 是表达式,不是语句,因为表达式有值,可以作为值return出去

/*
* kotlin中,if 是表达式,不是语句,类似于java中的三目运算符a > b ? a : b
* */
fun max(a: Int, b: Int): Int {
    return if (a > b) a else b
}

上面的if中的分支最后一行语句就是该分支的值,会作为函数的返回值。这其实跟java中的三元运算符类似,

/*
* java的三元运算符
* */
public int max2(int a, int b) {
    return a > b ? a : b;
}

上面是java中的三元运算符,kotlin中if是表达式有值,完全可以替代,故kotlin中已没有三元运算符了,用if来替代。 上面的max函数还可以简化成下面的形式

/*
* kotlin简化版本
* */
fun max2(a: Int, b: Int) = if (a > b) a else b

Example2:when语句

Kotlin中的when非常强大,完全可以取代Java中的switch和if/else,同时,when也是表达式,when的每个分支的最后一行为当前分支的值 先看一下java中的switch

    /*
    * java中的switch
    * */
    public String getPoint(char grade) {
        switch (grade) {
            case 'A':
                return "GOOD";
            case 'B':
            case 'C':
                return "OK";
            case 'D':
                return "BAD";
            default:
                return "UN_KNOW";
        }
    }

java中的switch有太多限制,我们再看看Kotlin怎样去简化的

/*
* kotlin中,when是表达式,可以取代Java 中的switch,when的每个分支的最后一行为当前分支的值
* */
fun getPoint(grade: Char) = when (grade) {
    'A' -> "GOOD"
    'B', 'C' -> {
        println("test when")
        "OK"
    }
    'D' -> "BAD"
    else -> "UN_KNOW"
}

同样的,when语句还可以取代java中的if/else if,其是表达式有值,并且更佳简洁

    /*
    * java中的if else
    * */
    public String getPoint2(Integer point) {
        if (point > 100) {
            return "GOOD";
        } else if (point > 60) {
            return "OK";
        } else if (point.hashCode() == 0x100) {
            //...
            return "STH";
        } else {
            return "UN_KNOW";
        }
    }

再看看kotlin的版本,使用不带参数的when,只需要6行代码

/*
* kotlin中,when是表达式,可以取代java的if/else,when的每个分支的最后一行为当前分支的值
* */
fun getPoint2(grade: Int) = when {
    grade > 90 -> "GOOD"
    grade > 60 -> "OK"
    grade.hashCode() == 0x100 -> "STH"
    else -> "UN_KNOW"
}

Tip3-更好调用的函数-显式参数名及默认参数值

回到目录

显式参数名

Kotlin的函数更加好调用,主要是表现在两个方面:1,显式的标示参数名,可以方便代码阅读;2,函数可以有默认参数值,可以大大减少Java中的函数重载。 例如现在需要实现一个工具函数,打印列表的内容: 详见案例代码KotlinTip3

/*
* 打印列表的内容
* */
fun <T> joinToString(collection: Collection<T>,
                     separator: String,
                     prefix: String,
                     postfix: String): String {
    val result = StringBuilder(prefix)
    for ((index, element) in collection.withIndex()) {
        if (index > 0) result.append(separator)
        result.append(element)
    }
    result.append(postfix)
    return result.toString()
}
/*
* 测试
* */
fun printList() {
    val list = listOf(2, 4, 0)
    // 不标明参数名
    println(joinToString(list, " - ", "[", "]"))
    // 显式的标明参数名称
    println(joinToString(list, separator = " - ", prefix = "[", postfix = "]"))
}

如上面的代码所示,函数joinToString想要打印列表的内容,需要传入四个参数:列表、分隔符、前缀和后缀。 由于参数很多,在后续使用该函数的时候不是很直观的知道每个参数是干什么用的,这时候可以显式的标明参数名称,增加代码可读性。

默认参数值

同时,定义函数的时候还可以给函数默认的参数,如下所示:

/*
* 打印列表的内容,带有默认的参数,可以避免java的函数重载
* */
fun <T> joinToString2(collection: Collection<T>,
                      separator: String = ", ",
                      prefix: String = "",
                      postfix: String = ""): String {
    val result = StringBuilder(prefix)
    for ((index, element) in collection.withIndex()) {
        if (index > 0) result.append(separator)
        result.append(element)
    }
    result.append(postfix)
    return result.toString()
}
/*
* 测试
* */
fun printList3() {
    val list = listOf(2, 4, 0)
    println(joinToString2(list, " - "))
    println(joinToString2(list, " , ", "["))
}

这样有了默认参数后,在使用函数时,如果不传入该参数,默认会使用默认的值,这样可以避免Java中大量的函数重载。

@JvmOverloads

在java与kotlin的混合项目中,会发现用kotlin实现的带默认参数的函数,在java中去调用的化就不能利用这个特性了,还是需要给所有参数赋值,像下面java这样:

List<Integer> arr = new ArrayList<Integer>() {{add(2);add(4);add(0);}};
String res = joinToString2(arr, "-", "", "");
System.out.println(res);

这时候可以在kotlin的函数前添加注解@JvmOverloads,添加注解后翻译为class的时候kotlin会帮你去生成多个函数实现函数重载,kotlin代码如下:

/*
* 通过注解@JvmOverloads解决java调用kotlin时不支持默认参数的问题
* */
@JvmOverloads
fun <T> joinToString2New(collection: Collection<T>,
                         separator: String = ", ",
                         prefix: String = "",
                         postfix: String = ""): String {
    val result = StringBuilder(prefix)
    for ((index, element) in collection.withIndex()) {
        if (index > 0) result.append(separator)
        result.append(element)
    }
    result.append(postfix)
    return result.toString()
}

这样以后,java调用kotlin的带默认参数的函数就跟kotlin一样方便了:

List<Integer> arr = new ArrayList<Integer>() {{add(2);add(4);add(0);}};
String res = joinToString2New(arr, "-");
System.out.println(res);
String res2 = joinToString2New(arr, "-", "=>");
System.out.println(res2);

Tip4-扩展函数和属性

回到目录

扩展函数和扩展属性是Kotlin非常方便实用的一个功能,它可以让我们随意的扩展第三方的库,你如果觉得别人给的SDK的Api不好用,或者不能满足你的需求,这时候你可以用扩展函数完全去自定义。

扩展函数

例如String类中,我们想获取最后一个字符,String中没有这样的直接函数,你可以用.后声明这样一个扩展函数: 详见案例代码KotlinTip4

/*
* 扩展函数
* */
fun String.lastChar(): Char = this.get(this.length - 1)
/*
* 测试
* */
fun testFunExtension() {
    val str = "test extension fun";
    println(str.lastChar())
}

这样定义好lastChar()函数后,之后只需要import进来后,就可以用String类直接调用该函数了,跟调用它自己的方法没有区别。这样可以避免重复代码和一些静态工具类,而且代码更加简洁明了。 例如我们可以改造上面tip3中的打印列表内容的函数:

/*
* 用扩展函数改造Tip3中的列表打印内容函数
* */
fun <T> Collection<T>.joinToString3(separator: String = ", ",
                                    prefix: String = "",
                                    postfix: String = ""): String {
    val result = StringBuilder(prefix)
    for ((index, element) in withIndex()) {
        if (index > 0) result.append(separator)
        result.append(element)
    }
    result.append(postfix)
    return result.toString()
}

fun printList4() {
    val list = listOf(2, 4, 0)
    println(list.joinToString3("/"))
}

扩展属性

除了扩展函数,还可以扩展属性,例如我想实现String和StringBuilder通过属性去直接获得最后字符:

/*
* 扩展属性 lastChar获取String的最后一个字符
* */
val String.lastChar: Char
    get() = get(length - 1)
/*
* 扩展属性 lastChar获取StringBuilder的最后一个字符
* */
var StringBuilder.lastChar: Char
    get() = get(length - 1)
    set(value: Char) {
        setCharAt(length - 1, value)
    }
/*
* 测试
* */
fun testExtension() {
    val s = "abc"
    println(s.lastChar)
    val sb = StringBuilder("abc")
    println(sb.lastChar)
}

定义好扩展属性后,之后只需import完了就跟使用自己的属性一样方便了。

Why?Kotlin为什么能实现扩展函数和属性这样的特性?

在Kotlin中要理解一些语法,只要认识到Kotlin语言最后需要编译为class字节码,Java也是编译为class执行,也就是可以大致理解为Kotlin需要转成Java一样的语法结构, Kotlin就是一种强大的语法糖而已,Java不具备的功能Kotlin也不能越界的。

  • 那Kotlin的扩展函数怎么实现的呢?介绍一种万能的办法去理解Kotlin的语法:将Kotlin代码转化成Java语言去理解,步骤如下:
    • 在Android Studio中选择Tools ---> Kotlin ---> Show Kotlin Bytecode 这样就把Kotlin转化为class字节码了
    • class码阅读不太友好,点击左上角的Decompile就转化为Java
  • 再介绍一个小窍门,在前期对Kotlin语法不熟悉的时候,可以先用Java写好代码,再利用AndroidStudio工具将Java代码转化为Kotlin代码,步骤如下:
    • 在Android Studio中选中要转换的Java代码 ---> 选择Code ---> Convert Java File to Kotlin File

我们看看将上面的扩展函数转成Java后的代码

/*
* 扩展函数会转化为一个静态的函数,同时这个静态函数的第一个参数就是该类的实例对象
* */
public static final char lastChar(@NotNull String $receiver) {
    Intrinsics.checkParameterIsNotNull($receiver, "$receiver");
    return $receiver.charAt($receiver.length() - 1);
}
/*
* 获取的扩展属性会转化为一个静态的get函数,同时这个静态函数的第一个参数就是该类的实例对象
* */
public static final char getLastChar(@NotNull StringBuilder $receiver) {
    Intrinsics.checkParameterIsNotNull($receiver, "$receiver");
    return $receiver.charAt($receiver.length() - 1);
}
/*
* 设置的扩展属性会转化为一个静态的set函数,同时这个静态函数的第一个参数就是该类的实例对象
* */
public static final void setLastChar(@NotNull StringBuilder $receiver, char value) {
    Intrinsics.checkParameterIsNotNull($receiver, "$receiver");
    $receiver.setCharAt($receiver.length() - 1, value);
}

查看上面的代码可知:对于扩展函数,转化为Java的时候其实就是一个静态的函数,同时这个静态函数的第一个参数就是该类的实例对象,这样把类的实例传入函数以后,函数内部就可以访问到类的公有方法。 对于扩展属性也类似,获取的扩展属性会转化为一个静态的get函数,同时这个静态函数的第一个参数就是该类的实例对象,设置的扩展属性会转化为一个静态的set函数,同时这个静态函数的第一个参数就是该类的实例对象。 函数内部可以访问公有的方法和属性。顶层的扩展函数是static的,不能被override