# 代码块&基本代码的语法区别
创建文档信息的业务场景
# Java 怎么写业务?
- 先声明一个待返回的空对象. (没有被初始化完毕的中间状态)
- 中间业务逻辑, 夹杂各种参数对象的getter, 以及返回对象的setter
- 持久化
- 包装返回
public RestResult<DocumentDo> generate(Dto dto) {
RestResult<DocumentDo> result = new RestResult<>();
DocumentDo docDo = new DocumentDo();
// ...
// 检查重复
if(checkDuplicate(dto.getUsername())) {
throw new BizException("...");
}
// 各种 username 合法过滤, 关键词过滤, 可能涉及到修改username的
RpcResult<UsernameDto> usernameRes = xxxService.getUsername(username)
String finalUsername = null;
if(usernameRes != null &&
usernameRes.isSuccess() &&
usernameRes.getData() != null) {
finalUsername = usernameRes.getData().getUsername();
}
// 能不能看出来这段代码的问题?
docDo.setUsername(finalUsername);
// 取块 并开始处理
ContentDto richContentDto = dto.getContent();
if(richContent == null) {
throw new BizException("...");
}
List<BlockDto> blocks = richContentDto.getBlocks();
if(blocks == null) {
throw new BizException("...");
}
// encode & flattern images
List<BlockDo> blockDoList = blocks
.stream()
.parallel()
.flatmap(block -> {
if(!BlockUtils.isImage(block)) {
return Stream.of(block);
}
byte[] bytes = (bytes[]) block.getContent();
byte[][] bytess = ImageUtils.analysis(bytes);
return Arrays
.stream()
.map(bytes -> {
BlockDo blockDo = new BlockDo();
blockDo.setXXX(block.getXXX());
blockDo.setContent(bytes);
})
})
.collect(Collectors.toList());
// 假设整个文档流经历了黑盒一样的处理计算, 你会选择相信这个blockDoList吗
docDo.setBlocks(blockDoList);
DocumentDo docResDo = dockDoRepository.save(docDo);
result.setSuccess(true);
result.setDate(docResDo);
return result;
}
# Kotlin 写法
(先忽略语法设计细节审美偏好, 如方法声明, 类型后置声明等等)
fun generate(dto: Dto): RestResult<DocumentDo> {
// 检查重复 以及 username 合法过滤, 关键词过滤, 可能涉及到username变更的
// val 定义为常量, 同时类型为String不可空类型
val finalUsername: String = dto
.username
?.takeIf { checkDuplicate(it) }
?.let { xxxService.getUsername(it)}
?.takeIf { it.isSuccess }
?.data
?.username
?: throw new BizException("...")
finalUsername.substring(0, 5)
// encode & flattern images
// val 定义为常量, 同时类型为List<BlockDo>不可空类型
val blockDoList: List<BlockDo> = dto
.content
?.blocks
?.flatmap { block ->
if(!block.isImage) listOf(block)
else (block.content as? ByteArray)
.let { bytes.analysis() }
.map { bytes ->
BlockDo().apply { this ->
xxx = xxx
content = bytes
}
}
}
}
?: throw new BizException("...")
val blockDoList2: List<BlockDo?>?
return DocumentDo(
xxx = xxx,
block = blockDos,
username = finalUsername,
)
.run { this -> dockDoRepository.save(this) }
.let { it -> RestResult(true, it) }
// run & let 的语义上不同,
// run lambda 块表现为拥有上下文this, 预期返回一个对象, this是被chain对象, 语义 跑个save
// val anotherObj = obj.run { this -> objB }
// let lambda 块表现为捕获一个为it参数, 预期返回一个对象, it是被chain对象, 语义 让xx变换
// val anotherObj = obj.let { it -> objB }
// 另外还有apply lambda 块表现为拥有上下文this, this是被chain对象, 没有返回值, 语义apply 应用
// obj.apply { this -> this.username = "xxx" }
// also lambda 块表现为捕获一个为it参数, it是被chain对象, 语义also 同时
// obj.also { it -> it.username = "xxx" }
}
通过Kotlin的转写, 其实已经"强制性"修复了Java版本遗留没有处理的NPE
- 常量
finalUsername
, 会被编译器以及IDE推导出val finalUsername: String
的不可空字符串类型的 - 常量
blockDos
, 会被编译器以及IDE推导出val finalUsername: List<BlockDo>
的不可空列表类型的
其中blockDos
, 还会更甚推断出容器内部的元素也不可空, 如果代码变换中有一处会返回空, 又或者确实需要显式的空元素, 相应的, 其类型应该为List<BlockDo?>
, 当然最坏的类型List<BlockDo?>?
我猜一定不是你想要的.
这就是Kotlin的强制空安全, 通过预发和编译器, 强制性的在代码编写中对类型的可空与否进行选择, 传递, 处理的操作.
# 进阶一下, 一个复杂的ViewModel转换
想象一下通过DB Query查询了一个ResultSet
ID | username | information | 30DaysLiveTimes | 60DaysLiveTimes |
---|---|---|---|---|
1 | 鲁迅 | 浙江周树人 | 123 | 60 |
2 | LO | ASC | 70 | 70 |
3 | ABC | ASD | 60 | 70 |
然后需要对这个结果集进行分裂, 排序输出成下面的展示格式
{
// 聚合
"informationList": [
{
"information" : "ASD",
"usernameList": ["LO", "ABC"]
}
],
// 排序
"rangeList": [
{
"username": "ASD",
"liveTimes30Days": 60
}
],
// 打平
"timesList": [
{
"username": "ASD",
"days": 30,
"times": 60
},
{
"username": "ASD",
"days": 60,
"times": 70
}
]
}
Kotlin的写法很简单, 同时针对于值可空的ResultSet, Map等容器, 提供了便捷的空操作处理
// ViewModel.kt
data class ViewModel(
val informationList: List<Information>,
val rangeList: List<Range>,
val timesList: List<Times>,
) {
data class Information(val information: String, val usernameList: List<String>)
data class Range(val username: String, liveTimes30Days: Int)
data class Times(val username: String, days: Int, times: Int)
}
fun main() {
val resultSet: List<Map<String, Any?>> = queryFromDb()
val informationList: List<Information> = resultSet
.groupingBy { it["information"]?.toString() } // Group<String?, Map<String, Any?>>
.fold(listOf<String>()) { list, map ->
val username = map["username"]?.toString()
username?.let { list.add(username) } ?: list
if (username == null) list else list.add(username)
} // Map<String?, List<String>>
.mapNotNull { (key, value) -> if (key != null) key to value else null }
// Map<String, List<String>>
.map { (key, value) -> Information(key, value) }
// List<Information>
val rangeList: List<Range> = resultSet
.sortedByDescending { it["30DaysLiveTimes"] as? Int } // List<Map<String, Any?>>
.mapNotNull @label2{
// 空则丢弃
val username = it["username"]?.toString() ?: return@mapNotNull null
val liveTimes30Days = (it["30DaysLiveTimes"] as? Int) ?: return@mapNotNull null
username to liveTimes30Days
} // List<Pair<String, Int>>
.map { (key, value) -> Range(key, value)} // List<Range>
List<Range?>?
val timesList = resultSet
.flatMap { map ->
// 把小范围共用方法抽象成一个 函数对象 generate
fun generate(days: Int): Triple<String, Int, Int>? {
val username = map["username"]?.toString() ?: return null
val times = (map["${days}DaysLiveTimes"] as? Int) ?: return null
return Triple(username, days, times)
}
// Stream
sequenceOf(
generate(30),
generate(60)
).filterNotNull()
}
.map { (first, second, third) -> Times(first, second, third) }
// 显式命名构造 xxx = xxx, 针对参数多和调用不够明确的时候, 帮助阅读性
val viewModel = ViewModel(
informationList = informationList,
rangeList = rangeList,
timesList = timesList,
)
}
# 类? 怎么编写一个Spring Bean Service?
Kotlin类的声明与Java相差不大, 对于依赖注入的的编写会更加简洁
下面注入代码等同于通过构造器的@Autowired
注入, 通过构造其注入, 保证了Bean的初始化一定是完整且正确的
@Component
class AbcService(
@Qualified("TestBcdService")
private val bcdService: BcdService,
private val applicationContext: ApplicationContext,
) {
@Resource
private val bcdService: BcdService
@Resource
private var bcdService: BcdService?
@Resource
private lazyinit var bcdService: BcdService
fun serviceAbc(request: Request): String {
val str =
return "Hellow" + str;
return "Hellow ${bcdService.run(request.username)}"
}
}
// public final class AbcService()
而对于Configuration
, 众周所知Configuration
的实现通过增强继承进行了实现, 然而由于Kotlin Class默认为final级别, 需要显式的Open. 正如空安全一样, 显式的反转更有助于程序的正确理解性
@Configuration
open class AbcConfiguration(
private properties: AbcConfigurationProperties,
) {
@Bean
open fun properties2(): Properties = Proerties()
@Bean
open fun dataSource(properties2: Properties): DataSource = properties.run {
HikariDataSourceBuilder()
.host(host)
.database(database)
.username(username)
.password(password)
.build();
}
}
@ConstructorBinding
@ConfigurationProperties
data class AbcConfigurationProperties(
val host: String = "jdbc://127.0.0.1:3306",
val database: String = "infomation_schema",
val username: String = "admin",
val password: String = "admin",
)
# Utils? Converter?
在Java里面, 很多时候想要根据业务场景对某个类进行扩充, 比如StringUtils.isNotBlack()
, DateUtils.yymmdd()
又或许需要对两个不同层次的同一领域模型进行转换, DomainObjectConverter.convertToPo()
大部分时候这些类的存在是没有意义的, 但正如public static void main()
一样, 你必须假设一个类的存在作为这些方法的容器
而Kotlin Extends Function的存在, 可以让他们更为合理的存在与拓展
// DateExtends.kt
package com.lohoknang.common
// uuuummdd
private val DATE_TIME_FORMATTER: DateTimeFormatter = DateTimeFormatter.ofPattern("uuuummdd")
fun Date.formatDatabase(): String = this.format(databaseFormat)
fun Any?.formatDatabase(): String? = this
?.toString
?.let { LocalDate.parse(it, ISO_DATE) }
?.format(databaseFormat)
fun main() {
val dateTime = LocalDateTime.now()
// 为 LocalDateTime 拓展了 formatDatabase 方法
println(dateTime.formatDatabase())
val dateTime = "2021-03-07".formatDatabase()
}
# DSL进阶
Kotlin DSL 其实是 Kotlin里面 Extend & Lamda & Function & Object 的结合体, 能够在你自身的领域中构建一个领域专属的声明式语言, 从而减少胶水代码, 以及提高表达性.
Html kotlin
android
举例一个场景, 定义一个HTML对象
html {
div {
class("card-box", "middle")
content("测试块")
}
div {
class("card-box", "middle")
span("行内文本")
icon { class="arrow" }
span("行内文本")
}
div({
})
divExtend {
}
fun divExtend(divBlock: Div.() -> Unit) : {
divBlock.apply {
class("card-box")
}
}
}
这一段代码表达的是我定义了一系列函数对象的组合, 单看顶层其实是fun html(blockFunction: () -> Unit)
的函数调用, 而{}
内的内容其实式blockFunction
的函数体
而具体你需要对blockFunction
这个函数对象进行如何处理, 拓展, 执行, 转换. 取决于html
这个函数式如何实现的. 这给予了Kotlin DSL无限的可能性, 这种声明式的编程帮助你可以抽象通用代码, 帮助复杂布局的编写, 帮助函数式变成里面状态的维护, 帮助定义自身领域模型