Android开发从java到kotlin

272 阅读8分钟

(TL;TR)相对于Java的高级操作

  1. 关于new:在Kotlin中,不需要使用new关键字来实例化对象,而是直接调用构造函数。
  2. 扩展函数: Kotlin允许你为任何已有的类定义新的方法,这意味着你可以为Java的集合类添加新的功能而无需继承或设计模式。例如,你可以为List定义一个扩展函数来轻松地打印其内容。
  3. 空安全: Kotlin的集合默认是空安全的,这意味着当你尝试访问一个不存在的元素时,它不会抛出NullPointerException,而是返回一个默认值或使用安全调用操作符(?.)来处理可能的null值。
  4. 解构声明: Kotlin允许你解构集合,例如将Pair或Map的键值对直接解构到变量中。例如:val (key, value) = mapOf("key" to "value").first()
  5. Lambda表达式与高阶函数: Kotlin的集合类与Java的不同之处在于它们大量使用了lambda表达式和高阶函数。这使得代码更加简洁且易于阅读。例如,使用filtermap来过滤和转换集合中的元素。
  6. 内联类与类型别名: Kotlin提供了内联类和类型别名的功能,这使得在处理特定类型的集合时更为方便和类型安全。
  7. 集合字面量: 与Java相比,Kotlin提供了更简洁的方式来创建集合,如使用listOf()setOf()mapOf()等函数。
  8. 默认参数与命名参数: 当使用Kotlin的集合API时,你可以利用默认参数和命名参数的功能来简化函数调用并提供更清晰的代码意图。
  9. 协程: 协程是 Kotlin 中一种用于简化异步编程的工具。相对于传统的回调、线程和异步任务,协程提供了更加轻量级、可读性更强、更易于维护的方式来处理并发和异步操作。

Kotlin 与 Java 语法差异

变量

语法JavaKotlin
声明int a = 1;var a = 1;
初始化可省略必须初始化
类型必须指定可省略,编译器会根据初始化值推断类型
赋值a = 2;a = 2;
运算相同

1. 基本的语法规则:

var <propertyName>[: <PropertyType>] [= <property_initializer>]
    [<getter>]
    [<setter>]
  • 使用 val 关键字声明不可变变量(常量),其值一旦赋值就不能再改变。
  • 使用 var 关键字声明可变变量,其值可以在后续代码中重新赋值。
  • 类型声明可以省略,由编译器根据初始值进行类型推断。
  • 变量名和类型之间用冒号分隔。
  • 变量定义可以包含初始值,也可以在后续代码中赋值。

2. 示例:

// 不可变变量(常量)的定义(val)
val pi: Double = 3.14
val message: String = "Hello, Kotlin!"

// 类型推断,省略类型声明
val inferenceExample = "Type inference is awesome!"

// 可变变量的定义(var)
var count: Int = 42
count += 8 // 可以修改值

// 类型声明和初始值都可以省略
var anotherCount = 42
anotherCount += 8 // 可以修改值

3. lateinit 的使用:

lateinit 用于延迟初始化,主要用于非空类型的属性,在声明时不需要立即初始化,但在使用之前必须初始化。lateinit 仅适用于类体中的属性,以及主构造函数中声明的属性。

class Example {
    lateinit var lateInitializedProperty: String

    fun initialize() {
        lateInitializedProperty = "Initialized!"
    }

    fun printValue() {
        if (::lateInitializedProperty.isInitialized) {
            println(lateInitializedProperty)
        } else {
            println("Property not initialized yet.")
        }
    }
}

fun main() {
    val example = Example()
    // 使用前必须调用初始化方法
    example.initialize()
    example.printValue() // 输出: Initialized!
}

4. by 关键字的使用:

by 关键字用于委托属性,允许你将属性的 getter 和 setter 委托给其他对象。常见的委托属性包括 lazyobservable 等。

import kotlin.properties.Delegates

class Example {
    // 使用 by lazy 委托,只有在首次访问时才会计算值
    val lazyValue: String by lazy {
        println("Computed!")
        "Lazy Value"
    }

