问题描述
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
表示 MyNum
是 Int
的另一个名字,差不多就是这样。您的“应该警告”检查不起作用,因为 MyNum
是 Int
而 Int
是 MyNum
- 就类型而言,两者之间没有区别系统关注。
如果您希望类型系统将它们视为单独的东西,那么您实际上需要一个单独的类型,如果您只想要一个“特殊的 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)
) 生成内联类,但这还不够。您还需要将所有 Int
到 MyInt
类型转换替换为实际的 MyInt
实例创建(42 as MyNum
应该变为 MyNum(42)
)及其所有具有属性访问权限的用法: (num == 42
应该变成 num.v == 42
)。
这是不可能的,因为:
-
反射 API 不提供对局部变量类型的访问(除非您也用一些注释明确标记它们)。
-
注解处理器不能修改现有文件,它们只能创建新文件(顺便说一句,typealias也会在那里并且会与生成的内联类发生冲突)
可能可以用编译器插件来完成,但目前"Kotlin compiler plugins API is extremely experimental and is under heavy development"