我试图创建以下类:
class MyClass {
var foos: List<Foo> = listOf()
constructor(foos: List<Foo>) {
this.foos = foos
}
constructor(bars: List<Bar>) : super() {
this.foos = bars.map { bar ->
Foo(bar)
}
}
}
但是,我收到一条错误消息:
平台声明冲突:以下声明具有相同的JVM签名((Ljava / util / List;)V):
我知道它们都是List对象,但是它们都是用泛型键入的,所以我确定这不会有问题。
答案 0 :(得分:1)
这不是Kotlin问题,而是Java泛型的一种机制。 该机制旨在避免仍然使用原始类型的旧代码中发生冲突。
例如,如果您使用这样的Java创建类:
public class MyClass {
private List<Foo> foos;
MyClass(List<Foo> foos) {
this.foos = foos;
}
MyClass(List<Bar> bars) {
List<Foo> foos = new ArrayList<>();
bars.forEach(bar -> foos.add(new Foo(bar)));
this.foos = foos;
}
}
您将收到一个编译时错误消息:
“ MyClass(List)”与“ MyClass(List)”相伴;两种方法的擦除相同
类型擦除的作用是仅在编译时强制执行类型约束,并在运行时丢弃元素类型信息。类的类型参数在代码编译期间被丢弃,并用其第一个绑定或Object
替换(如果该类型参数未绑定)。因此,这两个构造函数(因为它们都是无界的)将属于List,因此会出现错误。
答案 1 :(得分:0)
您会遇到此问题,因为在Java中存在一个称为type erasure的东西。而且由于kotlin使用JVM,它也受此限制的影响。给予TL; DR;
泛型类型保留在.class
文件中,因此Java知道该类(在您的情况下为List
)是泛型的。但是它无法跟踪实例的泛型类型。因此,实例List<Foo>
和List<Bar>
在运行时(List
)都以其raw type的形式进行处理。请记住,泛型仅在编译时使用,以确保类型安全。
要克服此限制,可以在kotlin中使用运算符重载。我们正在查看的运算符是()
,它使您可以调用任何实例。通过使用companion object
,我们甚至可以做到这一点
invoke
看起来像一个构造函数,并且像一个(MyClass()
)一样被调用。您的代码如下所示:
class MyClass(var foos: List<Foo>) {
companion object {
operator fun invoke(bars: List<Bar>) = MyClass(bars.map(::Foo))
}
}
您可以这样简单地调用它:
val mc1 = MyClass(foos) // calls constructor
val mc2 = MyClass(bars) // calls companion.invoke
答案 2 :(得分:0)
正如其他人提到的,这是由于Java的类型擦除。在某些情况下,伴随对象方法可以工作,但在其他情况下则不能。
我使用的一种变通办法是,如果您只有少数构造函数发生冲突,则可以利用Java的继承优势。
例如,您可以执行以下操作:
Collection
请注意,参数会发生变化(List
,ArrayList
,Foo
)。您的错误应该消失了,您应该能够传递Bar
,Baz
或ArrayList
的ArrayList对象,Java会调用正确的构造函数。
同样,您只能对少数构造函数执行此操作,如果您愿意对调用构造函数的参数类型进行一些调整(例如,将它们声明为List
而不是{{ 1}})。