【问题标题】:Enable Kotlin typealias type check with annotations使用注解启用 Kotlin typealias 类型检查
【发布时间】:2021-04-30 09:21:01
【问题描述】:

Kotlin 有类型别名,在你想要一个好的命名时非常方便。但是,在某些情况下,您希望有一个比别名多一点的类型别名,您希望它强制执行实际的编译时检查,而不需要创建新类。

这就是我想要实现的目标:

typealias MyNum = Int

fun isMagical(num: MyNum) = num == 42

fun main() {
    // Should fail/warn
    isMagical(42)
    // Should pass
    isMagical(42 as MyNum)

    // Should fail/warn
    val x = 3
    isMagical(x)

    // Should pass
    val y: MyNum = 3
    isMagical(y)
}

我知道我可以使用内联类来实现这一点,但我需要检查其中的许多类型,并且不想为每个类型创建一个类。

是否可以通过注释来实现?喜欢:

@Target(AnnotationTarget.TYPEALIAS)
annotation class StrongType

@StrongType
typealias MyNum = Int

然后让注释处理器进行检查?

我想做类似安卓的事情@IntDef:

// Android way (performant but needs to manually annotates methods and list the options in the annotation)
@Retention(SOURCE)
@IntDef({NAVIGATION_MODE_STANDARD, NAVIGATION_MODE_LIST, NAVIGATION_MODE_TABS})
public @interface NavigationMode {}
public static final int NAVIGATION_MODE_STANDARD = 0;
public static final int NAVIGATION_MODE_LIST = 1;
public static final int NAVIGATION_MODE_TABS = 2;
// Enum way (not performant)
enum class NavMode constructor(val value: Int){
    STANDARD(0),
    LIST(1),
    TABS(2)
}

// Inline class (performant but generating a lot of code)
inline class NavMode(val value: Int) {
    companion object {
        val STANDARD = NavMode(0)
        val LIST = NavMode(1)
        val TABS = NavMode(2)
    }
}

// This is what I would like: performant, type check in methods without annotating them, clean code
@StrongType
typealias NavMode = Int
const val STANDARD: NavMode = 0
const val LIST: NavMode = 1
const val TABS: NavMode = 2

请注意,这不是我真正的用例,我有很多这样的枚举要创建,同时保持高性能(就像在 android API 中一样)。 您认为实现我想要的最可行的方法是什么? 谢谢

【问题讨论】:

  • 可能不是您正在寻找的响应,但您建模数据的方式可能是问题所在。如果您需要经常进行isMagical 检查,我觉得您需要一个类型,因为检查可以是类型的一部分。如果只有某些值是“特殊的”,那么也许枚举会更合适。只是一个想法。
  • 我有效率限制和很多这样的枚举要创建,这对我来说并不是一个合适的解决方案

标签: java class kotlin annotations type-alias


【解决方案1】:

typealias MyNum = Int 表示MyNumInt 的另一个名字,差不多就是这样。您的“应该警告”检查不起作用,因为 MyNumIntIntMyNum - 就类型系统而言,两者之间没有区别。

如果您希望类型系统将它们视为单独的事物,那么您实际上需要一个单独的类型,如果您只想要一个“特殊 Int”,您将会遇到问题,因为 Int 是一个最终类,您不幸的是,不能子类化它。所以你不能仅仅将MyInt 视为Int


您使用 IntDefs、枚举等的示例有些不同 - 您肯定需要一种新类型,它具有一组有限的可能的预定义值。某些语言允许您这样做,但仍将其视为 Int - 但 Kotlin 不会。

IntDefs 可能最接近您想要的,您必须在其中注释所有内容,因为这是检查它的方式 - 它在类型系统之外。它很笨拙,但那是因为它是用螺栓固定的。

密封类可以得到你想要的那种“干净”的定义:

// or an interface
abstract class MyInt() {
    abstract val value: Int
}

// put them inside the sealed class (in {}) if you want them named NavMode.LIST etc
sealed class NavMode(override val value: Int) : MyInt()
object STANDARD : NavMode(0)
object LIST : NavMode(1)
object TABS : NavMode(2)

但这基本上是一个没有枚举的枚举(您可以利用它来确保每个值都是唯一的 - 这里没有检查,您可以为每个值传递 0)


你说你有“效率限制”,但是你说吗?就像,内存中每个枚举的一个实例实际上会是一个问题,还是持有对对象而不是原语的引用? IntDefs 是(是吗?这些天你没有听到太多关于它的消息......)避免这些对象分配和引用的推荐方法,但它是与更复杂的代码的权衡。

枚举很简单,有时更重要。我建议先进行一些分析,看看使用它们实际上会产生多大的影响,然后再抛出可能是最好的解决方案。只是值得考虑!

【讨论】:

    【解决方案2】:

    注解处理器不能改变编译器的工作方式。他们只能生成新的源代码文件(并将它们交给编译器)。 您可以使注释处理器从带注释的类型别名 (inline class MyNum(val v: Int)) 生成内联类,但这还不够。您还需要将所有 IntMyInt 类型转换替换为实际的 MyInt 实例创建(42 as MyNum 应变为 MyNum(42))以及它们的所有使用属性访问:(num == 42 应变为 num.v == 42) .

    这是不可能的,因为:

    1. Reflection API 不提供对局部变量类型的访问(除非您也用一些注释显式标记它们)。

    2. 注释处理器不能修改现有文件,它们只能创建新文件(顺便说一下,typealias 也会在那里,并且会与生成的内联类发生冲突)

    也许可以用编译器插件来完成,但目前"Kotlin compiler plugins API is extremely experimental and is under heavy development"

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-06-03
      • 2016-12-22
      • 2022-08-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多