    // 使用 by Delegates.observable 委托,当值发生变化时执行回调
    var observableValue: String by Delegates.observable("Initial Value") { _, oldValue, newValue ->
        println("Old Value: $oldValue, New Value: $newValue")
    }
}

fun main() {
    val example = Example()

    // 访问 lazyValue,触发计算值
    println(example.lazyValue) // 输出: Computed! Lazy Value
    // 再次访问 lazyValue,不再计算值
    println(example.lazyValue) // 输出: Lazy Value

    // 修改 observableValue,触发回调
    example.observableValue = "Updated Value" // 输出: Old Value: Initial Value, New Value: Updated Value
}

理解 Koltin 中的幕后字段和幕后属性:wavever.github.io/2020/03/25/…

函数

语法JavaKotlin
声明public void foo() { ... }fun foo() { ... }
参数相同
返回值可省略必须指定
调用foo();foo();
参数传递相同
返回值相同

在 Kotlin 中,函数定义语法相对简洁而灵活。以下是 Kotlin 中函数定义的基本语法:

fun functionName(parameter1: Type1, parameter2: Type2, ...): ReturnType {
    // 函数体
    // 可以包含表达式、语句等
    return result // 返回类型为 ReturnType 的值
}

其中,关键字 fun 用于声明一个函数。函数名 functionName 是函数的标识符,参数列表包含参数名称和类型,用括号括起来。冒号 : 后面是返回类型 ReturnType,如果函数没有返回值,可以省略返回类型。

以下是一个具体的例子:

fun sum(a: Int, b: Int): Int {
    return a + b
}

在这个例子中,sum 是一个函数,接收两个整数参数 ab,返回它们的和。

函数还支持默认参数、可变参数、局部函数等高级特性,以下是一些其他示例:

默认参数:

fun greet(name: String = "Guest") {
    println("Hello, $name!")
}

greet() // 输出: Hello, Guest!
greet("John") // 输出: Hello, John!

可变参数:

fun calculateSum(vararg numbers: Int): Int {
    return numbers.sum()
}

val sum = calculateSum(1, 2, 3, 4, 5) // 返回 15

单表达式函数:

对于单表达式的函数,可以简化为如下形式:

fun sum(a: Int, b: Int): Int = a + b

扩展函数

  • Java: 不支持。
  • Kotlin: 支持。允许为现有类添加新的方法,不需要继承或实现任何接口。
语法JavaKotlin
声明public static void println(String s) { ... }fun String.println() { ... }
使用System.out.println("Hello, world!");"Hello, world!".println()
优势可扩展现有类的功能,无需继承
fun String.customPrint() {  

    println("Custom print function for String")  

}

更深入了解:legacy.kotlincn.net/docs/refere…

在 Kotlin 中,类的定义和使用相对于 Java 更为简洁且具有一些额外的特性。以下是 Kotlin 类的基本语法:

class MyClass {
    // 类的成员变量和方法
}

主构造函数

Kotlin 类的主构造函数是类的构造函数,它位于类的定义中,没有构造函数关键字。主构造函数可以有参数,也可以没有参数。

如果主构造函数有参数,那么这些参数必须在类名后面声明。主构造函数的参数可以是任意类型,包括基本类型、引用类型、泛型类型等。

以下是一个带有参数的主构造函数的例子:

class Person(val name: String, val age: Int) {
    // ...
}

这段代码定义了一个 Person 类,它有一个主构造函数,有两个参数 nameage

如果主构造函数没有参数,那么可以省略类名后的参数列表。

以下是一个没有参数的主构造函数的例子:

class Point() {
    // ...
}

这段代码定义了一个 Point 类,它有一个主构造函数,没有参数。

主构造函数可以通过 constructor 关键字来显式声明。

以下是一个显式声明主构造函数的例子:

class Person {
    constructor(name: String, age: Int) {
        // ...
    }
}

这段代码与前面的例子是等价的。

主构造函数可以使用 init 块来初始化类的属性。

以下是一个使用 init 块来初始化属性的例子:

class Person(val name: String, val age: Int) {
    init {
        println("name = $name, age = $age")
    }
}

这段代码将在 Person 对象被创建时打印出 nameage 属性的值。

次构造函数

class Person(val name: String, val age: Int) {
    // 主构造函数

