Navigation Compose Demo

Dependencies implementation("androidx.navigation:navigation-compose:2.9.2") Use class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { val navController = rememberNavController() NavigationDemoApp(navController) } } } @Composable fun NavigationDemoApp(navController: NavHostController) { NavHost(navController = navController, startDestination = "screen1") { composable("screen1") { Screen1(navController) } composable("screen2") { Screen2(navController) } } } @Composable fun Screen1(navController: NavController) { Column( modifier = Modifier.fillMaxSize(), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { Text(text = "Screen 1") Button(onClick = { navController.navigate("screen2") }) { Text(text = "Go to Screen 2") } } } @Composable fun Screen2(navController: NavController) { Column( modifier = Modifier.fillMaxSize(), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { Text(text = "Screen 2") Button(onClick = { navController.popBackStack() }) { Text(text = "Go back") } } } 官方指南 https://developer.android.com/develop/ui/compose/navigation ...

July 19, 2025 · 1 min

ViewModel Demo

dependencies implementation(libs.androidx.lifecycle.viewmodel.ktx) implementation(libs.androidx.lifecycle.livedata.ktx) 一个继承自 ViewModel 的类 class MainViewModel : ViewModel() { private val _number = MutableStateFlow(100) val number: StateFlow<Int> get() = _number } 使用它 class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() setContent { val mainViewModel: MainViewModel by viewModels() val numberState by mainViewModel.number.collectAsState() ViewModelDemoTheme { Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding -> Greeting( num = numberState, modifier = Modifier.padding(innerPadding) ) } } } } } @Composable fun Greeting(num: Int, modifier: Modifier = Modifier) { Text( text = num.toString(), modifier = modifier ) }

July 18, 2025 · 1 min

Type Checks Casts

