1. 概述

in!in 是 Kotlin 提供的两个常用操作符。本文将深入讲解它们的常见用法,并通过实际示例演示如何对 in 操作符进行重载,以提升代码可读性和表达力。

掌握这些技巧不仅能写出更简洁的逻辑判断,还能在设计领域模型时提供更强的语义表达能力——这在实际开发中是个实用的小技能,用好了能让同事眼前一亮 ✅

2. in 与 !in 操作符简介

in!in 是二元操作符,最常见的用途有两个场景:

  • for 循环中遍历集合(如 for (item in list)
  • 判断某个元素是否存在于容器中

本文重点讨论第二种用法:成员存在性检查(containment check)

核心机制

in!in 操作符底层依赖于 contains() 方法:

  • x in y 等价于 y.contains(x)
  • x !in y 等价于 !y.contains(x)

这意味着只要一个类型实现了 contains() 方法,它就能天然支持 in!in 操作符 ❗

⚠️ 注意:虽然我们可以在 for 循环中使用 in,但它实际上是基于 iterator() 的实现,和本文讨论的 contains() 无关,此处不再展开。

为了验证效果,后续示例统一使用单元测试断言方式展示结果,便于理解。

3. 常见使用场景

3.1 集合与数组

集合(Collection)和数组是最典型的容器类型,天然支持 in / !in

val mySet = setOf("a", "b", "c", "d", "e")
assertTrue { "a" in mySet }
assertTrue { "x" !in mySet }

数组同样适用:

val myArray = arrayOf("a", "b", "c", "d", "e")
assertTrue { "a" in myArray }
assertTrue { "x" !in myArray }

✅ 小贴士:Kotlin 对数组的支持是通过扩展函数实现的,不需要手动实现。

3.2 区间(Range)

Kotlin 内建的 Range 类型默认支持 in 操作符,非常适合做数值范围判断:

val myRange = 1..100
assertTrue { 42 in myRange }
assertTrue { 777 !in myRange }

这种写法比手写 if (x >= 1 && x <= 100) 更直观,也更安全,尤其在处理日期、年龄、分数等场景时非常实用。

3.3 字符串

字符串也可以使用 in 来判断子串或字符是否存在:

val myString = "a b c d e"
assertTrue { "a b" in myString } // 子串匹配
assertTrue { 'c' in myString }   // 字符匹配
assertTrue { "X" !in myString }

底层调用的是 String.contains() 方法,行为符合直觉。

4. in 操作符重载实战

Kotlin 支持操作符重载(operator overloading),我们可以通过定义带有 operator 关键字的 contains 函数,让自定义类型也支持 in 操作符。

语法要求:

  • 成员函数 或 扩展函数
  • 使用 operator 修饰
  • 名称为 contains

下面通过两个典型例子说明如何合理使用这一特性。

4.1 球员与比赛场景

假设我们有如下数据类:

data class Player(val name: String, val rank: Int)
data class Team(val name: String, val players: Set<Player>)
data class Match(val place: String, val teams: Pair<Team, Team>)

初始化一些测试数据:

val eric = Player("Eric", 8)
val kai = Player("Kai", 7)
val teamA = Team("Team A", setOf(eric, kai))

val kevin = Player("Kevin", 6)
val saajan = Player("Saajan", 11)
val teamB = Team("Team B", setOf(kevin, saajan))

val tom = Player("Tom", 1)
val jerry = Player("Jerry", 9)
val teamC = Team("Team C", setOf(tom, jerry))

val match1 = Match("Frankfurt", teamA to teamC)
val match2 = Match("Hamburg", teamB to teamC)
val match3 = Match("Berlin", teamA to teamB)

现在想判断某位球员是否参与了某场比赛。常规做法需要层层解构:

// 老老实实写判断
fun isPlayerInMatch(player: Player, match: Match): Boolean {
    return player in match.teams.first.players || player in match.teams.second.players
}

但如果我们能让 in 直接支持 Match 类型,代码就会优雅很多:

operator fun Match.contains(player: Player): Boolean {
   return teams.toList().any { player in it.players }
}

重载后即可直接使用:

assertTrue { eric in match1 }
assertTrue { saajan !in match1 }

assertFalse { kai in match2 }
assertTrue { tom !in match3 }

✅ 效果:语义清晰,接近自然语言表达,别人一眼就能看懂“eric 是否参加了 match1”。

4.2 正则匹配模式识别

我们有三个预定义的正则表达式:

val pattern33 = Regex("^[a-z]{3} - [a-z]{3}$")
val pattern34 = Regex("^[a-z]{3} - [a-z]{4}$")
val pattern44 = Regex("^[a-z]{4} - [a-z]{4}$")

目标是根据输入字符串判断其符合哪种模式。

方案一:if-else 判断

fun determinePattern1(input: String): String {
    return if (input.matches(pattern33)) {
        "3-3"
    } else if (input.matches(pattern34)) {
        "3-4"
    } else if (input.matches(pattern44)) {
        "4-4"
    } else {
        "none"
    }
}

方案二:when 表达式优化

fun determinePattern2(input: String): String {
    return when {
        input.matches(pattern33) -> "3-3"
        input.matches(pattern34) -> "3-4"
        input.matches(pattern44) -> "4-4"
        else -> "none"
    }
}

虽然结构更清晰,但仍存在重复调用 matches() 的问题。

方案三:重载 in 操作符

通过为 Regex 添加扩展函数,让其支持 in

operator fun Regex.contains(input: String): Boolean {
    return this.matches(input)
}

然后就可以在 when 中直接使用 in

fun determinePattern(input: String): String {
    return when (input) {
        in pattern33 -> "3-3"
        in pattern34 -> "3-4"
        in pattern44 -> "4-4"
        else -> "none"
    }
}

✅ 最终效果:逻辑清晰、无冗余调用、可读性强,堪称 Kotlin 风格典范。

验证测试:

assertEquals("3-3", determinePattern("abc - xyz"))
assertEquals("3-4", determinePattern("top - down"))
assertEquals("4-4", determinePattern("good - nice"))
assertEquals("none", determinePattern("1234 - 4321"))

⚠️ 踩坑提醒:重载操作符要适度!不要为了炫技而滥用,否则会让团队新人难以理解。建议只在语义明确、能显著提升可读性的场景下使用。

5. 总结

本文系统介绍了 Kotlin 中 in!in 操作符的核心机制与典型应用:

  • in 本质是 contains() 方法的语法糖
  • ✅ 常用于集合、数组、区间、字符串的存在性检查
  • ✅ 可通过 operator + contains 实现自定义类型的 in 支持
  • ✅ 合理重载能大幅提升代码可读性,尤其是在领域建模和 DSL 设计中

完整示例代码已托管至 GitHub:https://github.com/Baeldung/kotlin-tutorials/tree/master/core-kotlin-modules/core-kotlin-collections-5

掌握这些技巧后,下次写条件判断时不妨想想:能不能用 in 让代码更优雅一点?💡


原始标题:in and !in Operators in Kotlin