kotlin 深入学习

一、进阶用法

  1. 解构

 
  

解构: 解构指的是将对象的多个属性分别赋值给多个变量的过程 fun main(args: Array) { val user = User(12, "name") val (age, name) = user println(age) println(name) } class User(var age: Int, var name: String) { operator fun component1() = age operator fun component2() = name }

operator:将⼀个函数标记为重载⼀个操作符或者实现⼀个约定

 
  

fun main(args: Array) { val map = mapOf("key" to "key", "value" to "value") for ((k, v) in map) { println("$k---$v") } }

  1. 循环语法

 
  

// 左闭右闭区间 for (i in 1..10) { println(i) } // 左闭右开区间 for (i in 1 until 10) { println(i) } // 倒序 for (i in 10 downTo 1) { println(i) } for (i in 1..10 step 2) { println(i) } repeat(10) { println(it) } val list = arrayListOf("a", "b", "c", "d") for (str in list) { println(str) } for ((index, str) in list.withIndex()) { println("第${index}几个元素是$str") }

  1. 操作符

  1. 集合操作符

 
  

val list = arrayListOf('a', 'b', 'c', 'd') val a = list.map { it - 'a' } .filter { it > 0 } .find { it > 1 }

  1. 常用集合操作符

 
  

元素操作类 contains —— 判断是否有指定元素 elementAt —— 返回对应的元素,越界会抛IndexOutOfBoundsException firstOrNull —— 返回符合条件的第⼀个元素,没有 返回null lastOrNull —— 返回符合条件的最后⼀个元素,没有 返回null indexOf —— 返回指定元素的下标,没有 返回-1 singleOrNull —— 返回符合条件的单个元素,如有没有符合或超过⼀个,返回null 判断类 any —— 判断集合中 是否有满⾜条件 的元素 all —— 判断集合中的元素 是否都满⾜条件 none —— 判断集合中是否 都不满⾜条件,是则返回true count —— 查询集合中 满⾜条件 的 元素个数 reduce —— 从 第⼀项到最后⼀项进⾏累计 过滤类 filter —— 过滤 掉所有 满⾜条件 的元素 filterNot —— 过滤所有不满⾜条件的元素 filterNotNull —— 过滤NULL take —— 返回前 n 个元素 转换类 map —— 转换成另⼀个集合 mapIndexed —— 除了转换成另⼀个集合,还可以拿到Index(下标) mapNotNull —— 执⾏转换前过滤掉 为 NULL 的元素 flatMap —— ⾃定义逻辑合并两个集合 groupBy —— 按照某个条件分组,返回Map 排序类 reversed —— 反序 sorted —— 升序 sortedBy —— ⾃定义排序 sortedDescending —— 降序

  1. 作用域函数

 
  

run {...} with(T) {...} let {...} apply {...} also {...} takeIf{...} takeUnless{...} repeat(number: Int){...} let和run的区别: 都会返回闭包参数,区别let有闭包参数,run没有闭包参数 also和apply的区别: 都不会返回闭包结果,区别also有闭包参数,apply没有闭包参数 // 常用于设置同一个控件的多个属性 with(user) { this: User this.name = "with" } user.apply{ this.name = "with" }

  1. 操作符实现原理

 
  

public inline fun Array.forEach(action: (T) -> Unit): Unit { for (element in this) action(element) }

  1. 运算符重载与中缀表达式

  1. 运算符重载

 
  

fun main(args: Array) { val point2 = Point(1, 2) + Point(3, 4) println(point2) } class Point(val x: Int, val y: Int) { operator fun plus(other: Point): Point { return Point(this.x + other.x, this.y + other.y) } override fun toString(): String { return "Point{x = $x, y = $y}" } }

Expression

Translated to

+a

a.unaryPlus()

-a

a.unaryMinus()

!a

a.not()

a++

a.inc() + see below

a--

a.dec() + see below

a + b

a.plus(b)

a - b

a.minus(b)

a * b

a.times(b)

a / b

a.div(b)

a % b

a.rem(b)

a..b

a.rangeTo(b)

a in b

b.contains(a)

a !in b

!b.contains(a)

  1. 中缀表达式

 
  

// 运算符有上限,需要使用中缀表达式来扩展 infix fun Int.vs(num: Int): CompareResult = if (this - num > 0) { CompareResult.MORE }else if (this - num > 0) { CompareResult.LESS }else { CompareResult.EQUAL } fun main(args: Array) { println(5 vs 6) // 扩展函数 println(5.vs(6)) } sealed class CompareResult { object LESS: CompareResult() { override fun toString(): String { return "小于" } } object MORE: CompareResult() { override fun toString(): String { return "大于" } } object EQUAL: CompareResult() { override fun toString(): String { return "等于" } } } 一个函数只有用于两个角色类似的对象时才将其声明为中缀函数。 推荐示例:and、 to、zip。 反例:add。 如果一个方法会改动其接收者,那么不要声明为中缀形式。

  1. 一些特殊符号

  1. 反引号

 
  

