边写软件边学kotlin(二)——语法推进

(一)TypeToken

是一个类,因Google的Gson库为了解决Java原生中泛型擦除 的痛点而生,通常搭配匿名内部类使用。

kotlin 复制代码
val listType = object : TypeToken<List<InterviewPost>>() {}.type
Gson().fromJson<List<InterviewPost>>(jsonString, listType)
  1. object :...(){}:是kotlin语言中用以创建匿名内部类的一种写法
  2. 泛型擦除:在Java语言中,在将json字符串转成对象的过程中,只知"数据结构",不知"数据类型",例如想将json字符串转换成List< User >类型,转换的时候只知道要转换成list列表,但是不知道里面是User类型。这就是泛型擦除
  3. TypeToken解决方式:通过创建一个匿名内部类,可以通过反射拿到该类的父类信息,这个类的父类信息储存了我们需要的数据类型
  4. .type:获取java.lang.reflect.type对象,也就是我们在Java代码中执行反射操作的东西

(二)

kotlin 复制代码
val assetManager = getApplication<Application>().assets
val inputStream = assetManager.open("interview_data.json")
val reader = BufferedReader(InputStreamReader(inputStream))
  1. getApplication< Application >():获取当前的全局单例上下文对象context,说白了就是当前这个应用的实例
  2. getApplication< Application >().assets:获取这个应用下的针对assets包的AssetsManager资源管理器,可以访问该包下的目录,这个资源管理器可以读取原始字节流
  3. assetManager.open:获取该包下从文件到内存的字节输入流

(三)

kotlin 复制代码
viewModelScope.launch {
            val loadedPosts = withContext(Dispatchers.IO) {
                try {
                    val assetManager = getApplication<Application>().assets
                    val inputStream = assetManager.open("interview_data.json")
                    val reader = BufferedReader(InputStreamReader(inputStream))
                    val jsonString = reader.use { it.readText() }



                    val listType = object : TypeToken<List<InterviewPost>>() {}.type
                    Gson().fromJson<List<InterviewPost>>(jsonString, listType)

                } catch (e: Exception) {
                    e.printStackTrace()
                    emptyList<InterviewPost>()
                }
            }
            _posts.value = loadedPosts
        }
  1. .launch:发起一个线程
  2. withContext(Dispatchers.IO)withContext :切换到另一个线程,Dispatcher.IO:标明这是一个专门用来处理IO操作的线程

小tips:

  1. 在kotlin中,可以出现定义在后,使用在前的情况
  2. 正则表达式:是一种用来匹配和处理字符串的工具

(四)

kotlin 复制代码
for (i in 0 until 5) {
                // 1. 尝试获取 AI 返回的维度名,如果获取不到或为空,则使用默认侧写
                val defaultList = listOf("核心技能", "综合表现", "成长潜力", "行业认知", "实践能力")
                val dName = dimArray?.optString(i)?.takeIf { it.isNotBlank() && !it.contains("维度") }
                    ?: defaultList[i]
                newDims.add(dName)

                // 2. 尝试获取分值,并进行归一化和钳位
                var v = valArray?.optDouble(i, 0.5)?.toFloat() ?: 0.5f
                if (v > 1.0f) v /= 100f
                val finalVal = v.coerceIn(0.1f, 1.0f) // 最小值 0.1 保证雷达图有形状
                newVals.add(finalVal)
                totalSum += finalVal
            }
  1. optString:获取json中的字符串类型的数据,与getString()方法不同的是,即使获取到的字符串为空,也不会抛出异常。
  2. optDouble:获取json中的double类型的数据,与getDouble()方法不同的是,即使获取到的数据为0.0f,也不会抛出异常
  3. . takeif():如果对象满足括号里面的条件,则返回对象本身。
  4. coerceIn:将值限制在一个区间内,例如coerceIn(0.1f, 1.0f)就是将值限制在0.1~1.0之间

(五)

kotlin 复制代码
aiScore = (totalSum / 5f * 100).roundToInt().toString()

roundToInt:将浮点数四舍五入转换成整数

在安卓开发中,有一个类叫Bitmap,他代表图片,图片一般在内存中以字节数组,流,文件的形式存储,而BitmapFactory负责从内存中读取这些数据转换为Bitmap对象方便操作,也可以把图片转换成字节数组,流,文件。如下图所示

