Kotlin-无法创建具有不同List类型参数的两个构造函数

时间:2019-05-30 20:09:01

标签: java generics kotlin jvm

我试图创建以下类:

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对象,但是它们都是用泛型键入的,所以我确定这不会有问题。

3 个答案:

答案 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

请注意,参数会发生变化(ListArrayListFoo)。您的错误应该消失了,您应该能够传递BarBazArrayList的ArrayList对象,Java会调用正确的构造函数。

同样,您只能对少数构造函数执行此操作,如果您愿意对调用构造函数的参数类型进行一些调整(例如,将它们声明为List而不是{{ 1}})。