反引号:键盘左上角与波浪线在一起的符号 在 Kotlin 中, 可以用反引号解决关键字冲突问题 将一个不合法的字符变成合法的 fun `1234`() {} fun ` `() {} fun ` `(){} 应用场景: Kotlin 有个特有的访问修饰符 internal,它允许当前模块的类只能在当前模块使用而不能被其他模块访问, 但是 Kotlin 与 Java 是完全兼容的,所以 Kotlin 支持了这种特性的话 Java 也必须要支持,可是 Java 是没有 internal 这样的关键字的,所以当 Kotlin 声明为 internal 时 Java 会把它声明成 public, 这样的话在 Java 中跨模块是能够直接访问到的。所以在这种场景下,如果你确定你的类是不希望被 Java 访问到的话,就可以在类名上做一些特殊不合法的字符,这样的话就可以只在 Kotlin 中访问而不能被 Java 访问。

  1. 比较对象

 
  

Kotlin Java a == b a.equals(b) a === b a == b public static void main(String[] s) { String string = "string"; String newString = new String("string"); System.out.println(string == newString); System.out.println(string.equals(newString)); }

  1. 类型链接

 
  

typealias 类型别名 = 已有类型 package kotlin.collections @kotlin.SinceKotlin public typealias ArrayList = java.util.ArrayList @kotlin.SinceKotlin public typealias HashMap = java.util.HashMap @kotlin.SinceKotlin public typealias HashSet = java.util.HashSet @kotlin.SinceKotlin public typealias LinkedHashMap = java.util.LinkedHashMap @kotlin.SinceKotlin public typealias LinkedHashSet = java.util.LinkedHashSet @kotlin.SinceKotlin public typealias RandomAccess = java.util.RandomAccess

  1. 语法特性背后的知识

  1. 变量、常量与只读

 
  

var [: ] [= ] [] [] 变量名 :变量类型 = 变量初始值 getter setter var 与 val 声明的变量,最本质的区别是: val 不能有 setter val == 常量? class Person(var birthYear: Int) { val age: Int get() { return Calendar.getInstance().get(Calendar.YEAR) - birthYear } } 编译时常量 const val a = 0 const 只允许在top-level级别和object(伴随对象也是obejct)中声明 const val constVariable = "const_variable" object obj{ const val constVariable = "const_variable" } class ConstKotlin { companion object{ const val constVariable = "const_variable" } }

  1. 空安全是如何实现的

 
  

空指针: 尝试调用空对象的成员变量或方法会触发空指针异常 空安全机制: 每次引用对象的时候,都去进行对象判空,在运行期避免对象空指针 通过静态代码检查,编译插件检查,在编译期避免空指针异常

  1. 可空类型

 
  

Kotlin代码 fun main(args: Array) { val a: String? = null println(getValue(a)) } fun getValue(s: String?): String { return "1" + s?.length } Kotlin转换成Java后(Tools—Kotlin—Show Kotlin Bytecode—Decompile) @NonNull public static final String getValue(@Nullable String s) { return "1" + (s != null ? s.length() : null); }

  1. 非空类型

 
  

Kotlin代码 fun main(args: Array) { val a: String = "" println(getValue(a)) } fun getValue(s: String): String { return "1" + s.length } Kotlin转换成Java后 @NonNull public static final String getValue(@NonNull String s) { Intrinsics.checkNotNullParameter(s, "s"); return "1" + s.length(); }

  1. 内联的特殊情况

 
  