kotlin 复制代码
val options = BitmapFactory.Options().apply {
                inJustDecodeBounds = true
            }
  1. .options():表示对BitmapFactory对象的一些参数的设置
  2. .apply:对这些参数对象进行设置并返回它们

(六)

kotlin 复制代码
context.contentResolver.openInputStream(uri)?.use { input ->
                BitmapFactory.decodeStream(input, null, options)
            }
  1. contentResolver:用来访问和操作内容提供者中的数据
  2. BitmapFactory.decodeStream(input, null, options):打开从图片文件到内存的输入流,获取图片输入进来的流,对流进行解码,null表示不设置解码区域(即解码整个区域),options表示使用对BitmapFactory使用刚才的设置参数

?:后面既可以是一个具体的返回结果,也可以是一段执行逻辑

kotlin 复制代码
val outputStream = ByteArrayOutputStream()
复制代码
	-创建一个字节输出流的对象,这个对象是可以动态扩展字节数组的,因此不需要预先设置数组的大小
kotlin 复制代码
bitmap.compress(Bitmap.CompressFormat.JPEG, 70, outputStream)
复制代码
	将bitmap对象压缩成原来70%的质量,并以JPEG的形式存储在outputStream这个字节数组文件中
kotlin 复制代码
val bytes = outputStream.toByteArray()
复制代码
	将字节输出流中的字节数据转成字节数组
kotlin 复制代码
Base64.encodeToString(bytes, Base64.NO_WRAP)
复制代码
	将字节数组转换成Base64格式的字符串,并且(No_WRAP)去除任何换行符
  1. 为什么要去除换行符:因为Base64编码的过程中可能每76个字符就会加一个换行符,这些换行符可能会影响处理结果
  2. Base64编码:将二进制数据转换成ASCII码,字节数组就是二进制数据,所以才会用字节数组进行Base64编码

(七)

kotlin 复制代码
class A{
		init{
   }
}

init{}:类似Java中的构造代码块,在类创建实例的时候也会一并执行

(八)

kotlin 复制代码
val searchQuery: StateFlow<String> = _searchQuery.asStateFlow()
  1. val:表示searchQuery是一个不可变的变量,也就是说这个变量的引用不变了
  2. StateFlow< String >:是一个发布String类型数据的流,这个流的状态的变化可以被外界观察,使用asStateFlow(),就可以将StateFlow变成不可变的StateFlow,使其只能被订阅而不能被改变
kotlin 复制代码
val uiList: StateFlow<List<CompanyListItem>> = combine(
        _searchQuery,
        _selectedIndustry
    ) { query, industry ->
        Triple(query, industry, allCompanyData)
    }.map { (query, industry, data) ->
        // 在后台线程进行数据处理
        if (query.isEmpty() && industry == null) {
            emptyList()
        } else {
            generateUiList(query, industry, data)
        }
    }
    .flowOn(Dispatchers.Default) // 确保计算在后台线程
    .stateIn(
        scope = viewModelScope,
        started = SharingStarted.WhileSubscribed(5000),
        initialValue = emptyList()
    )
  1. combine:用以合并流,代码中就是将_searchQuery和_selectedIndustry两个流进行合并,有任何一个流的值发生改变都会触发合并操作
  2. Triple:可以固定存储三个不限数据类型的数据的容器,例如:
kotlin 复制代码
val triple = Triple("Hello", 42, true)

就是将字符串,数字,布尔值统一放在一个容器中

(九)

kotlin 复制代码
.flowOn(Dispatchers.Default) 
  1. flowOn:字面意义上理解就是"流在哪里",意思就是线程在哪里执行
  2. Default:默认后台线程,就是说把这个线程放在了后台进行执行

(十)

kotlin 复制代码
.stateIn(
        scope = viewModelScope,
        started = SharingStarted.WhileSubscribed(5000),
        initialValue = emptyList()
    )
  1. stateIn():将flow变成一个可被观察状态的容器
  2. scope:设置生命周期,它的生命周期和viewModelScope一样长
  3. started:设置需要重新计算的时间阈值。例如切出屏幕,5秒内再回来就不会重新计算,屏幕如初,5秒之后再回来,就会重新计算(Flow计算链)

(十一)

