微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

使用注释启用 Kotlin typealias 类型检查

如何解决使用注释启用 Kotlin typealias 类型检查

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

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

我想做一些类似于 Android @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 中一样)。 您认为实现我想要的最可行的方法是什么? 谢谢

解决方法

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 是(是吗?你现在很少听到它……)避免那些对象分配和引用的推荐方法,但它是一种更复杂的代码的权衡。

枚举很简单,有时更重要。我建议先做一些分析,看看使用它们会产生多大的影响,然后再放弃可能是最好的解决方案。值得思考!

,

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

这是不可能的,因为:

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

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

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

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。