    constructor(name: String) : this(name, 0) {
        // 次构造函数,一定要调用主构造函数
    }
}

成员变量和方法

class MyClass {
    var myVariable: Int = 0

    fun myFunction() {
        // 函数体
    }
}

继承与接口

继承

open class Animal(val name: String) {
    fun makeSound() {
        println("Some generic sound")
    }
}

class Dog(name: String) : Animal(name) {
    override fun makeSound() {
        println("Woof!")
    }
}

接口

语法JavaKotlin
声明public interface Runnable { void run(); }interface Runnable { fun run(); }
方法抽象方法抽象方法
实现class MyRunnable implements Runnable { public void run() { ... } }class MyRunnable : Runnable { override fun run() { ... } }
优势可使用委托
interface Shape {
    fun area(): Double
}

class Circle(val radius: Double) : Shape {
    override fun area(): Double {
        return 3.14 * radius * radius
    }
}

可见性修饰符

class MyClass {
    public val publicVariable: Int = 0
    private val privateVariable: Int = 1
    internal val internalVariable: Int = 2
    protected val protectedVariable: Int = 3
}

数据类

data class Person(val name: String, val age: Int)

数据类自动生成 equals()hashCode()toString() 等方法,用于简化类的定义。

密封类

sealed class Result {
    class Success(val data: String) : Result()
    class Error(val message: String) : Result()
}

密封类用于限制类的继承结构,在声明的文件内所有子类必须嵌套在密封类中。

以上是 Kotlin 中类的一些基本语法,涵盖了类的定义、构造函数、成员变量、方法、继承、接口、可见性修饰符、数据类和密封类等方面。这些特性使得 Kotlin 中的类更为灵活且易于使用。

总结:转变过程

语法JavaKotlin
变量int a = 1;var a = 1;
函数public void foo() { ... }fun foo() { ... }
扩展函数public static void println(String s) { ... }fun String.println() { ... }
public class Person { ... }class Person { ... }
接口public interface Runnable { void run(); }interface Runnable { fun run(); }

对象表达式(Object Expressions)

对象表达式用于创建一个匿名对象,通常用于创建实现某个接口或继承自某个类的对象。

// Kotlin - 对象表达式
interface MyInterface {
    fun myFunction()
}

val myObject = object : MyInterface {
    override fun myFunction() {
        println("Function implementation")
    }
}

myObject.myFunction() // 调用接口方法

伴生对象(Companion Objects)

伴生对象用于在类内部创建一个单例对象,可以通过类名直接访问,类似于 Java 中的静态成员。

// Kotlin - 伴生对象
class MyClass {
    companion object {
        const val myConstant = 42

        fun myFunction() {
            println("Function in companion object")
        }
    }
}

val constantValue = MyClass.myConstant
MyClass.myFunction()

对象声明(Object Declarations)

对象声明用于创建单例对象,通过 object 关键字直接定义对象,可以直接访问对象的成员。

// Kotlin - 对象声明
object MySingleton {
    const val myConstant = 42

    fun myFunction() {
        println("Function in object declaration")
    }
}

val constantValue = MySingleton.myConstant
MySingleton.myFunction()

匿名对象(Anonymous Objects)

匿名对象用于创建一个对象实例,并且不声明其类型,通常用于作为函数的参数或返回值。

// Kotlin - 匿名对象
fun createAndUseObject(obj: Any) {
    println(obj.toString())
}

val myAnonymousObject = object {
    val name = "John"
    val age = 30
}

createAndUseObject(myAnonymousObject)

在这个例子中,myAnonymousObject 是一个匿名对象,被传递给 createAndUseObject 函数,该函数接受任意类型的参数。

这四种用法展示了 object 关键字在 Kotlin 中的多样性,可以用于创建对象表达式、伴生对象、对象声明和匿名对象,每种用法都有其独特的应用场景。

集合操作