kotlin 复制代码
companies.filter { it.name.contains(query, ignoreCase = true) }
  1. filter:筛选出符合括号内条件的元素(自动实现了循环逻辑
  2. ignoreCase:忽略大小写

(十二)

kotlin 复制代码
val grouped = filtered.groupBy { it.pinyinFirstLetter }
	colors = TextFieldDefaults.colors(
	focusedContainerColor = Color.Transparent,
	unfocusedContainerColor = Color.Transparent,
	disabledContainerColor = Color.Transparent,
	focusedIndicatorColor = Color.Transparent,
	unfocusedIndicatorColor = Color.Transparent
),
singleLine = true
  1. groupBy:按括号内的条件进行组分,其返回值是一个Map,"键"是单个字符:A,B,C,D。"值"是原本filtered内部的值
  2. it.pinyinFirstLetter:它的拼音首字母
  3. focusedContainerColor :获得聚焦时容器的颜色。unfocusedContainerColor :没活得聚焦时容器的颜色。disabledContainerColor :被禁用时的容器颜色。focusedIndicatorColor:获得聚焦时下划线的颜色
  4. singleLine = true:将输入框设置成仅支持一行,也就是不能换行

(十三)

kotlin 复制代码
fun CompanySearchScreen(
    onBack: (() -> Unit)? = null,
    showSearchBar: Boolean = true,
    viewModel: CompanySearchViewModel = viewModel()
) 
val searchQuery by viewModel.searchQuery.collectAsState()
  1. kotlin语言中的小特性:括号内可以赋入初始值,而Java语言不行
  2. val searchQuery by viewModel.searchQuery.collectAsState()其实就等于
kotlin 复制代码
val state = viewModel.searchQuery.collectAsState()
val searchQuery = state.value()
by语法的作用就是获取值
  1. collectAsState:将Flow数据流变成Compose可监听的state

(十四)

kotlin 复制代码
remember{}

将括号内的对象进行记忆,使得界面重组,重新调用函数的时候不会再把创建新对象的代码再跑一遍。而是直接拿第一次创建好的对象去用。有个好处就是之前的数据不会丢

(十五)

kotlin 复制代码
fun ProfileSectionCard(title: String, content: @Composable ColumnScope.() -> Unit) {
    Card(
        modifier = Modifier.fillMaxWidth(),
        colors = CardDefaults.cardColors(containerColor = Color.White),
        shape = RoundedCornerShape(12.dp)
    ) {
        Column(
            modifier = Modifier.padding(16.dp)
        ) {
            Text(text = title, fontWeight = FontWeight.Bold, fontSize = 15.sp)
            Spacer(modifier = Modifier.height(12.dp))
            content()
        }
    }
}
  1. @Composable:表明这个函数是用来写UI的
  2. ColumnScope:Column的内部环境
  3. ()->Unit:表明这是一个函数
    content: @Composable ColumnScope.() -> Unit:组合在一起就是content是一个函数,可以在Column的内部环境运行,并且可以往column里面写UI

(十六)

kotlin 复制代码
object QuestionRepository {
    fun getQuestionsByCategory(category: String): List<Question> {
        return (1..150).map {
            Question(id = it, title = "[$category] 第 $it 题", answer = "", category = category, passRate = "${(60..95).random()}%")
        }
    }
}

object:与class的区别就是,class可以创建多个实例,而object所创建出来的对象全局唯一

(十七)

kotlin 复制代码
private val industryNamesMap = mapOf(
        '互' to 'H', '金' to 'J', '医' to 'Y', '食' to 'S', '政' to 'Z',
        '教' to 'J', '制' to 'Z', '房' to 'F', '新' to 'X', '物' to 'W',
        '汽' to 'Q', '半' to 'B', '游' to 'Y', '传' to 'C', '零' to 'L',
        '咨' to 'Z', '法' to 'F', '建' to 'J', '化' to 'H', '航' to 'H',
        '旅' to 'L', '农' to 'N', '环' to 'H', '硬' to 'Y', '体' to 'T'
    )

作用:创建一个哈希表,并把一个一个元素对应起来

(十八)

kotlin 复制代码
val firstChar = name.firstOrNull() ?: return '#'
if (firstChar in 'A'..'Z') return firstChar
if (firstChar in 'a'..'z') return firstChar.uppercaseChar()
  1. name.firstOrNull():找到name这个字符串的第一个字符并返回,如果name字符串为空则返回"#"
  2. uppercaseChar:将小写字母转成大写字母

(十九)

kotlin 复制代码
return when (firstChar) {
            '阿', '爱', '安' -> 'A'
            '百', '比', '哔', '保', '北', '碧' -> 'B'
            '长', '腾', '传' -> 'C' // 腾->T, 但这里简单处理
            '滴', '德', '叠' -> 'D'
            '饿' -> 'E'
            '富', '复', '粉' -> 'F'
            '广', '工', '贵', '格', '高', '国' -> 'G'
            '华', '海', '恒', '好' -> 'H'
            '阿' -> 'J' // 修正
            '京', '吉', '金', '巨', '晶' -> 'J'
            '快', '科' -> 'K'
            '理', '隆', '龙', '联', '立' -> 'L'
            '美', '蚂', '迈', '蒙', '明', '米' -> 'M'
            '宁', '农', '南' -> 'N'
            '拼', '平', '片' -> 'P'
            '奇', '企', '去', '青' -> 'Q'
            '人', '日' -> 'R'
            '三', '搜', '顺', '申', '上' -> 'S'
            '腾', '泰', '天', '通' -> 'T'
            '阿' -> 'W'
            '万', '微', '蔚', '网', '韦', '五', '娃' -> 'W'
            '小', '携', '新', '西' -> 'X'
            '云', '优', '猿', '药', '英', '圆', '韵', '伊', '阳' -> 'Y'
            '字', '中', '知', '招', '紫', '兆', '智', '作' -> 'Z'
            else -> if (industryNamesMap.contains(firstChar)) industryNamesMap[firstChar]!! 
            else 'Z' // 兜底
}
  1. when():具有返回值,根据括号中的变量的不同情况返回不同值,类似于Java中的switch结构。举一个例子,在这段代码中,如果检测出firstChar是'阿' 或者'安'或者 '爱',就返回一个'A'
  2. industryNamesMap[firstChar]:是kotlin中对哈希表中value值的调取方式
  3. !!:强制非空,如果为空则报错

(二十)

kotlin 复制代码
return industries.associateWith { industry ->
            // 关键修复:先对原始数据去重,防止 LazyColumn 因 key 重复而崩溃
            val existingNames = realData[industry]?.distinct() ?: emptyList()
            // 使用 MutableSet 来存储,既能保持去重,又能快速查找
            val fullSet = LinkedHashSet(existingNames)
            val baseList = existingNames.ifEmpty { listOf("$industry 行业知名企业") }
            
            val suffixes = listOf("分公司", "研发中心", "技术部", "营销中心", "办事处", "控股子公司", "集团")
            var suffixIndex = 0
            var nameIndex = 0
            
            while (fullSet.size < 100) {
                // 循环使用已有真实大厂名字 + 后缀
                val baseName = baseList[nameIndex % baseList.size]
                val suffix = suffixes[suffixIndex % suffixes.size]
                val newName = if (baseName.contains("No.")) "$baseName ${fullSet.size + 1}" else "$baseName$suffix"
                
                fullSet.add(newName)
                
                nameIndex++
                if (nameIndex % baseList.size == 0) {
                    suffixIndex++
                }
            }
            
            // 转换为 Company 对象并按拼音首字母排序
            fullSet.map { name ->
                Company(name, getPinyinFirstLetter(name))
            }.sortedBy { it.pinyinFirstLetter }
        }
  1. .distinct:将realData[industry]去重
  2. LinkedHashSet():能对列表去重并保留顺序
  3. .map:把前面的列表元素映射成另一种新元素,并返回新列表
  4. .sortedBy { it.pinyinFirstLetter }:把新列表按照拼音顺序排列
相关推荐
清水白石00810 小时前
突破并行瓶颈:Python 多进程开销全解析与 IPC 优化实战
开发语言·网络·python
百锦再11 小时前
Java之Volatile 关键字全方位解析:从底层原理到最佳实践
java·开发语言·spring boot·struts·kafka·tomcat·maven
daad77711 小时前
rcu 内核线程
java·开发语言
xzjiang_36511 小时前
检查是否安装了MinGW 编译器
开发语言·qt·visual studio code
百锦再12 小时前
Java JUC并发编程全面解析:从原理到实战
java·开发语言·spring boot·struts·kafka·tomcat·maven
清水白石00812 小时前
突破性能瓶颈:深度解析 Numba 如何让 Python 飙到 C 语言的速度
开发语言·python
Eternity∞12 小时前
Linux系统下,C语言基础
linux·c语言·开发语言
wangluoqi13 小时前
c++ 树上问题 小总结
开发语言·c++
Go_Zezhou14 小时前
pnpm下载后无法识别的问题及解决方法
开发语言·node.js