我正在使用来自后端的一种动态表单系统。为了能够映射我的表单,我有一个带有泛型的访问者模式,我使用Java工作,但我不能让它在Kotlin中工作。
我有这个界面:
internal interface FormFieldAccessor<T> {
fun getFormField(formFieldDefinition: FormFieldDefinition): FormField<T>
fun setValueToBuilder(builder: Builder, value: T)
fun accept(visitor: FormFieldVisitor)
fun getValue(personalInfo: PersonalInfo): T
}
然后我有这样的访问者列表:
val accessors = mutableMapOf<String, FormFieldAccessor<*>>()
accessors[FIRST_NAME] = object : FormFieldAccessor<String> {
override fun getValue(personalInfo: PersonalInfo): String {
return personalInfo.surname
}
override fun accept(visitor: FormFieldVisitor) {
visitor.visitString(this)
}
override fun getFormField(formFieldDefinition: FormFieldDefinition): FormField<String> {
//not relevant
}
override fun setValueToBuilder(builder: Builder, value: String) {
builder.withSurname(value)
}
}
//more other accessors with different type like Int or Boolean
想要像这样使用它:
accessors[FIRST_NAME]!!.setValueToBuilder(builder, field.value )
但这不起作用并给我:
Out-projected type 'FormFieldAccessor<*>' prohibits the use of 'public abstract fun setValueToBuilder(builder: Builder, value: T): Unit defined in FormFieldAccessor'
如果你知道我做错了什么就会很酷:)
编辑:这里有一个较小的结构要点https://gist.github.com/jaumard/1fd1ccc9db0374cb5d08f047414a6bc8
我不想通过使用Any来松开类型,与Java相比感到沮丧,因为它很容易实现。我现在明白了星形投影的问题,但除此之外还有什么可以达到和java一样吗?
答案 0 :(得分:1)
使用star-projection表示您对实际类型一无所知,正如文档所述:
有时您想说您对类型参数一无所知,但仍希望以安全的方式使用它。这里安全的方法是定义泛型类型的这种投影,该泛型类型的每个具体实例都是该投影的子类型。
[...]
对于
Foo<out T : TUpper>
,其中T
是具有上限TUpper
的协变类型参数,Foo<*>
等同于Foo<out TUpper>
。这意味着当T
未知时,您可以从TUpper
安全地读取<{1}}的值。
你可以做的是投射到合适的类型:
Foo<*>
然而,这些类型的演员阵容容易出错,更安全的方式如下;
(accessors[FIRST_NAME] as FormFieldAccessor<String>).setValueToBuilder(builder, field.value)
对星形投影地图的访问被包裹在一个对象中,使用此解决方案访问这些值是安全的。
你可以像这样使用它:
object FormFieldProvider {
private val accessors = mutableMapOf<String, FormFieldAccessor<*>>()
fun <T : Any> addAccessor(key: String, fieldValidator: FormFieldAccessor<T>) {
accessors[key] = fieldValidator
}
@Suppress("UNCHECKED_CAST")
operator fun <T : Any> get(key: String): FormFieldAccessor<T> =
accessors[key] as? FormFieldAccessor<T>
?: throw IllegalArgumentException(
"No accessor found for $key")
}
答案 1 :(得分:0)
@ s1m0nw1的答案为您提供问题的原因和简单的修复。但是,根据您的设置,可能还有另一种可能性。
添加
fun setValueFromForm(builder: Builder, fieldDefinition: FormFieldDefinition) {
setValueToBuilder(builder, getFormField(fieldDefinition).value)
}
到FormFieldAccessor<T>
。由于其签名不涉及T
,因此可以在FormFieldAccessor<*>
上安全地调用它。
如果您还将FormFieldDefinition
存储在地图中,现在可以将其称为
accessors[FIRST_NAME]!!.setValueToBuilder(builder, fieldDefinitions[FIRST_NAME]!!)
进一步的改进取决于您系统的细节。
编辑:
即使没有看到Java代码使用原始类型(即FormFieldAccessor
而不是FormFieldAccessor<Something>
或FormFieldAccessor<?>
),我也相当肯定。像
Map<String, FormFieldAccessor> accessors = new HashMap<>();
...
accessors.get(FIRST_NAME).setValueToBuilder(builder, field.value);
但这也不安全,编译器只是忽略了问题,而不是告诉你它们。 accessors.get(FIRST_NAME)
的价值实际上仍可以是一个FormFieldAccessor<Boolean>
,在这种情况下,setValueToBuilder
将失败并显示ClassCastException
。您可以通过不小心将错误的名称传递给get
或在地图中存储错误的访问者来看到这一点:它不会阻止任何编译。
相反,更好的Java代码将使用Map<String, FormFieldAccessor<?>>
,然后需要在get
之后进行转换,就像Kotlin代码一样。
原始类型的存在主要是为了允许非常旧的Java-5前代码仍然编译。 Kotlin没有这个考虑因素,所以它不支持原始类型,你唯一的选择就是用 在Java中做的事情。
答案 2 :(得分:0)
在这种情况下,您可以添加一个将Any用作值并检查其类型的包装器方法
internal interface FormFieldAccessor<T> {
fun getFormField(formFieldDefinition: FormFieldDefinition): FormField<T>
fun _setValueToBuilder(builder: Builder, value: T)
fun setValueToBuilder(builder: Builder, value: Any){
val value = value as? T ?: return
_setValueToBuilder(builder, value)
}
fun accept(visitor: FormFieldVisitor)
fun getValue(personalInfo: PersonalInfo): T
}