受控组件 = React当老板 输入框的值完全由React state控制,必须配套:有value就必须有onChange 实时同步:每打一个字,state就更新,组件就重新渲染 受控组件:React控制一切 const [text, setText] = useState(''); <input value={text} // React说:你的值是这个 onChange={(e) => setText(e.target.value)} // 用户输入时,React更新state /> 非受控组件 = DOM当老板 DOM说了算,输入框的值由浏览器DOM管理,React只是看客,需要的时候通过ref去DOM那里要值 懒得管理,React不关心你输入什么,需要时才去看 const inputRef = useRef(); <input ref={inputRef} // 给input一个引用 defaultValue="初始值" // 只设置初始值,之后DOM自己管 /> 总结 受控 = value + onChange(缺一不可) 非受控 = ref + defaultValue(要值时去DOM要) file类型永远非受控(文件只能通过DOM获取) 什么时候用哪个? 需要实时验证 → 用受控(比如密码长度检查) 简单表单提交 → 用非受控(比如登录表单) 文件上传 → 必须非受控(file类型限制)
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 ...
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 ) }
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 循环:只要类型检查条件成立,循环内保持转换状态。 ...
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() } } }
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> ,正确答案如下。 ...
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 列作为外键
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. ...
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. ...
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 只在第一次访问时才被初始化。