Java/Kotlin 为具有通用 return 类型的访问者模式转换异常

Java/Kotlin cast exception for visitor pattern with generic return type

我正在尝试使用类似访客模式的东西, 和 return 值。

然而,虽然没有显式转换,但我收到了 ClassCastException:

Exception in thread "main" java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.CharSequence;
    at Printer.combine(...)
    at Split.accept(...)
    at MWEKt.main(...)

代码:

interface TreeElem {
    fun <T> accept(visitor: TreeVisitor<T>): T
}

class Leaf: TreeElem {
    override fun <T> accept(visitor: TreeVisitor<T>): T {
        return visitor.visit(this)
    }
}

class Split(val left: TreeElem, val right: TreeElem): TreeElem {
    override fun <T> accept(visitor: TreeVisitor<T>): T {
        return visitor.combine(  // this causes cast error
            visitor.visit(this),
            left.accept(visitor),
            right.accept(visitor))
    }
}

interface TreeVisitor<T> {
    // multiple implementations with different T in future (only one in this example)
    fun visit(tree: Leaf): T
    fun visit(tree: Split): T
    fun combine(vararg inputs: T): T
}

class Printer: TreeVisitor<CharSequence> {
    override fun combine(vararg inputs: CharSequence): CharSequence { // error here
        return inputs.joinToString(" ")
    }
    override fun visit(tree: Leaf): CharSequence { return "leaf" }
    override fun visit(tree: Split): CharSequence { return "split" }
}

fun main(args: Array<String>) {
    val tree = Split(Leaf(), Leaf())
    val printer = Printer()
    println(tree.accept(printer))
}

我不知道是什么问题。我是在尝试做一些不可能的事情,还是我没有正确表达它,或者是类型擦除使一些应该可能的事情变得不可能?

到目前为止我的想法:

  1. Printer.combine 预计 CharSequence 秒;
  2. 我正在调用 TreeElem.accept 的通用重载 returns CharSequence
  3. 编译器可能会在 JVM 代码中插入强制转换(类型擦除?)
  4. 但是运行时类型是兼容的,所以转换应该可以工作

由于最后一点与现实主义相冲突,我可能理解有误。

编辑:我已将 MWE 翻译成 Java 以查看它是否是 Kotlin 问题并吸引答案:

interface TreeElem {
    <T> T accept(TreeVisitor<T> visitor);
}

class Leaf implements TreeElem {
    public <T> T accept(TreeVisitor<T> visitor) {
        return visitor.visit(this);
    }
}

class Split implements TreeElem {
    private TreeElem left;
    private TreeElem right;
    Split(TreeElem left, TreeElem right) {
        this.left = left;
        this.right = right;
    }
    public <T> T accept(TreeVisitor<T> visitor) {
        return visitor.combine(
            visitor.visit(this),
            left.accept(visitor),
            right.accept(visitor));
    }
}

interface TreeVisitor<T> {
    T visit(Leaf tree);
    T visit(Split tree);
    T combine(T... inputs);
}

class Printer implements TreeVisitor<CharSequence> {
    public CharSequence combine(CharSequence... inputs) {
        StringBuilder text = new StringBuilder();
        for (CharSequence input : inputs) {
            text.append(input);
        }
        return text;
    }
    public CharSequence visit(Leaf tree) { return "leaf"; }
    public CharSequence visit(Split tree) { return "split"; }
}

public class MWEjava {
    public static void main(String[] args) {
        TreeElem tree = new Split(new Leaf(), new Leaf());
        Printer printer = new Printer();
        System.out.println(tree.accept(printer));
    }
}

错误与 Java 情况相同。

我很确定这是这个问题的重复:

但是,要提供特定的解决方案,您可以将 vararg 参数替换为 List<T>,这样就可以正常工作:

class Split(val left: TreeElem, val right: TreeElem) : TreeElem {
    override fun <T> accept(visitor: TreeVisitor<T>): T {
        return visitor.combine(listOf(
                visitor.visit(this),
                left.accept(visitor),
                right.accept(visitor)))
    }
}

interface TreeVisitor<T> {
    fun combine(inputs: List<T>): T
    // ...
}

class Printer : TreeVisitor<CharSequence> {
    override fun combine(inputs: List<CharSequence>): CharSequence {
        return inputs.joinToString(" ")
    }
    // ...
}

不是很漂亮,但它与泛型配合得很好。