Kotlin 集合操作函数,这些函数允许你以一种更为声明性和函数式的方式处理集合。以下是其中几个常用的集合操作函数: Kotlin 关于集合的高级函数主要包括以下几类:

  • 转换函数:用于将集合转换为另一种类型。例如,map() 函数可以将集合中的每个元素映射到另一个值,flatMap() 函数可以将集合中的每个元素转换为另一个集合,groupBy() 函数可以根据某个条件将集合中的元素分组。
  • 过滤函数:用于从集合中过滤出符合条件的元素。例如,filter() 函数可以过滤出集合中满足某个条件的元素,take() 函数可以从集合中取出前 n 个元素,drop() 函数可以从集合中丢弃前 n 个元素。
  • 聚合函数:用于对集合中的元素进行聚合操作。例如,sum() 函数可以求集合中所有元素的和,average() 函数可以求集合中所有元素的平均值,count() 函数可以统计集合中的元素个数。
  • 查找函数:用于查找集合中符合条件的元素。例如,find() 函数可以查找集合中第一个满足某个条件的元素,any() 函数可以判断集合中是否存在满足某个条件的元素,all() 函数可以判断集合中所有元素是否都满足某个条件。

以下是一些常用的集合高级函数的示例:

Kotlin

// 转换函数
val numbers = listOf(1, 2, 3, 4, 5)
val strings = numbers.map { it.toString() }
println(strings) // [1, 2, 3, 4, 5]

// 过滤函数
val evenNumbers = numbers.filter { it % 2 == 0 }
println(evenNumbers) // [2, 4]

// 聚合函数
val sum = numbers.sum()
println(sum) // 15

// 查找函数
val firstEvenNumber = numbers.find { it % 2 == 0 }
println(firstEvenNumber) // 2

val hasEvenNumber = numbers.any { it % 2 == 0 }
println(hasEvenNumber) // true

val allEvenNumbers = numbers.all { it % 2 == 0 }
println(allEvenNumbers) // false

协程

什么是协程?

协程是一种轻量级的并发编程机制,允许在代码中以顺序的方式表达异步操作,而不是采用传统的回调或者线程的方式。它是一种用户空间的轻量级线程,由开发者控制调度,并且可以在需要时挂起和恢复,避免了传统线程模型的开销。与线程和进程相比,协程更轻量,更灵活,更易于使用。

区别:

  • 线程: 是操作系统级别的执行单元,由操作系统进行调度。线程切换的开销相对较大,创建和销毁线程需要操作系统的介入。多线程之间共享同一进程的资源。
  • 进程: 是独立的执行单元,拥有自己的内存空间和资源。进程之间通常通过进程间通信(IPC)来进行数据交换。
  • 协程: 是用户空间的执行单元,由开发者控制调度。协程之间的切换开销相对较小,不需要操作系统介入。协程可以在同一线程内并发执行,共享线程的资源。

协程的优势

优势:

  • 轻量级: 协程是轻量级的,可以在单个线程上运行大量协程,而不引入过多的线程开销。
  • 可读性: 协程的代码结构更加顺序和直观,避免了回调地狱,使得异步代码更易读。
  • 异常处理: 协程提供了类似同步代码的异常处理机制,可以使用 trycatch 来捕获异步操作中的异常。
  • 取消机制: 协程提供了协作式的取消机制,可以方便地取消整个协程的执行。

轻量级:

// Kotlin
suspend fun fetchData() {
    // 挂起函数,可以在此执行异步操作
    delay(1000) // 模拟异步操作
    println("Data fetched!")
}

runBlocking {
    repeat(5) {
        // 启动协程,不会创建新线程
        launch {
            fetchData()
        }
    }
}

挂起和恢复:

// Kotlin
suspend fun fetchData(): String {
    delay(1000)
    return "Data fetched!"
}

runBlocking {
    val result = async {
        fetchData()
    }

    println("Do something else while waiting for async operation...")
    val data = result.await() // 挂起,等待异步操作完成
    println(data)
}

顺序性代码结构:

// Kotlin
suspend fun fetchData(id: Int): String {
    delay(1000)
    return "Data for id $id"
}

runBlocking {
    val data1 = async { fetchData(1) }
    val data2 = async { fetchData(2) }

    println("Processing data...")
    val result1 = data1.await()
    val result2 = data2.await()

    println("Results: $result1, $result2")
}

异常处理:

// Kotlin
suspend fun fetchData(): String {
    delay(1000)
    throw RuntimeException("Error fetching data")
}

