通过前面几篇文章,我们已基本掌握kotlin的基本写法与使用,但是在开发过程中,以及一些开源的API还是会出现大家模式的高阶玩法以及问题,如何避免,接下来讲解针对原来的文章进行一些扩展,解决大家在工作中遇到的问题,如何去解决
如果还有人不了解kotlin,可以查看我的基础篇kotlin。
Android kotlin在实战过程问题总结与开发技巧详解_kotlin 同步锁_蜗牛、Z的博客-CSDN博客
lazy是kotlin中的懒加载,这种写法在很多场景中都有,懒加载并不是立刻在内存中申请,而是通过lazy(),调用才会
lazy的只能是常量,用val修饰,变量是全局也可以是局部变量
通过这个lazy{}可以看出,无论你在最上面调用什么,默认值都是最后一个。通过IDE,可以提示看到ccc ^lazy,这就是懒加载的值。这个值也可以通过方法获取
fun a() {
val aa: String by lazy {
MyLog.log("val aa:String by lazy start")
"aaaa"
"bbbb"
b()
}
MyLog.log(aa)
MyLog.log(aa)
}
fun b(): String {
return "aaaaa"
}
一旦懒加载对象被调用过,后期在调用将只能取到值,不会再处理其他的lazy{} 方法体。
注意:lazy和lateinit无法一起使用。lazy修饰val变量,lateinit修饰的是var变量。
也是懒加载机制,对象是var的格式。但是,这个变量需要是全局变量,不能是局部变量。
lateinit var string: String
lateinit var people: People
fun c() {
people = People()
people.name = "zhangshan"
string = "aaaa"
MyLog.log(people.name!!)
val child: People by lazy{
People()
}
}
kotlin的变量如果不是懒加载修饰,那么你在申请的时候,就要给出变量值,即默认值
常见写法:
var name:String?=null,表示当前对象是null
var title:String="title",表示已默认值、
但是如果你申请为null,那么你在赋值的时候,这个参数就要在多个地方加上"!!",否则会报错。这种写法很烦人。
如何避免?
使用懒加载机制,通过上面两个懒加载,可以避免这种问题的参数。否则你在任何地方进程参数传递都会被判为null。
kotlin支持在现有的类中,动态扩展函数,格式就是类对象+"."+函数名,可以支持参数和返回值,扩展的函数和正常函数一样使用
以下是对String扩展一个两个方法,分别是log()和size()
fun String.log(log:String) {
MyLog.log(log)
}
fun String.size(): Int {
return toString().length
}
kotlin支持动态新增变量,格式就类名+"."+变量名+":"+类型
这种写法可以无限的扩展你需要的变量,扩展变量一样,只能在最外围,不能在方法体中定义,
扩展属性需要额外重写get()方法,且不能申请直接赋值
val String.defaulename: String
get() {
return "defaulename"
}
fun e() {
val name=""
MyLog.log(name.defaulename)
}
这里需要重写get(),给出默认值。
apply与alse是链式设置,返回的是当前对象,let返回值是unit,
我们经常会使用如下写法:
if(people!=null){
peope.name="zhangshan"
}
people?.apply{it->
it.name="zhangsnan"
}
kotlin中没有static{}模块,是通过init{}替代了static。如果需要提前初始化的可以放在init中,常见的so库加载可以放到init中
class Example {
init {
//init module
}
}
如果接口中有两个相同的方法名,在java中是需要修改一个的。在kotlin中就可以避免这种。只要在继承方法中,指向各个类的即可。
class TestInterface : IntFacA, IntFacB {
override fun log() {
super.log()
super.log()
}
override fun info() {
}
}
internal
介绍internal
属于修饰,和provite、public、protect一样
private
意味着只在这个类内部(包含其所有成员)可见;protected
—— 和 private
一样 + 在子类中可见。internal
—— 能见到类声明的 本模块内 的任何客户端都可见其 internal
成员;public
—— 能见到类声明的任何客户端都可见其 public
成员。将类对象修饰成data,需要的变量直接在构造器中申明,不需要方法体
data class User(val name: String = "", val age: Int = 0)
密封类用来表示受限的类继承结构:当一个值为有限几种的类型、而不能有任何其他类型时。在某种意义上,他们是枚举类的扩展:枚举类型的值集合也是受限的,但每个枚举常量只存在一个实例,而密封类的一个子类可以有可包含状态的多个实例。
要声明一个密封类,需要在类名前面添加 sealed
修饰符。虽然密封类也可以有子类,但是所有子类都必须在与密封类自身相同的文件中声明
sealed class Expr
data class Const(val number: Double) : Expr()
data class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr()
一个密封类是自身抽象的,它不能直接实例化并可以有抽象(abstract)成员。
密封类不允许有非-private 构造函数(其构造函数默认为 private)。
fun eval(expr: Expr): Double = when(expr) {
is Const -> expr.number
is Sum -> eval(expr.e1) + eval(expr.e2)
NotANumber -> Double.NaN
// 不再需要 `else` 子句,因为我们已经覆盖了所有的情况
}
类似接口继承,通过泛型来判断
在开发过程中,泛型的利用,可以很好的解决代码臃肿问题,也能很好的进行解耦。在kotlin中,泛型的使用有普通的,也有通配符限定
interface Factory {
fun getEntity(): T
}
class Room : Factory {
override fun getEntity(): String {
return "String info"
}
}
这种是我们最常见的泛型,通过泛型,可以很好的获取不同的对象类型
java中也有通配符,? extend E,? extends E
表示此方法接受 E
或者 E
的 一些子类型对象的集合,而不仅仅是 E
自身。简而言之,带 extends 限定(上界)的通配符类型使得类型是协变的。
还有一种,? super String,表示只接收String类型。
在 kotlin 语言中,out 表示协变,in 表示逆变;
kotlin 中的 “out T” 等同于 Java 的 “?extends T”
kotlin 中的 “in T” 等同于 Java 的 “?super T”
也就是上线和下线。
内部类可以直接使用,就是通过最外层类引用即可,也可以单独使用
class Student {
lateinit var name: String
class Classmate {
lateinit var name: String
}
}
fun main() {
var mate=Student.Classmate()
mate.name="zhangsan"
MyLog.log(mate.name)
}
标记为 inner 的嵌套类能够访问其外部类的成员。内部类会带有一个对外部类的对象的引用。
class Student {
lateinit var name: String
inner class Classmate {
lateinit var name: String
}
}
fun main() {
var mate=Student().Classmate()
mate.name="zhangsan"
MyLog.log(mate.name)
}
使用对象表达式创建匿名内部类实例:
window.addMouseListener(object : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) { …… }
override fun mouseEntered(e: MouseEvent) { …… }
})
1.lambda表达式
view.setOnClickListener(object :View.OnClickListener{
override fun onClick(v: View?) {
}
})
枚举类的最基本的用法是实现类型安全的枚举,kotlin枚举有一下几种
enum class MyEmu {
ONE{
var size="123"
},TWO,THREE
}
enum class MyEmu1(tag:Int) {
ONE(1),TWO(2),THREE(3)
}
如果你想弄那么复杂,就当普通枚举使用
enum class MyEmu :MyInfo{
ONE{
override fun log() {
TODO("Not yet implemented")
}
},TWO{
override fun log() {
TODO("Not yet implemented")
}
}
}
interface MyInfo {
fun log();
}
如果枚举类型实现了接口,那么枚举中的任何元素的都变成了匿名内部类,都要实现接口的方法
如果一个类无法被继承,就可以将该类通过open修饰,如果类中的变量无法使用,也可以通过open修饰。
open class MyNAME {
open var name = ""
}
静态类,是通过object修饰,该类里面的所有变量和方法都是静态。且不在需要class修饰
object staticName {
var name = ""
fun log() {
}
}
伴随对象是在普通类中进行扩展一个对象出来的,该伴生对象的成员可通过只使用类名作为限定符来调用
class BanshengObject {
companion object {
var name = ""
fun log() {
}
}
}
fun main() {
BanshengObject.log()
BanshengObject.name
}
请注意,即使伴生对象的成员看起来像其他语言的静态成员,在运行时他们仍然是真实对象的实例成员
如果使用 @JvmStatic
注解,你可以将伴生对象的成员生成为真正的静态方法和字段
类型别名为现有类型提供替代名称。 如果类型名称太长,你可以另外引入较短的名称,并使用新的名称替代原类型名。
class Alias {
inner class MyStudent(name:String)
}
typealias Alias_MyStudent = Alias.MyStudent
typealias SetString=Set
typealias MyHandler = (Int, String, Any)->Void
fun main() {
var m:MyHandler
}
这个别名使用在kotlin有很多,kotlin对java的api进行封装,就是通过别名完成。也可以支持方法的别名
内联类必须含有唯一的一个属性在主构造函数中初始化。在运行时,将使用这个唯一属性来表示内联类的实例
inline class InlinerBean(val title:String) {
val length: Int
get() = title.length
}
fun main() {
val bean=InlinerBean("this is inline class")
}
// 不存在 'InlinerBean' 类的真实实例对象
// 在运行时,'InlinerBean' 仅仅包含 'String'
val bean=InlinerBean("this is inline class")
同时,内联是为了解决额外的堆内存分配问题。
内联类支持普通类中的一些功能。特别是,内联类可以声明属性与函数
内联函数,有且仅有一个构造参数
内联函数也可以当普通函数使用,可以实现接口
委托模式已经证明是实现继承的一个很好的替代方式, 而 Kotlin 可以零样板代码地原生支持它
interface Base {
val message: String
fun print()
}
class BaseImpl(val x: Int) : Base {
override val message = "BaseImpl: x = $x"
override fun print() { println(message) }
}
class Derived(b: Base) : Base by b {
// 在 b 的 `print` 实现中不会访问到这个属性
override val message = "Message of Derived"
}
fun main() {
val b = BaseImpl(10)
val derived = Derived(b)
derived.print()
println(derived.message)
}
注意,以这种方式重写的成员不会在委托对象的成员中调用 ,委托对象的成员只能访问其自身对接口成员实现。
有一些常见的属性类型,虽然我们可以在每次需要的时候手动实现它们, 但是如果能够为大家把他们只实现一次并放入一个库会更好。例如包括:
为了涵盖这些(以及其他)情况,Kotlin 支持 委托属性:
class Example {
var p: String by Delegate()
}
语法是: val/var <属性名>: <类型> by <表达式>
。在 by 后面的表达式是该 委托, 因为属性对应的 get()
(与 set()
)会被委托给它的 getValue()
与 setValue()
方法。 属性的委托不必实现任何的接口,但是需要提供一个 getValue()
函数(与 setValue()
——对于 var 属性)
class Delegate {
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return "$thisRef, thank you for delegating '${property.name}' to me!"
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
println("$value has been assigned to '${property.name}' in $thisRef.")
}
}
by是ReadOnlyProperty的连接,
public fun interface ReadOnlyProperty {
/**
* Returns the value of the property for the given object.
* @param thisRef the object for which the value is requested.
* @param property the metadata for the property.
* @return the property value.
*/
public operator fun getValue(thisRef: T, property: KProperty<*>): V
}
所有我们在属性委托时,需要实现 public operator fun getValue(thisRef: T, property: KProperty<*>): V
所以定义属性委托必须要实现接口ReadOnlyProperty
在jetpack的datastore中,也提供了属性委托。
val Context.dataStore: DataStore by preferencesDataStore(
"my_datastore",
produceMigrations = { it ->
listOf(SharedPreferencesMigration(it, "sp_test"))
})
lambda 表达式与匿名函数是“函数字面值”,即未声明的函数, 但立即做为表达式传递
max(strings, { a, b -> a.length < b.length })
函数 max
是一个高阶函数,它接受一个函数作为第二个参数。 其第二个参数是一个表达式,它本身是一个函数,如下:
fun compare(a: String, b: String): Boolean = a.length < b.length
Lambda 表达式的完整语法形式如下:
val sum: (Int, Int) -> Int = { x: Int, y: Int -> x + y }
lambda 表达式总是括在花括号中, 完整语法形式的参数声明放在花括号内,并有可选的类型标注, 函数体跟在一个 ->
符号之后
val sum = { x: Int, y: Int -> x + y }
val sum: (Int, Int) -> Int = { x: Int, y: Int -> x + y }
sum(2,3)
val add = { x: Int, y: Int -> x + y }
add(2,3)
ints.filter {
val shouldFilter = it > 0
shouldFilter
}
ints.filter {
val shouldFilter = it > 0
return@filter shouldFilter
}
在开发过程中,发现使用集合都需要指定泛型,还有一种可以指定一个默认值,后面所有的集合都是这种
val numbersSet = setOf("one", "two", "three", "four")
val emptySet = mutableSetOf()
还有用于创建没有任何元素的集合的函数:emptyList()、emptySet() 与 emptyMap()。 创建空集合时,应指定集合将包含的元素类型。
要创建具体类型的集合,例如 ArrayList
或 LinkedList
,可以使用这些类型的构造函数。 类似的构造函数对于 Set
与 Map
的各实现中均有提供
val linkedList = LinkedList(listOf("one", "two", "three"))
val presizedSet = HashSet(32)
我们正常使用逻辑都是对于变量运算,但是在kotlin中的集合也可以使用
val plusList = numbers + "five"
val minusList = numbers - listOf("three", "four")
val minusList2 = numbers - "three"
println(plusList)
println(minusList)
println(minusList2)
Kotlin 标准库提供用于对集合元素进行分组的扩展函数。 基本函数 groupBy() 使用一个 lambda 函数并返回一个 Map。 在此 Map 中,每个键都是 lambda 结果,而对应的值是返回此结果的元素 List。 例如,可以使用此函数将 String 列表按首字母分组。
val numbers = listOf("one", "two", "three", "four", "five")
println(numbers.groupBy { it.first().toUpperCase() })
Kotlin 是一门仅在标准库中提供最基本底层 API 以便各种其他库能够利用协程的语言。与许多其他具有类似功能的语言不同,async
与 await
在 Kotlin 中并不是关键字,甚至都不是标准库的一部分。此外,Kotlin 的 挂起函数 概念为异步操作提供了比 future 与 promise 更安全、更不易出错的抽象。
kotlinx.coroutines
是由 JetBrains 开发的功能丰富的协程库。它包含本指南中涵盖的很多启用高级协程的原语,包括 launch
、 async
等等。
1.同步:runBlocking:
2.异步:kotlinx-coroutines-core库
代码库:
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.1"
class MySuspendMain {
suspend fun info() {
}
}
fun main() {
GlobalScope.launch { // 在后台启动一个新的协程并继续
delay(1000L) // 非阻塞的等待 1 秒钟(默认时间单位是毫秒)
println("World!") // 在延迟后打印输出
}
println("Hello,") // 协程已在等待时主线程还在继续
Thread.sleep(2000L) // 阻塞主线程 2 秒钟来保证 JVM 存活
runBlocking {
val mains=MySuspendMain()
mains.info()
}
}
由于kotlinx-coroutines-core库很大,我会出一篇详细的文章讲解。
Android kotlin实战之协程suspend详解与使用_蜗牛、Z的博客-CSDN博客