ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

B02_Kotlin空安全与类型边界

B02_Kotlin空安全与类型边界 Android 基础补强 B02空安全不是加问号让类型表达数据边界摘要Kotlin 能区分可空类型却不会替业务判断“缺失”和“无效”。本文围绕详情文章 ID、作者显示和 Java 互操作分析安全调用、Elvis、非空断言与智能转换的适用边界。标签Kotlin、空安全、类型系统、Java互操作、第一行代码一个问号背后的三种状态详情页拿到的文章 ID 可能缺失可能是非数字文本也可能是格式正确但业务无效的零。如果全部转换失败都返回 null界面只能看到“没有值”却不知道是入口没传参数还是用户提供了错误参数。本篇对应《第一行代码》2.7 空指针检查并延伸到实际接口边界。D02 练习批量清洗文章本篇进一步讨论单个值应该用什么类型表达以及什么时候应当终止流程而不是给所有字段安排一个看似安全的默认值。可空类型是编译器与调用者之间的契约String? 允许没有字符串String 要求此处已有字符串。但空字符串、纯空格与语义错误仍然是普通字符串需要业务规则继续判断。把 null 统一替换成空字符串有时只是把早期错误挪到了更难定位的地方。从安全访问走向明确分支安全调用让接收者为空时跳过访问Elvis 运算符为 null 分支提供结果非空断言则把“不为空”的责任交给开发者。它们都只是语言工具没有哪一个能替代输入约束。Kotlin 空安全文档显示作者时缺失可以用“匿名”兜底因为页面允许作者未知。查询详情时缺失 ID 不能随意兜底成一因为这样可能显示了完全不同的文章。两个字段同样可空却不该采用同一种恢复策略。下面是独立 Kotlin 核心示例仅依赖标准库未在本次写稿中编译。它保留边界的具体结果让上层可以做不同处理。sealedinterfaceArticleIdResult{dataclassValid(valid:Long):ArticleIdResultdataobjectMissing:ArticleIdResultdataclassInvalid(valreason:String):ArticleIdResult}funparseArticleId(raw:String?):ArticleIdResult{if(rawnull)returnArticleIdResult.Missingvalnormalizedraw.trim()if(normalized.isEmpty())returnArticleIdResult.Invalid(文章 ID 为空)validnormalized.toLongOrNull()?:returnArticleIdResult.Invalid(文章 ID 不是有效整数)if(id0)returnArticleIdResult.Invalid(文章 ID 必须大于零)returnArticleIdResult.Valid(id)}fundescribeId(raw:String?):Stringwhen(valresultparseArticleId(raw)){isArticleIdResult.Valid-可以查询文章${result.id}ArticleIdResult.Missing-入口没有提供文章 IDisArticleIdResult.Invalid-result.reason}funverifyIdBoundary(){check(parseArticleId(null)ArticleIdResult.Missing)check(parseArticleId( )isArticleIdResult.Invalid)check(parseArticleId(9223372036854775808)isArticleIdResult.Invalid)check(parseArticleId( 42 )ArticleIdResult.Valid(42L))}其中超出 Long 范围的输入与非数字输入都走解析失败不会因为 toLong 抛出异常打断正常输入流程。错误文案只是教学用途正式应用可以返回错误码或领域类型由 UI 层生成可本地化文案。哪些位置适合尽早失败外部输入无效通常需要可解释地拒绝内部本应成立的约束被破坏则应在靠近错误的位置暴露。例如函数要求正整数 ID可以在边界使用 require。状态机处于不可能分支时可以用 check 表达内部状态约束。不要把所有异常吞掉返回空列表因为这会把程序缺陷伪装成“暂时没有文章”。不过尽早失败不是让普通用户输入轻易崩溃。判断依据是来源和责任用户填错文本是预期输入之一内部调用者违反已承诺前置条件才是代码问题。公开接口与内部函数可以采用不同结果表达前提是契约明确。非空断言也不应被简单评价为“永远不能用”。如果有严格证明可以使用但当它只是为了让红线消失就等于把编译器提示转换成运行期风险。学习阶段最好写出那个证明值在哪里赋予、谁可能修改、访问时宿主是否仍然有效。智能转换为什么有时不成立局部稳定变量经过判空后编译器往往可以把它视为非空。可变属性、自定义 getter 或跨线程变化的引用则不一定具有同样保证。因为两次读取之间值可能发生改变第一次判断无法证明第二次读取安全。类型检查与转换一种常见做法是先读取到局部 val再对这一快照判空。它保证当前分支使用的是同一次读取结果但并不自动解决业务并发一致性如果对象内部仍在被其他线程修改持有一个非空引用不代表全部字段形成原子快照。这在 Android 中尤其容易和生命周期交叉。某个 View 引用在进入回调时非空不说明长时间异步任务完成时该 View 仍属于当前页面。类型非空和对象有效期是两个维度后者需要生命周期与任务取消共同处理。Java 互操作会把不确定性带进来调用缺乏明确可空注解的 Java API 时Kotlin 可能看到平台类型。代码看上去允许按非空值使用但运行时仍可能得到 null。遇到这类边界应该查看 API 契约并在适当位置规范化而不是认为“Kotlin 工程已经不可能出现空指针”。调用 Java 代码对项目而言可以在 DTO 或 Java 接口转换到领域模型时集中处理不确定性让内部模型尽量具有清晰约束。不要在每个按钮、每个 Text 上散落相同的非空断言和默认值这会使同一个缺失作者在不同页面显示成三种结果。故障实验与验收先用 null、空格、字母、零、负数、溢出整数和正常 ID 各执行一次解析记录每个分支。再把 parseArticleId 改成 raw!!.toLong预期部分输入直接抛异常。对照不是为了证明短写法不好而是说明它丢失了哪些输入语义与错误处理能力。最后把作者规则改成“空白视为未知未知显示匿名”但详情 ID 继续明确拒绝。能为两个可空字段设计不同规则并解释为什么才说明理解了空安全与业务边界的分工。三道原创面试问答1. Kotlin 有空安全为什么仍可能空指针非空断言、Java 平台类型以及初始化或外部契约问题仍可能带来运行期风险。追问最佳修复是否总是改成安全调用不是安全调用可能静默跳过必须执行的操作应先确认缺失是否允许。2. null 和空字符串有什么区别前者表示缺少值后者是存在但长度为零的字符串是否合并由业务决定。追问为什么不能把无效文章 ID 兜底成零零未必是合法身份会掩盖输入错误并污染后续查询。3. 局部 val 快照是否解决线程安全它只稳定了这次引用读取不能使对象内部修改自动具备同步关系。追问如果对象是完全不可变的呢快照更易推理但共享状态的发布与更新仍需遵循所处并发模型。以上均为课程自拟题。发布时可以附上自己运行的输入输出表没有执行时仍应保持实验预期的表述。
返回列表