在Kotlin中,内部Lambda是不允许中断外部函数执行的 inline 的 Lambda 可以中断外部函数调用 inline fun test(l:()-> Unit){ l.invoke() return } fun main(args Array){ test{ println("hello") //inline 的lambda 可以中断外部函数调用。此示例只会输出 “hello” //如果test 方法没有用inline 修饰,那么在lambda 表达式中是不允许添加return 关键字的 return //如果test方法没有用inline 修饰,那么在lambda中的return写法如下 return@test //指的是将当前闭包返回,不在执行闭包内return后面的代码 } printlin("world") } crossinline 不允许 inline 的Lambda 中断外部函数执行 inline fun test(crossinline l:()-> Unit){ l.invoke() return } //当想让hello 被直行,xiaoming 不被执行。并且world需要被执行时, //使用crossinline 修饰lambda。表示不允许lambda 中断外部函数执行 fun main(args Array){ test{ println("hello") return@test //只能retutn 当前lambda println("xioaming") } printlin("world") } test() noinline 拒绝内联 // 如果l2 lambda 不申明为noinline 无法return // 如果 return func1 编译器会报错,因为func1参与了内联,在调用处是将函数体直接复制过去 inline fun test(l1:()-> Unit,noinline l2:()->Unit):()->Unit{ l1.invoke() l2.invoke() println("test") return l2 }

  1. Kotlin的真泛型与实现方法

  1. 支持限定泛型参数类型

 
  

// 如下代码表示T 需要直接或间接继承或实现 Callback ,Runnable 类型(是其子类),t 才能被传递进来,否则编译器报错 class Test where T:Callback ,T:Runnable { fun add(t:T){ t.run() t.callback() } } class A :Callback,Runnable{ override fun callback(){ printlin("callback") } override fun run(){ printlin("run") } } interface Callback{ fun callback() } fun main(args:Array){ val test=Test test.add(A) }

  1. kotlin的泛型是真范型

 
  

kotlin 的泛型是真泛型,不会像java 泛型,在编译后参数都被抹去,直接变成object //以gson库代码举例 java code 如下 public T fromJson(String json,Class classOfT){ //... } kotlin code 如下 inline fun Gson.fromJson(json:String):T{ return fromJson(json,T::class.java) } //注:此处inline不可被省略 // 通过reified 修饰,告诉编译器 T 是一个真泛型,如此我们在代码运行时,就可以拿到T在运行时实际代表的那个javaClass // reified 只能用来修饰函数,不能用来修饰类

  1. Kotlin 的DSL

  1. 什么是DSL

 
  

DSL 全称是 Domain Specific Language,即领域特定语言。顾名思义 DSL 是用来专门解决某一特定问题的语言,比如我们常见的 SQL 或者正则表达式等,DSL 没有通用编程语言(Java、Kotlin等)那么万能,但是在特定问题的解决上更高效。 创作一套全新新语言的成本很高,所以很多时候我们可以基于已有的通用编程语言打造自己的 DSL,比如日常开发中我们将常见到 gradle 脚本 ,其本质就是来自 Groovy 的一套 DSL:

 
  

android { compileSdkVersion 28 defaultConfig { applicationId "com.my.app" minSdkVersion 24 targetSdkVersion 30 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } } build.gradle 中我们可以用大括号表现层级结构,使用键值对的形式设置参数,没有多余的程序符号,非常直观。如果将其还原成标准的 Groovy 语法则变成下面这样,是下面这样,在可读性上的好坏立判: Android(30, DefaultConfig("com.my.app", 24, 30, 1, "1.0", "android.support.test.runner.AndroidJUnitRunner" ) ), BuildTypes( Release(false, getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' ) )

  1. kotlin的DSL及其优势

 
  

Kotlin 是 Android 的主要编程语言,因此我们可以在 Android 开发中发挥其 DSL 优势,提升特定场景下的开发效率。例如 Compose 的 UI 代码就是一个很好的示范,它借助 DSL 让 Kotlin 代码具有了不输于 XML 的表现力,同时还兼顾了类型安全,提升了 UI 开发效率。 普通的 Android View 也可以使用 DSL 进行描述。下面是一个简单的 UI 布局,左边是其对应的 XML 代码,右边是我们为其设计的 Kotlin DSL 代码

xml

Compose

通过对比可以看到 Kotin DSL 有诸多好处: 1.有着近似 XML 的结构化表现力 2.较少的字符串,更多的强类型,更安全 3.linearLayoutParams 这样的对象可以多次复用 4.可以在定义布局的同时实现 onClick 等 5.如果需要,还可以嵌入 if ,for 这样的控制语句

 
  

不使用DSL LinearLayout(context).apply { addView(ImageView(context).apply { image = context.getDrawable(R.drawable.avatar) }, LinearLayout.LayoutParams(context, null).apply {...}) addView(LinearLayout(context).apply { ... }, LinearLayout.LayoutParams(context,null).apply {...}) addView(Button(context).apply { setOnClickListener { ... } }, LinearLayout.LayoutParams(0,0).apply {...}) }

  1. Kotlin 如何实现DSL

 
  

1.使用带有尾 lambda 的高阶函数实现大括号的层级调用

2.为 lambda 添加 Receiver,通过 this 传递上下文

3.通过扩展函数优化代码风格,DSL 中避免出现命令式的语义

4.使用 infix 减少点号圆括号等符号的出现,提高可读性

5.使用 @DslMarker 限制 DSL 作用域,避免出错

6.使用 inline 提升性能,同时使用 @PublishedApi 避免不必要的代码暴露

你可能感兴趣的:(开发分享,java,servlet,android)