Kotlin知识体系(一) : Kotlin的五大基础语法特性
前言
在Android开发领域,Kotlin凭借其简洁性和安全性已成为官方推荐语言。本文将通过Kotlin的五大基础语法特性,结合实际应用场景展示它们在Android开发中的独特价值。
一、变量声明:val与var的哲学
1.1 不可变优先原则
Kotlin的val
关键字用于声明不可变变量(相当于Java的final),这是构建可靠Android应用的基石:
val PI = 3.14159 // 类型推断为Double
val context: Context = requireContext() // Android组件上下文
最佳实践:
• 在View绑定中优先使用val
val binding: MainBinding by viewBinding()
• 数据类属性推荐使用val
保证线程安全
data class User(val id: Long, val name: String)
1.2 可变变量的使用
var
用于需要修改的变量,在Android开发中常见于:
var currentState = State.LOADING // 页面状态管理
var lastClickTime = 0L // 防重复点击计时
生命周期注意点:
• Fragment/Activity中的var
变量需要在onSaveInstanceState
中保存状态
• ViewModel中的在Fragment/Activity中用到的可变状态建议使用LiveData或StateFlow包装,因为它们可以感知生命周期
二、类型推断的艺术
2.1 智能类型推断
Kotlin编译器能自动推断大部分变量类型:
val appName = "MyApp" // String
val versionCode = 42 // Int
val dpValue = 16f // Float
2.2 显式类型声明的场景
以下情况需要显式声明类型:
// 可空类型初始化
val cacheDir: File? = getExternalCacheDir()
// 接口类型声明
val callback: OnClickListener = View.OnClickListener { ... }
// 复杂泛型类型
val configMap: Map<String, Pair<Int, Boolean>> = mapOf(...)
Android资源获取示例:
val colorRes = when (isDarkTheme) {
true -> R.color.dark_text
false -> R.color.light_text
}
val textColor = ContextCompat.getColor(context, colorRes)
三、函数设计的现代化方案
3.1 基础函数结构
fun createIntent(context: Context, clazz: Class<*>): Intent {
return Intent(context, clazz).apply {
putExtra(KEY_VERSION, BuildConfig.VERSION_CODE)
}
}
3.2 默认参数与命名参数
重构传统Builder模式:
fun showDialog(
title: String = "提示",
message: String = "",
confirmText: String = "确定",
cancelable: Boolean = true
) {
// Dialog初始化逻辑
}
// 使用示例
showDialog(
message = "是否保存修改?",
confirmText = "保存"
)
在Fragment工厂方法中的应用:
fun newInstance(
id: Long,
type: String = TYPE_DEFAULT,
from: String? = null
): DetailFragment {
return DetailFragment().apply {
arguments = bundleOf(
ARG_ID to id,
ARG_TYPE to type,
ARG_FROM to from
)
}
}
四、字符串模板的进阶用法
4.1 基础插值
val apiUrl = "${BuildConfig.BASE_URL}/api/v${BuildConfig.API_VERSION}"
4.2 复杂表达式处理
fun formatLog(tag: String, vararg params: Any): String {
return "[${System.currentTimeMillis()}] $tag - ${params.joinToString()}".trimIndent()
}
4.3 多行字符串处理
使用"""
val sqlQuery = """
SELECT * FROM ${UserTable.TABLE_NAME}
WHERE ${UserTable.COLUMN_AGE} > ?
AND ${UserTable.COLUMN_STATUS} = 'ACTIVE'
ORDER BY ${UserTable.COLUMN_CREATED_AT} DESC
""".trimIndent()
五、空安全机制的使用
5.1 安全调用链
// 处理RecyclerView的ViewHolder
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
getItem(position)?.let { item ->
holder.bind(item)
} ?: run {
holder.clear()
}
}
5.2 Elvis操作符的妙用
// SharedPreferences安全读取
fun getPreferenceString(key: String): String {
return prefs.getString(key, null) ?: ""
}
// 网络响应处理
val response = apiService.getData()
val data = response.body() ?: throw IOException("Empty response body")
5.3 平台类型处理
处理Java库返回的类型:
// Retrofit回调处理
override fun onResponse(call: Call<User>, response: Response<User>) {
val user = response.body() as? User ?: return
updateUI(user)
}
String!
并非 Kotlin 中常规的类型声明,而是在 Kotlin 与 Java 交互时,用于表示从 Java 代码获取的 String 类型的平台类型。
对于平台类型,Kotlin 编译器不会自动进行可空性检查,开发者需要手动处理可能的 null 值。
5.4 非空断言的合理使用
// 生命周期安全的断言
fun requireUser(): User {
return arguments?.getParcelable(ARG_USER)!!
}
// 测试环境验证
fun testLoginFlow() {
val mockUser = MockUserProvider.user!!
startLoginFlow(mockUser)
}
空安全最佳实践:
- 优先使用
?.
安全调用和Elvis操作符 - 仅在确定非空时使用
!!
,并添加必要的注释说明 - 使用
@Nullable
/@NotNull
注解改进Java互操作性 - 对可能为空的集合使用
filterNotNull()
- 配合
lateinit var
处理生命周期确定的初始化
六、Android开发中的综合应用
示例:安全的网络请求处理
fun loadData() {
viewModelScope.launch {
try {
val response = repository.fetchData()?.body()
?: throw IllegalStateException("Empty response")
_uiState.value = response.data?.let { data ->
UIState.Success(data)
} ?: UIState.Error("Invalid data format")
} catch (e: Exception) {
_uiState.value = UIState.Error(e.message ?: "Unknown error")
}
}
}
完整代码如下
// 1. 状态密封类定义
sealed class UIState<T> {
object Loading : UIState<Nothing>()
data class Success<T>(val data: T) : UIState<T>()
data class Error(val message: String, val code: Int = -1) : UIState<Nothing>()
object Empty : UIState<Nothing>()
}
// 2. 数据层Repository
interface DataRepository {
suspend fun fetchData(): Response<DataModel>
}
class RemoteDataRepository(
private val apiService: ApiService,
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO
) : DataRepository {
override suspend fun fetchData(): Response<DataModel> =
withContext(ioDispatcher) {
try {
apiService.getData()
} catch (e: IOException) {
throw NetworkException("网络连接异常", e)
} catch (e: Exception) {
throw UnknownException("未知错误", e)
}
}
}
// 3. 自定义异常类型
sealed class AppException : Exception() {
data class NetworkException(val errorMsg: String, val cause: Throwable?) : AppException()
data class ParseException(val rawData: String, val cause: Throwable?) : AppException()
data class UnknownException(val context: String, val cause: Throwable?) : AppException()
}
// 4. ViewModel完整实现
class DataViewModel(
private val repository: DataRepository
) : ViewModel() {
private val _uiState = MutableStateFlow<UIState<DataModel>>(UIState.Loading)
val uiState: StateFlow<UIState<DataModel>> = _uiState.asStateFlow()
fun loadData() {
viewModelScope.launch {
_uiState.value = UIState.Loading
try {
val response = repository.fetchData()
when {
response.isSuccessful -> {
response.body()?.let { data ->
if (data.isValid()) {
_uiState.value = UIState.Success(data)
} else {
_uiState.value = UIState.Empty
}
} ?: run {
_uiState.value = UIState.Error("空响应体")
}
}
else -> {
val errorBody = response.errorBody()?.string() ?: ""
_uiState.value = UIState.Error(
message = "错误码: ${response.code()}",
code = response.code()
)
// 记录原始错误信息
logError("API Error: $errorBody")
}
}
} catch (e: AppException) {
_uiState.value = when (e) {
is AppException.NetworkException ->
UIState.Error("网络异常: ${e.errorMsg}")
is AppException.ParseException ->
UIState.Error("数据解析失败")
else ->
UIState.Error("系统错误: ${e.message ?: "未知原因"}")
}
} catch (e: Exception) {
_uiState.value = UIState.Error("未知错误: ${e.localizedMessage}")
}
}
}
private fun logError(message: String) {
Timber.tag("Networking").e(message)
// 上报错误到监控系统
Crashlytics.logException(Exception(message))
}
}
// 5. UI层观察处理(Activity/Fragment)
class MainActivity : AppCompatActivity() {
private val binding by viewBinding(ActivityMainBinding::inflate)
private val viewModel by viewModels<DataViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setupObservers()
setupRetryButton()
viewModel.loadData()
}
private fun setupObservers() {
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState.collect { state ->
when (state) {
is UIState.Loading -> showLoading()
is UIState.Success -> showData(state.data)
is UIState.Error -> showError(state.message)
UIState.Empty -> showEmptyView()
}
}
}
}
}
private fun showLoading() {
binding.progress.visible()
binding.errorView.gone()
binding.emptyView.gone()
}
private fun showData(data: DataModel) {
binding.progress.gone()
binding.dataView.apply {
text = data.formattedString
visible()
}
}
private fun showError(message: String) {
binding.progress.gone()
binding.errorView.apply {
errorText.text = message
visible()
}
}
private fun showEmptyView() {
binding.progress.gone()
binding.emptyView.visible()
}
private fun setupRetryButton() {
binding.errorView.retryButton.setOnClickListener {
viewModel.loadData()
}
}
}
// 6. 网络层配置(Retrofit示例)
object RetrofitClient {
private const val BASE_URL = "https://api.example.com/v2/"
val instance: ApiService by lazy {
Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(MoshiConverterFactory.create())
.client(createOkHttpClient())
.build()
.create(ApiService::class.java)
}
private fun createOkHttpClient(): OkHttpClient {
return OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.addInterceptor(HttpLoggingInterceptor().apply {
level = if (BuildConfig.DEBUG) BODY else NONE
})
.addInterceptor { chain ->
val request = chain.request().newBuilder()
.addHeader("Authorization", "Bearer ${getAccessToken()}")
.build()
chain.proceed(request)
}
.build()
}
}
// 7. 扩展函数工具
fun View.visible() { visibility = View.VISIBLE }
fun View.gone() { visibility = View.GONE }
fun View.invisible() { visibility = View.INVISIBLE }