is and !is operators is 用于执行运行时确定对象是否符合给定类型。 if (obj is String) { print(obj.length) } if (obj !is String) { // Same as !(obj is String) print("Not a String") } else { print(obj.length) } smart casts 核心原理 编译器自动跟踪类型检查结果,无需手动强制转换,直接使用目标类型的属性和方法。 fun demo(x: Any) { if (x is String) { print(x.length) // 编译器自动将 x 转换为 String 类型 } } 支持的控制流 if 条件:通过 is 或 !is 检查后,块内自动转换。 when 表达式:根据不同类型分支自动转换。 when (x) { is Int -> print(x + 1) is String -> print(x.length + 1) is IntArray -> print(x.sum()) } • while 循环:只要类型检查条件成立,循环内保持转换状态。 ...

July 6, 2025 · 2 min

Generator

生成器是一种特殊的迭代器,使用 延迟计算 生成元素,无需预先准备所有数据。在Kotlin中通过 iterator() 函数和 yield 关键字实现(协程的简化版)。那么我们在迭代器所实现的方法,也可以用生成器来替代。 class DateRange(val start: MyDate, val end: MyDate) : Iterable<MyDate> { override fun iterator(): Iterator<MyDate> = iterator { var current = start while (current <= end) { yield(current) current = current.followingDate() } } }

July 6, 2025 · 1 min

Iterable Iterator

简单场景描述 我们拥有一个数据类MyDate ,一个获取下一天的方法followingDate以及重写rangeTo操作符。 data class MyDate(val year: Int, val month: Int, val dayOfMonth: Int) : Comparable<MyDate> { override fun compareTo(other: MyDate): Int = when { year != other.year -> year - other.year month != other.month -> month - other.month else -> dayOfMonth - other.dayOfMonth } fun followingDate(): MyDate { val localDate = LocalDate.of(year, month, dayOfMonth) val nextDay = localDate.plusDays(1) return MyDate(nextDay.year, nextDay.monthValue, nextDay.dayOfMonth) } operator fun rangeTo(other: MyDate): DateRange = DateRange(this, other) } 我们想要使用for loop来遍历MyDate区间中的每一天。 fun iterateOverDateRange(firstDate: MyDate, secondDate: MyDate, handler: (MyDate) -> Unit) { for (date in firstDate..secondDate) { handler(date) } } 那么我们的目标是:Make the class DateRange implement Iterable<MyDate> ,正确答案如下。 ...

July 5, 2025 · 2 min

JPA OneToMany 和 ManyToOne 超简单入门示例

什么是 OneToMany 和 ManyToOne? OneToMany(一对多):一个东西可以对应多个另一个东西,比如一个用户有多个好友关系 ManyToOne(多对一):多个东西对应一个东西,比如多个好友关系都属于同一个用户 用好友关系举例理解 1. 用户实体(One 方) @Entity data class User { @Id var id: Long? = null var username: String = "" // 一个用户有多个好友关系,mappedBy 指向关联的属性名 @OneToMany(mappedBy = "user") var friendRelations: List<Friendship> = emptyList() } 2. 好友关系实体(Many 方) @Entity data class Friendship { @Id var id: Long? = null // 多个好友关系属于一个用户,@JoinColumn 定义外键列 @ManyToOne @JoinColumn(name = "user_id") var user: User? = null var friendId: Long = 0 } 核心配置一句话解释 @OneToMany:放在 “一” 的那一方,告诉 JPA 这个东西可以有多个关联对象 mappedBy = “user”:表示关联关系由对方(Friendship)的 user 属性维护 @ManyToOne:放在 “多” 的那一方,表示这个东西属于另一个东西 @JoinColumn(name = “user_id”):在数据库表中创建 user_id 列作为外键

July 5, 2025 · 1 min

Nothing type

Nothing type can be used as a return type for a function that always throws an exception. When you call such a function, the compiler uses the information that the execution doesn’t continue beyond the function. import kotlin.IllegalArgumentException fun failWithWrongAge(age: Int?) { throw IllegalArgumentException("Wrong age: $age") } fun checkAge(age: Int?) { if (age == null || age !in 0..150) failWithWrongAge(age) println("Congrats! Next year you'll be ${age + 1}.") } fun main() { checkAge(10) } 如果这里failWithWrongAge 方法没有标明返回值类型为Nothing ,那么 the checkAge function doesn’t compile because the compiler assumes the age can be null.你应该 Specify Nothing return type for the failWithWrongAge function. ...

July 2, 2025 · 1 min

Named Arguments

Make the function joinOptions() return the list in a JSON format (for example, [a, b, c]) by specifying only two arguments. Default and named arguments help to minimize the number of overloads and improve the readability of the function invocation. The library function joinToString is declared with default values for parameters: **fun** joinToString( separator: String = ", ", prefix: String = "", postfix: String = "", /* ... */ ): String It can be called on a collection of Strings. ...

July 2, 2025 · 1 min

lazy

Kotlin 中的 lazy 在 Kotlin 中,lazy 是一个用于延迟初始化属性或值的函数。这意味着一个属性的值直到第一次被访问时才会被计算。这在以下场景中非常有用: 性能优化: 如果一个对象的创建成本很高,但它不总是立即需要,那么 lazy 可以避免不必要的计算,从而提高应用程序的启动速度或响应能力。 资源管理: 当初始化某个对象会占用大量资源(例如,数据库连接、文件句柄等)时,使用 lazy 可以确保这些资源只在真正需要时才被分配。 处理循环依赖: 在某些复杂对象模型中,可能存在对象之间的循环依赖。lazy 有时可以帮助解决这种问题,因为它允许你推迟一个对象的初始化,直到另一个对象已经完全构建。 lazy 的基本用法 lazy 函数接受一个 lambda 表达式作为参数,这个 lambda 表达式定义了如何计算被延迟初始化的值。它返回一个 Lazy<T> 实例,你可以通过调用其 value 属性来获取实际的值。 val myLazyValue: String by lazy { println("正在初始化 myLazyValue...") "这是一个延迟初始化的字符串" } fun main() { println("程序开始运行") println(myLazyValue) // 第一次访问,会触发初始化 println(myLazyValue) // 第二次访问,直接使用已初始化的值 } 输出: 程序开始运行 正在初始化 myLazyValue... 这是一个延迟初始化的字符串 这是一个延迟初始化的字符串 从上面的输出可以看出,“正在初始化 myLazyValue…” 这句话只打印了一次,证明了 myLazyValue 只在第一次访问时才被初始化。

July 2, 2025 · 1 min

Singleton Pattern

单例模式是一种创建型设计模式,它的核心思想是确保一个类在整个应用程序运行期间只能创建一个实例,并提供全局访问这个实例的方法。 想象一个公司只有一台打印机,所有员工都需要使用它。你不会为每个员工都买一台打印机,而是让大家共享这一台。单例模式就是这个道理 - 对于某些资源,我们只需要一个实例就够了。 1. Object 关键字实现(最推荐) // 1. Object关键字实现单例 object DatabaseManager { private var connectionCount = 0 init { println("DatabaseManager 初始化") connect() } private fun connect() { println("连接到数据库") connectionCount++ } fun executeQuery(sql: String): String { return "执行查询: $sql, 连接数: $connectionCount" } fun getConnectionCount(): Int = connectionCount } // 使用示例 fun main() { println("=== Object关键字单例示例 ===") // 直接使用,无需获取实例 println(DatabaseManager.executeQuery("SELECT * FROM users")) println(DatabaseManager.executeQuery("SELECT * FROM orders")) println("连接数: ${DatabaseManager.getConnectionCount()}") // 验证是同一个实例 val ref1 = DatabaseManager val ref2 = DatabaseManager println("是否为同一个实例: ${ref1 === ref2}") } Object 关键字的特点 自动线程安全:Kotlin 编译器保证 object 的初始化是线程安全的,使用双重检查锁定机制。 懒加载:object 在第一次被访问时才会初始化,不是在程序启动时。 简洁性:代码最简洁,无需手动管理实例。 内存效率:编译后生成的字节码与手写的单例模式相当。 适用场景:大多数单例需求都可以用 object 解决,特别是工具类、管理器类等。 Object 关键字不够用的地方 需要构造参数 需要依赖注入 需要生命周期管理 需要复杂继承关系 什么条件需要单例模式? 资源管理类:数据库连接池、文件系统访问、网络连接管理器等需要统一管理系统资源的场景。 配置管理:应用程序配置、用户偏好设置等需要全局访问且保持一致性的数据。 缓存系统:内存缓存、图片缓存等需要全局共享且避免重复创建的组件。 日志记录器:应用日志管理,需要统一的日志记录入口。 硬件接口控制:打印机驱动、传感器控制等只能有一个实例操作的硬件接口。 什么条件能用单例模式? 无状态或状态不可变:单例对象最好是无状态的,或者状态是只读的,避免并发修改问题。 线程安全考虑:如果单例会被多线程访问,必须确保线程安全,Kotlin 的 object 关键字天然线程安全。 生命周期明确:单例的生命周期应该与应用程序一致,不需要手动销毁和重建。 依赖关系简单:单例不应该依赖太多外部资源,避免复杂的初始化逻辑。 测试友好:考虑到单元测试的需要,可以设计成可测试的形式,比如提供重置方法或使用依赖注入。

July 1, 2025 · 1 min