├── .gitignore ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── sw │ │ │ └── kotlin │ │ │ ├── MainActivity.kt │ │ │ ├── tip1 │ │ │ ├── JavaExample1.java │ │ │ └── KotlinTip1.kt │ │ │ ├── tip10 │ │ │ ├── JavaExample10.java │ │ │ └── KotlinTip10.kt │ │ │ ├── tip11 │ │ │ └── KotlinTip11.kt │ │ │ ├── tip12 │ │ │ └── KotlinTip12.kt │ │ │ ├── tip13 │ │ │ └── KotlinTip13.kt │ │ │ ├── tip14 │ │ │ └── KotlinTip14.kt │ │ │ ├── tip15 │ │ │ └── KotlinTip15.kt │ │ │ ├── tip16 │ │ │ ├── JavaExample16.java │ │ │ └── KotlinTip16.kt │ │ │ ├── tip2 │ │ │ ├── JavaExample2.java │ │ │ └── KotlinTip2.kt │ │ │ ├── tip3 │ │ │ ├── JavaExample3.java │ │ │ └── KotlinTip3.kt │ │ │ ├── tip4 │ │ │ ├── ActivityExtensions.kt │ │ │ ├── Extensions.kt │ │ │ ├── ExtensionsAsMembers.kt │ │ │ ├── KotlinTip4.kt │ │ │ └── StaticallyExtension.kt │ │ │ ├── tip5 │ │ │ └── KotlinTip5.kt │ │ │ ├── tip6 │ │ │ ├── KotlinTip6.kt │ │ │ ├── Tip6Activity.kt │ │ │ └── Tip6Fragment.kt │ │ │ ├── tip7 │ │ │ └── KotlinTip7.kt │ │ │ ├── tip8 │ │ │ ├── JavaExample8.java │ │ │ └── KotlinTip8.kt │ │ │ └── tip9 │ │ │ └── KotlinTip9.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ ├── activity_fragment_container.xml │ │ ├── activity_main.xml │ │ ├── activity_tip6.xml │ │ ├── fragment_tip6.xml │ │ └── layout_tip6.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── sw │ └── kotlin │ └── tips │ └── ExampleUnitTest.kt ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea 5 | /.idea/workspace.xml 6 | /.idea/libraries 7 | .DS_Store 8 | /build 9 | /captures 10 | .externalNativeBuild 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 怎么用Kotlin去提高生产力:Kotlin Tips 2 | ================================== 3 | 4 | 汇总Kotlin相对于Java的优势,以及怎么用Kotlin去简洁、务实、高效、安全的开发,每个tip都有详细的说明和案例代码,争取把每个tip分析得清楚易懂,会不断的更新维护tips,欢迎fork进来加入我们一起来维护,有问题的话欢迎提Issues。 5 | 6 | - 推荐:Android模块化通信项目[module-service-manager](https://github.com/heimashi/module-service-manager),支持模块间功能服务/View/Fragment的通信调用等,通过注解标示模块内需要暴露出来的服务和View,然后gradle插件会通过transform来hook编译过程,扫描出注解信息后再利用asm生成代码来向服务管理中心注册对应的服务和View,之后模块间就可以利用框架这个桥梁来调用和通信了 7 | - 推荐:Kotlin的实践项目[debug_view_kotlin](https://github.com/heimashi/debug_view_kotlin),用Kotlin实现的Android浮层调试控制台,实时的显示内存、FPS、App启动时间、Activity启动时间、文字Log 8 | - 推荐:数据预加载项目[and-load-aot](https://github.com/heimashi/and-load-aot),通过提前加载数据来提高页面启动速度,利用编译时注解生成加载方法的路由,在Activity启动前就去加载数据 9 | 10 | **** 11 | ## 目录 12 | * [Tip1-更简洁的字符串](#Tip1-更简洁的字符串) 13 | * 1、三个引号 2、字符串模版 14 | * [Tip2-Kotlin中大多数控制结构都是表达式](#Tip2-Kotlin中大多数控制结构都是表达式) 15 | * 1、语句和表达式 2、if 3、when 16 | * [Tip3-更好调用的函数:显式参数名及默认参数值](#Tip3-更好调用的函数-显式参数名及默认参数值) 17 | * 1、显式参数名 2、默认参数值 3、@JvmOverloads 18 | * [Tip4-扩展函数和属性](#Tip4-扩展函数和属性) 19 | * 1、扩展函数 2、扩展属性 20 | * [Tip5-懒初始化bylazy和延迟初始化lateinit](#Tip5-懒初始化bylazy和延迟初始化lateinit) 21 | * 1、by lazy 2、lateinit 22 | * [Tip6-不用再手写findViewById](#Tip6-不用再手写findViewById) 23 | * 1、Activity 2、子View或者include标签 3、Fragment 24 | * [Tip7-利用局部函数抽取重复代码](#Tip7-利用局部函数抽取重复代码) 25 | * 1、局部函数 2、扩展函数 26 | * [Tip8-使用数据类来快速实现model类](#Tip8-使用数据类来快速实现model类) 27 | * [Tip9-用类委托来快速实现装饰器模式](#Tip9-用类委托来快速实现装饰器模式) 28 | * [Tip10-Lambda表达式简化OnClickListener](#Tip10-Lambda表达式简化OnClickListener) 29 | * [Tip11-with函数来简化代码](#Tip11-with函数来简化代码) 30 | * [Tip12-apply函数来简化代码](#Tip12-apply函数来简化代码) 31 | * [Tip13-在编译阶段避免掉NullPointerException](#Tip13-在编译阶段避免掉NullPointerException) 32 | * 1、可空和不可空类型 2、let 3、Elvis操作符 33 | * [Tip14-运算符重载](#Tip14-运算符重载) 34 | * [Tip15-高阶函数简化代码](#Tip15-高阶函数简化代码) 35 | * [Tip16-用Lambda来简化策略模式](#Tip16-用Lambda来简化策略模式) 36 | **** 37 | 38 | ## Tip1-更简洁的字符串 39 | [回到目录](#目录) 40 | 41 | #### 三个引号 42 | 详见案例代码[KotlinTip1](https://github.com/heimashi/kotlin_tips/blob/master/app/src/main/java/com/sw/kotlin/tip1/KotlinTip1.kt) 43 | 44 | Kotlin中的字符串基本Java中的类似,有一点区别是加入了三个引号"""来方便长篇字符的编写。 45 | 而在Java中,这些都需要转义,先看看java中的式例 46 | ```java 47 | public void testString1() { 48 | String str1 = "abc"; 49 | String str2 = "line1\n" + 50 | "line2\n" + 51 | "line3"; 52 | String js = "function myFunction()\n" + 53 | "{\n" + 54 | " document.getElementById(\"demo\").innerHTML=\"My First JavaScript Function\";\n" + 55 | "}"; 56 | System.out.println(str1); 57 | System.out.println(str2); 58 | System.out.println(js); 59 | } 60 | ``` 61 | kotlin除了有单个双引号的字符串,还对字符串的加强,引入了**三个引号**,"""中可以包含换行、反斜杠等等特殊字符: 62 | ```kotlin 63 | /* 64 | * kotlin对字符串的加强,三个引号"""中可以包含换行、反斜杠等等特殊字符 65 | * */ 66 | fun testString() { 67 | val str1 = "abc" 68 | val str2 = """line1\n 69 | line2 70 | line3 71 | """ 72 | val js = """ 73 | function myFunction() 74 | { 75 | document.getElementById("demo").innerHTML="My First JavaScript Function"; 76 | } 77 | """.trimIndent() 78 | println(str1) 79 | println(str2) 80 | println(js) 81 | } 82 | ``` 83 | 84 | #### 字符串模版 85 | 同时,Kotlin中引入了**字符串模版**,方便字符串的拼接,可以用$符号拼接变量和表达式 86 | ```kotlin 87 | /* 88 | * kotlin字符串模版,可以用$符号拼接变量和表达式 89 | * */ 90 | fun testString2() { 91 | val strings = arrayListOf("abc", "efd", "gfg") 92 | println("First content is $strings") 93 | println("First content is ${strings[0]}") 94 | println("First content is ${if (strings.size > 0) strings[0] else "null"}") 95 | } 96 | ``` 97 | 值得注意的是,在Kotlin中,美元符号$是特殊字符,在字符串中不能直接显示,必须经过转义,方法1是用反斜杠,方法二是${'$'} 98 | ```kotlin 99 | /* 100 | * Kotlin中,美元符号$是特殊字符,在字符串中不能直接显示,必须经过转义,方法1是用反斜杠,方法二是${'$'} 101 | * */ 102 | fun testString3() { 103 | println("First content is \$strings") 104 | println("First content is ${'$'}strings") 105 | } 106 | ``` 107 | 108 | ## Tip2-Kotlin中大多数控制结构都是表达式 109 | [回到目录](#目录) 110 | 111 | 首先,需要弄清楚一个概念**语句和表达式**,然后会介绍控制结构表达式的优点:**简洁** 112 | #### 语句和表达式是什么? 113 | - 表达式有值,并且能作为另一个表达式的一部分使用 114 | - 语句总是包围着它的代码块中的顶层元素,并且没有自己的值 115 | #### Kotlin与Java的区别 116 | - Java中,所有的控制结构都是语句,也就是控制结构都没有值 117 | - Kotlin中,除了循环(for、do和do/while)以外,大多数控制结构都是表达式(if/when等) 118 | 119 | 120 | 详见案例代码[tip2](https://github.com/heimashi/kotlin_tips/blob/master/app/src/main/java/com/sw/kotlin/tip2) 121 | #### Example1:if语句 122 | java中,if 是语句,没有值,必须显式的return 123 | ```java 124 | /* 125 | * java中的if语句 126 | * */ 127 | public int max(int a, int b) { 128 | if (a > b) { 129 | return a; 130 | } else { 131 | return b; 132 | } 133 | } 134 | ``` 135 | kotlin中,if 是表达式,不是语句,因为表达式有值,可以作为值return出去 136 | ```kotlin 137 | /* 138 | * kotlin中,if 是表达式,不是语句,类似于java中的三目运算符a > b ? a : b 139 | * */ 140 | fun max(a: Int, b: Int): Int { 141 | return if (a > b) a else b 142 | } 143 | ``` 144 | 上面的if中的分支最后一行语句就是该分支的值,会作为函数的返回值。这其实跟java中的三元运算符类似, 145 | ```java 146 | /* 147 | * java的三元运算符 148 | * */ 149 | public int max2(int a, int b) { 150 | return a > b ? a : b; 151 | } 152 | ``` 153 | 上面是java中的三元运算符,kotlin中if是表达式有值,完全可以替代,**故kotlin中已没有三元运算符了**,用if来替代。 154 | 上面的max函数还可以简化成下面的形式 155 | ```kotlin 156 | /* 157 | * kotlin简化版本 158 | * */ 159 | fun max2(a: Int, b: Int) = if (a > b) a else b 160 | ``` 161 | 162 | #### Example2:when语句 163 | Kotlin中的when非常强大,完全可以取代Java中的switch和if/else,同时,**when也是表达式**,when的每个分支的最后一行为当前分支的值 164 | 先看一下java中的switch 165 | ```java 166 | /* 167 | * java中的switch 168 | * */ 169 | public String getPoint(char grade) { 170 | switch (grade) { 171 | case 'A': 172 | return "GOOD"; 173 | case 'B': 174 | case 'C': 175 | return "OK"; 176 | case 'D': 177 | return "BAD"; 178 | default: 179 | return "UN_KNOW"; 180 | } 181 | } 182 | ``` 183 | java中的switch有太多限制,我们再看看Kotlin怎样去简化的 184 | ```kotlin 185 | /* 186 | * kotlin中,when是表达式,可以取代Java 中的switch,when的每个分支的最后一行为当前分支的值 187 | * */ 188 | fun getPoint(grade: Char) = when (grade) { 189 | 'A' -> "GOOD" 190 | 'B', 'C' -> { 191 | println("test when") 192 | "OK" 193 | } 194 | 'D' -> "BAD" 195 | else -> "UN_KNOW" 196 | } 197 | ``` 198 | 同样的,when语句还可以取代java中的if/else if,其是表达式有值,并且更佳简洁 199 | ```java 200 | /* 201 | * java中的if else 202 | * */ 203 | public String getPoint2(Integer point) { 204 | if (point > 100) { 205 | return "GOOD"; 206 | } else if (point > 60) { 207 | return "OK"; 208 | } else if (point.hashCode() == 0x100) { 209 | //... 210 | return "STH"; 211 | } else { 212 | return "UN_KNOW"; 213 | } 214 | } 215 | ``` 216 | 再看看kotlin的版本,使用**不带参数的when**,只需要6行代码 217 | ```kotlin 218 | /* 219 | * kotlin中,when是表达式,可以取代java的if/else,when的每个分支的最后一行为当前分支的值 220 | * */ 221 | fun getPoint2(grade: Int) = when { 222 | grade > 90 -> "GOOD" 223 | grade > 60 -> "OK" 224 | grade.hashCode() == 0x100 -> "STH" 225 | else -> "UN_KNOW" 226 | } 227 | ``` 228 | 229 | ## Tip3-更好调用的函数-显式参数名及默认参数值 230 | [回到目录](#目录) 231 | 232 | #### 显式参数名 233 | Kotlin的函数更加好调用,主要是表现在两个方面:1,显式的**标示参数名**,可以方便代码阅读;2,函数可以有**默认参数值**,可以大大**减少Java中的函数重载**。 234 | 例如现在需要实现一个工具函数,打印列表的内容: 235 | 详见案例代码[KotlinTip3](https://github.com/heimashi/kotlin_tips/blob/master/app/src/main/java/com/sw/kotlin/tip3/KotlinTip3.kt) 236 | ```kotlin 237 | /* 238 | * 打印列表的内容 239 | * */ 240 | fun joinToString(collection: Collection, 241 | separator: String, 242 | prefix: String, 243 | postfix: String): String { 244 | val result = StringBuilder(prefix) 245 | for ((index, element) in collection.withIndex()) { 246 | if (index > 0) result.append(separator) 247 | result.append(element) 248 | } 249 | result.append(postfix) 250 | return result.toString() 251 | } 252 | /* 253 | * 测试 254 | * */ 255 | fun printList() { 256 | val list = listOf(2, 4, 0) 257 | // 不标明参数名 258 | println(joinToString(list, " - ", "[", "]")) 259 | // 显式的标明参数名称 260 | println(joinToString(list, separator = " - ", prefix = "[", postfix = "]")) 261 | } 262 | ``` 263 | 如上面的代码所示,函数joinToString想要打印列表的内容,需要传入四个参数:列表、分隔符、前缀和后缀。 264 | 由于参数很多,在后续使用该函数的时候不是很直观的知道每个参数是干什么用的,这时候可以显式的标明参数名称,增加代码可读性。 265 | 266 | #### 默认参数值 267 | 同时,定义函数的时候还可以给函数默认的参数,如下所示: 268 | ```kotlin 269 | /* 270 | * 打印列表的内容,带有默认的参数,可以避免java的函数重载 271 | * */ 272 | fun joinToString2(collection: Collection, 273 | separator: String = ", ", 274 | prefix: String = "", 275 | postfix: String = ""): String { 276 | val result = StringBuilder(prefix) 277 | for ((index, element) in collection.withIndex()) { 278 | if (index > 0) result.append(separator) 279 | result.append(element) 280 | } 281 | result.append(postfix) 282 | return result.toString() 283 | } 284 | /* 285 | * 测试 286 | * */ 287 | fun printList3() { 288 | val list = listOf(2, 4, 0) 289 | println(joinToString2(list, " - ")) 290 | println(joinToString2(list, " , ", "[")) 291 | } 292 | ``` 293 | 这样有了默认参数后,在使用函数时,如果不传入该参数,默认会使用默认的值,这样可以避免Java中大量的函数重载。 294 | 295 | #### @JvmOverloads 296 | 在java与kotlin的混合项目中,会发现用kotlin实现的带默认参数的函数,在java中去调用的化就不能利用这个特性了,还是需要给所有参数赋值,像下面java这样: 297 | ```java 298 | List arr = new ArrayList() {{add(2);add(4);add(0);}}; 299 | String res = joinToString2(arr, "-", "", ""); 300 | System.out.println(res); 301 | ``` 302 | 这时候可以在kotlin的函数前添加注解@JvmOverloads,添加注解后翻译为class的时候kotlin会帮你去生成多个函数实现函数重载,kotlin代码如下: 303 | ```kotlin 304 | /* 305 | * 通过注解@JvmOverloads解决java调用kotlin时不支持默认参数的问题 306 | * */ 307 | @JvmOverloads 308 | fun joinToString2New(collection: Collection, 309 | separator: String = ", ", 310 | prefix: String = "", 311 | postfix: String = ""): String { 312 | val result = StringBuilder(prefix) 313 | for ((index, element) in collection.withIndex()) { 314 | if (index > 0) result.append(separator) 315 | result.append(element) 316 | } 317 | result.append(postfix) 318 | return result.toString() 319 | } 320 | ``` 321 | 这样以后,java调用kotlin的带默认参数的函数就跟kotlin一样方便了: 322 | ```java 323 | List arr = new ArrayList() {{add(2);add(4);add(0);}}; 324 | String res = joinToString2New(arr, "-"); 325 | System.out.println(res); 326 | String res2 = joinToString2New(arr, "-", "=>"); 327 | System.out.println(res2); 328 | ``` 329 | 330 | ## Tip4-扩展函数和属性 331 | [回到目录](#目录) 332 | 333 | 扩展函数和扩展属性是Kotlin非常方便实用的一个功能,它可以让我们随意的扩展第三方的库,你如果觉得别人给的SDK的Api不好用,或者不能满足你的需求,这时候你可以用扩展函数完全去自定义。 334 | 335 | #### 扩展函数 336 | 例如String类中,我们想获取最后一个字符,String中没有这样的直接函数,你可以用.后声明这样一个扩展函数: 337 | 详见案例代码[KotlinTip4](https://github.com/heimashi/kotlin_tips/blob/master/app/src/main/java/com/sw/kotlin/tip4/KotlinTip4.kt) 338 | ```kotlin 339 | /* 340 | * 扩展函数 341 | * */ 342 | fun String.lastChar(): Char = this.get(this.length - 1) 343 | /* 344 | * 测试 345 | * */ 346 | fun testFunExtension() { 347 | val str = "test extension fun"; 348 | println(str.lastChar()) 349 | } 350 | ``` 351 | 这样定义好lastChar()函数后,之后只需要import进来后,就可以用String类直接调用该函数了,跟调用它自己的方法没有区别。这样可以避免重复代码和一些静态工具类,而且代码更加简洁明了。 352 | 例如我们可以改造上面tip3中的打印列表内容的函数: 353 | ```kotlin 354 | /* 355 | * 用扩展函数改造Tip3中的列表打印内容函数 356 | * */ 357 | fun Collection.joinToString3(separator: String = ", ", 358 | prefix: String = "", 359 | postfix: String = ""): String { 360 | val result = StringBuilder(prefix) 361 | for ((index, element) in withIndex()) { 362 | if (index > 0) result.append(separator) 363 | result.append(element) 364 | } 365 | result.append(postfix) 366 | return result.toString() 367 | } 368 | 369 | fun printList4() { 370 | val list = listOf(2, 4, 0) 371 | println(list.joinToString3("/")) 372 | } 373 | ``` 374 | 375 | #### 扩展属性 376 | 除了扩展函数,还可以扩展属性,例如我想实现String和StringBuilder通过属性去直接获得最后字符: 377 | ```kotlin 378 | /* 379 | * 扩展属性 lastChar获取String的最后一个字符 380 | * */ 381 | val String.lastChar: Char 382 | get() = get(length - 1) 383 | /* 384 | * 扩展属性 lastChar获取StringBuilder的最后一个字符 385 | * */ 386 | var StringBuilder.lastChar: Char 387 | get() = get(length - 1) 388 | set(value: Char) { 389 | setCharAt(length - 1, value) 390 | } 391 | /* 392 | * 测试 393 | * */ 394 | fun testExtension() { 395 | val s = "abc" 396 | println(s.lastChar) 397 | val sb = StringBuilder("abc") 398 | println(sb.lastChar) 399 | } 400 | ``` 401 | 定义好扩展属性后,之后只需import完了就跟使用自己的属性一样方便了。 402 | 403 | #### Why?Kotlin为什么能实现扩展函数和属性这样的特性? 404 | 在Kotlin中要理解一些语法,只要认识到**Kotlin语言最后需要编译为class字节码,Java也是编译为class执行,也就是可以大致理解为Kotlin需要转成Java一样的语法结构**, 405 | Kotlin就是一种**强大的语法糖**而已,Java不具备的功能Kotlin也不能越界的。 406 | - 那Kotlin的扩展函数怎么实现的呢?介绍一种万能的办法去理解Kotlin的语法:**将Kotlin代码转化成Java语言**去理解,步骤如下: 407 | - 在Android Studio中选择Tools ---> Kotlin ---> Show Kotlin Bytecode 这样就把Kotlin转化为class字节码了 408 | - class码阅读不太友好,点击左上角的Decompile就转化为Java 409 | - 再介绍一个小窍门,在前期对Kotlin语法不熟悉的时候,可以先用Java写好代码,再利用AndroidStudio工具**将Java代码转化为Kotlin代码**,步骤如下: 410 | - 在Android Studio中选中要转换的Java代码 ---> 选择Code ---> Convert Java File to Kotlin File 411 | 412 | 我们看看将上面的扩展函数转成Java后的代码 413 | ```java 414 | /* 415 | * 扩展函数会转化为一个静态的函数,同时这个静态函数的第一个参数就是该类的实例对象 416 | * */ 417 | public static final char lastChar(@NotNull String $receiver) { 418 | Intrinsics.checkParameterIsNotNull($receiver, "$receiver"); 419 | return $receiver.charAt($receiver.length() - 1); 420 | } 421 | /* 422 | * 获取的扩展属性会转化为一个静态的get函数,同时这个静态函数的第一个参数就是该类的实例对象 423 | * */ 424 | public static final char getLastChar(@NotNull StringBuilder $receiver) { 425 | Intrinsics.checkParameterIsNotNull($receiver, "$receiver"); 426 | return $receiver.charAt($receiver.length() - 1); 427 | } 428 | /* 429 | * 设置的扩展属性会转化为一个静态的set函数,同时这个静态函数的第一个参数就是该类的实例对象 430 | * */ 431 | public static final void setLastChar(@NotNull StringBuilder $receiver, char value) { 432 | Intrinsics.checkParameterIsNotNull($receiver, "$receiver"); 433 | $receiver.setCharAt($receiver.length() - 1, value); 434 | } 435 | ``` 436 | 查看上面的代码可知:对于扩展函数,转化为Java的时候其实就是一个静态的函数,同时这个静态函数的第一个参数就是该类的实例对象,这样把类的实例传入函数以后,函数内部就可以访问到类的公有方法。 437 | 对于扩展属性也类似,获取的扩展属性会转化为一个静态的get函数,同时这个静态函数的第一个参数就是该类的实例对象,设置的扩展属性会转化为一个静态的set函数,同时这个静态函数的第一个参数就是该类的实例对象。 438 | 函数内部可以访问公有的方法和属性。顶层的扩展函数是static的,**不能被override** 439 | - 从上面转换的源码其实可以看到**扩展函数和扩展属性适用的地方和缺陷**: 440 | - 扩展函数和扩展属性内**只能访问到类的公有方法和属性**,私有的和protected是访问不了的 441 | - 扩展函数**不是真的修改了原来的类**,定义一个扩展函数不是将新成员函数插入到类中,扩展函数的类型是"静态的",不是在运行时决定类型,案例代码[StaticllyExtension.kt](https://github.com/heimashi/kotlin_tips/blob/master/app/src/main/java/com/sw/kotlin/tip4/StaticllyExtension.kt) 442 | ```kotlin 443 | open class C 444 | 445 | class D : C() 446 | 447 | fun C.foo() = "c" 448 | 449 | fun D.foo() = "d" 450 | 451 | /* 452 | * https://kotlinlang.org/docs/reference/extensions.html 453 | * Extensions do not actually modify classes they extend. By defining an extension, you do not insert new members into a class, 454 | * but merely make new functions callable with the dot-notation on variables of this type. Extension functions are 455 | * dispatched statically. 456 | * */ 457 | fun printFoo(c: C) { 458 | println(c.foo()) 459 | } 460 | 461 | fun testStatically() { 462 | printFoo(C()) // print c 463 | printFoo(D()) // also print c 464 | } 465 | ``` 466 | 上面的案例中即使调用printFoo(D())还是打印出c,而不是d。转成java中会看到下面的代码,D类型在调用的时候会强制转换为C类型: 467 | ```java 468 | public static final String foo(@NotNull C $receiver) { 469 | Intrinsics.checkParameterIsNotNull($receiver, "$receiver"); 470 | return "c"; 471 | } 472 | 473 | public static final String foo(@NotNull D $receiver) { 474 | Intrinsics.checkParameterIsNotNull($receiver, "$receiver"); 475 | return "d"; 476 | } 477 | 478 | public static final void printFoo(@NotNull C c) { 479 | Intrinsics.checkParameterIsNotNull(c, "c"); 480 | String var1 = foo(c); 481 | System.out.println(var1); 482 | } 483 | public static final void testStatically() { 484 | printFoo(new C()); 485 | printFoo((C)(new D())); 486 | } 487 | ``` 488 | 489 | - 声明扩展函数作为类的成员变量 490 | - 上面的例子扩展函数是作为顶层函数,如果把扩展函数申明为类的成员变量,即扩展函数的作用域就在类的内部有效,案例代码[ExtensionsAsMembers.kt](https://github.com/heimashi/kotlin_tips/blob/master/app/src/main/java/com/sw/kotlin/tip4/ExtensionsAsMembers.kt) 491 | ```kotlin 492 | open class D { 493 | } 494 | 495 | class D1 : D() { 496 | } 497 | 498 | open class C { 499 | open fun D.foo() { 500 | println("D.foo in C") 501 | } 502 | 503 | open fun D1.foo() { 504 | println("D1.foo in C") 505 | } 506 | 507 | fun caller(d: D) { 508 | d.foo() // call the extension function 509 | } 510 | } 511 | 512 | class C1 : C() { 513 | override fun D.foo() { 514 | println("D.foo in C1") 515 | } 516 | 517 | override fun D1.foo() { 518 | println("D1.foo in C1") 519 | } 520 | } 521 | 522 | fun testAsMembers() { 523 | C().caller(D()) // prints "D.foo in C" 524 | C1().caller(D()) // prints "D.foo in C1" - dispatch receiver is resolved virtually 525 | C().caller(D1()) // prints "D.foo in C" - extension receiver is resolved statically 526 | C1().caller(D1()) // prints "D.foo in C1" 527 | } 528 | ``` 529 | 函数caller的类型是D,即使调用C().caller(D1()),打印的结果还是D.foo in C,而不是D1.foo in C,不是运行时来动态决定类型,成员扩展函数申明为open, 530 | 一旦在子类中被override,就调用不到在父类中的扩展函数,在子类中的作用域内的只能访问到override后的函数,不能像普通函数override那样通过super关键字访问了。 531 | 532 | - 下面再举几个扩展函数的例子,让大家感受一下扩展函数的方便: 533 | ```kotlin 534 | /* 535 | * show toast in activity 536 | * */ 537 | fun Activity.toast(msg: String) { 538 | Toast.makeText(this, msg, Toast.LENGTH_SHORT).show() 539 | } 540 | 541 | val Context.inputMethodManager: InputMethodManager? 542 | get() = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager 543 | 544 | /* 545 | * hide soft input 546 | * */ 547 | fun Context.hideSoftInput(view: View) { 548 | inputMethodManager?.hideSoftInputFromWindow(view.windowToken, 0) 549 | } 550 | 551 | 552 | /** 553 | * screen width in pixels 554 | */ 555 | val Context.screenWidth 556 | get() = resources.displayMetrics.widthPixels 557 | 558 | /** 559 | * screen height in pixels 560 | */ 561 | val Context.screenHeight 562 | get() = resources.displayMetrics.heightPixels 563 | 564 | /** 565 | * returns dip(dp) dimension value in pixels 566 | * @param value dp 567 | */ 568 | fun Context.dip2px(value: Int): Int = (value * resources.displayMetrics.density).toInt() 569 | ``` 570 | 571 | ## Tip5-懒初始化bylazy和延迟初始化lateinit 572 | [回到目录](#目录) 573 | 574 | #### 懒初始化by lazy 575 | 懒初始化是指推迟一个变量的初始化时机,变量在使用的时候才去实例化,这样会更加的高效。因为我们通常会遇到这样的情况,一个变量直到使用时才需要被初始化,或者仅仅是它的初始化依赖于某些无法立即获得的上下文。 576 | 详见案例代码[KotlinTip5](https://github.com/heimashi/kotlin_tips/blob/master/app/src/main/java/com/sw/kotlin/tip5/KotlinTip5.kt) 577 | ```kotlin 578 | /* 579 | * 懒初始化api实例 580 | * */ 581 | val purchasingApi: PurchasingApi by lazy { 582 | val retrofit: Retrofit = Retrofit.Builder() 583 | .baseUrl(API_URL) 584 | .addConverterFactory(MoshiConverterFactory.create()) 585 | .build() 586 | retrofit.create(PurchasingApi::class.java) 587 | } 588 | ``` 589 | 像上面的代码,retrofit生成的api实例会在首次使用到的时候才去实例化。需要注意的是by lazy一般只能修饰val不变的对象,不能修饰var可变对象。 590 | ```kotlin 591 | class User(var name: String, var age: Int) 592 | 593 | /* 594 | * 懒初始化by lazy 595 | * */ 596 | val user1: User by lazy { 597 | User("jack", 15) 598 | } 599 | ``` 600 | 601 | #### 延迟初始化lateinit 602 | 另外,对于var的变量,如果类型是非空的,是必须初始化的,不然编译不通过,这时候需要用到lateinit延迟初始化,使用的时候再去实例化。 603 | ```kotlin 604 | /* 605 | * 延迟初始化lateinit 606 | * */ 607 | lateinit var user2: User 608 | 609 | fun testLateInit() { 610 | user2 = User("Lily", 14) 611 | } 612 | ``` 613 | #### by lazy 和 lateinit 的区别 614 | - by lazy 修饰val的变量 615 | - lateinit 修饰var的变量,且变量是非空的类型 616 | 617 | ## Tip6-不用再手写findViewById 618 | [回到目录](#目录) 619 | 620 | #### 在Activity中使用 621 | 在Android的View中,会有很多代码是在声明一个View,然后通过findViewById后从xml中实例化赋值给对应的View。在kotlin中可以完全解放出来了,利用kotlin-android-extensions插件,不用再手写findViewById。步骤如下: 622 | 详见案例代码[KotlinTip6](https://github.com/heimashi/kotlin_tips/blob/master/app/src/main/java/com/sw/kotlin/tip6/KotlinTip6.kt) 623 | - 步骤1,在项目的gradle中 apply plugin: 'kotlin-android-extensions' 624 | - 步骤2,按照原来的习惯书写布局xml文件 625 | ```xml 626 | 627 | 631 | 632 | 636 | 637 | 641 | 642 |