runBlocking {
    try {
        val result = async { fetchData() }
        println(result.await())
    } catch (e: Exception) {
        println("Error: ${e.message}")
    }
}

取消:

// Kotlin
suspend fun fetchData(): String {
    delay(1000)
    return "Data fetched!"
}

runBlocking {
    val job = launch {
        repeat(5) {
            // 在协程内部可以检查取消状态
            if (isActive) {
                fetchData()
            }
        }
    }

    delay(2000) // 取消协程
    job.cancel()
}

代码简洁性:

// Kotlin
suspend fun fetchData(): String {
    delay(1000)
    return "Data fetched!"
}

runBlocking {
    repeat(3) {
        // 使用协程简化异步操作
        val result = async { fetchData() }
        println(result.await())
    }
}

Kotlin协程高级语法与使用示例

1. Flow:

Flow是Kotlin中用于处理异步数据流的工具。它允许你以声明式的方式定义数据流,并对其进行操作、转换和组合。

示例:

import kotlinx.coroutines.*  

import kotlinx.coroutines.flow.*  



// 定义一个Flow<Int>  

val flow = flow { emit(1) }  

    .map { it * 2 } // 转换操作,将每个元素乘以2  

    .filter { it > 5 } // 过滤操作,只保留大于5的元素  

    .toList() // 将流转换为列表  



// 启动协程并获取结果  

flow.collect { value ->  

    println(value) // 输出:[10]  

}

2. Channel:

Channel是Kotlin协程中用于发送和接收数据的工具。它允许你在协程之间进行异步通信。

示例:

import kotlinx.coroutines.*  

import kotlinx.coroutines.channels.*  



// 创建Channel<Int>  

val channel = Channel<Int>()  



// 启动一个协程发送数据到Channel  

launch {  

    delay(1000) // 模拟耗时操作  

    channel.send(42) // 发送数据到Channel  

}  



// 在另一个协程中从Channel接收数据  

launch {  

    delay(2000) // 等待发送方完成发送数据  

    val value = channel.receive() // 从Channel接收数据,这里会阻塞直到有数据可用  

    println(value) // 输出:42  

}

3. 使用协程与Flow结合:

你可以结合使用协程与Flow来处理异步操作,并在需要时将结果返回到主线程。

示例:

假设我们有一个异步API调用,返回一个Flow:

	fun fetchData(): Flow<Data> = flow { emit(fetchFromApi()) } // 假设fetchFromApi()是一个异步API调用返回Data对象

我们可以使用launch来启动一个协程,并使用collect来收集结果:

launch {  

    val dataList = fetchData().toList() // 从Flow获取列表数据,这会触发API调用并等待其完成  

    withContext(Dispatchers.Main) { // 将结果返回到主线程进行UI更新等操作  

        val adapter = RecyclerView.Adapter<MyAdapter>(MyAdapter()) // 假设我们在RecyclerView中使用该列表数据,并进行适配器的更新操作  

        myRecyclerView.adapter = adapter // 将适配器设置为RecyclerView的适配器,这会触发UI的更新操作  

    }  

}

在上述示例中,我们使用了Kotlin协程和Flow来异步处理API调用并返回数据,然后将数据返回到主线程进行UI更新操作。这是Kotlin协程与Flow结合的一个典型应用场景。 这些示例演示了 Kotlin 协程在不同方面的应用,包括并发、异步操作、异常处理和取消机制,以及如何通过协程简化代码。请注意,这只是简单的示例,实际使用时可能需要更复杂的场景和实践。

Flow vs. Channel:

  • Flow 是冷流,按需发送和接收数据。
  • Channel 是热流,数据发送和接收是主动的。
  • Flow 更适合处理数据流,Channel 更适合协程之间的通信。

在 Flow 中,数据是按需产生的,只有当有收集者(collect)时,才会触发流的执行。而 Channel 则是主动推送数据给接收者,适合于协程之间的实时通信。

综上所述,Flow 和 Channel 提供了不同的抽象层级,可以根据场景选择使用,Flow 用于处理数据流,Channel 用于协程之间的通信。

参考网站

legacy.kotlincn.net/docs/refere…