【问题标题】:Is there an elegant way to check if an object's type belongs to the list of types?有没有一种优雅的方法来检查对象的类型是否属于类型列表?
【发布时间】:2020-12-18 21:43:51
【问题描述】:

使用字符串时,您可以检查字符串是否属于这样的字符串集:

if (a in listOf("ab", "abab")) {
    // do something
}

或者:

when (a) {
    in listOf("akshd", "sd") -> {
        // do something
    }
}

现在,假设您有一个密封类:

sealed class SealedClass {
    class Subclass1 : SealedClass()
    class Subclass2 : SealedClass()
    class Subclass3 : SealedClass()
}

有一个很好的方法可以检查某个对象 b 是否在这些类型的子集中。如果你使用when 表达式,你可以这样写:

when (b) {
    is SealedClass.Subclass1, is SealedClass.Subclass2 -> {
        // do something
    }
}

如果你想使用if 表达式,事情就会变得复杂。你可以这样写:

if (b is SealedClass.Subclass1 || b is SealedClass.Subclass2) {
    // do something
}

或者这个:

if (
    b::class in listOf(
        SealedClass.Subclass1::class,
        SealedClass.Subclass2::class
    )
) {
    // do something
}

上述两种解决方案都相当笨拙。 使用if 语句时,有没有更优雅的方式来表达这种检查?

【问题讨论】:

    标签: kotlin


    【解决方案1】:

    我不相信有办法解决这个问题。

    创建自定义解决方案可能会适得其反,因为类型安全将成为问题。如果使用了不兼容的类型,则代码将运行,但您可能会以运行时错误结束。然而,如果你直接使用is,你会得到一个编译时错误,说“不兼容的类型”。

    解决这个问题的干净方法是在密封类中创建一个方法或属性。使用正确的命名,代码将更容易阅读。例如:

    sealed class Animal {
        class Cat : Animal()
        class Dog : Animal()
        class Tiger : Animal()
    
        val isDomestic
            get() = this is Cat || this is Dog
    }
    

    用法:

    if (animal.isDomestic) {
        // do something
    }
    

    【讨论】:

      【解决方案2】:

      如果经常使用相同的接受类型列表,您可以在某个地方定义它并与这个简单的checkType 函数一起使用:

      import kotlin.reflect.KClass
      
      private val acceptedTypes: List<KClass<out Comparable<*>>> =
          listOf(Boolean::class, Int::class, String::class)
      
      fun main() {
          val value1 = "This is a string"
          val value2 = 123_456L
      
          if (checkType(value1, acceptedTypes)) {
              println("Value \"$value1\" has type \"${value1::class}\" and " +
                      "is in the list of accepted types $acceptedTypes.")
          }
      
          if (!checkType(value2, acceptedTypes)) {
              println("Long value is not in the list of accepted types!")
          }
      }
      
      private fun checkType(value: Any, acceptedTypes: List<KClass<out Comparable<*>>>):
              Boolean = value::class in acceptedTypes
      

      带有 Any 类型的扩展函数:

      fun main() {
          val value1 = "This is a string"
          val value2 = 123_456L
      
          if (value1.checkType(acceptedTypes)) {
              println("Value \"$value1\" has type \"${value1::class}\" and " +
                      "is in the list of accepted types $acceptedTypes.")
          }
      
          if (!value2.checkType(acceptedTypes)) {
              println("Long value is not in the list of accepted types!")
          }
      }
      
      fun Any.checkType(acceptedTypes: List<KClass<out Comparable<*>>>) = this::class in acceptedTypes
      

      【讨论】:

      • 对于返回值的函数,名称checkType() 读起来不太好。 isType() 怎么样?
      【解决方案3】:

      您只是想检查类型是Subclass1 还是Subclass2,但不是 Subclass3?如果您想强制执行更复杂的类型组,您可以嵌套密封类,例如:

      sealed class SealedClass {
         sealed class CoolSubtype : SealedClass() {
             class Subclass1 : CoolSubtype()
             class Subclass2 : CoolSubtype()
         }
         class Subclass3 : SealedClass()
      }
      

      这样你就可以做到subclass is CoolSubtype。如果您不想要,则不需要嵌套类定义。如果你想给它们提供多种类型,你也可以创建空接口并用它们装饰子类。


      如果您要创建任意组,那么是的,我认为您唯一的选择确实是带有in 检查的::class 对象的集合。你当然可以用一个函数来整理它——如果你愿意,可以把它改成infix

      infix fun <T : Any> Collection<KClass<out T>>.includes(item: Any) = item::class in this
      
      // same function, just reversed so you can express it the other way around
      infix fun <T : Any> Any.memberOf(types: Collection<KClass<out T>>) = this::class in types
      
      fun main() {
          val types = listOf(Subclass1::class, Subclass2::class)
          val one = Subclass1()
          val three = Subclass3()
          println("One: ${types includes one}")
          println("Three: ${three memberOf types}")
      }
      > One: true
      > Three: false
      

      或者,如果您愿意,您可以重载 in 运算符:

      operator fun <T : Any> Collection<KClass<out T>>.contains(item: KClass<T>) = item in this
      println("One: ${one::class in types}")
      > True
      

      但是您需要为此传递KClass,如果您为Any 参数重载它,您基本上会在任何地方更改in 的行为(并且您不能在函数体,因为它会递归调用自己)

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2023-04-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-01-14
        • 2011-02-20
        • 1970-01-01
        相关资源
        最近更新 更多