【问题标题】:Java/Kotlin cast exception for visitor pattern with generic return type [duplicate]具有通用返回类型的访问者模式的 Java/Kotlin 转换异常 [重复]
【发布时间】:2017-12-30 19:49:09
【问题描述】:

我正在尝试使用带有返回值的访问者模式 but

但是,虽然没有显式强制转换,但我得到了 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 预计 CharSequences;
  2. 我正在调用TreeElem.accept 的泛型重载,它返回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 案例的错误是一样的。

【问题讨论】:

    标签: java generics kotlin type-erasure visitor-pattern


    【解决方案1】:

    我很确定这是这个问题的重复:https://stackoverflow.com/a/9058259/4465208

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

    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(" ")
        }
        // ...
    }
    

    没有那么漂亮,但它与泛型配合得很好。

    【讨论】:

    • 有道理,谢谢!不知怎的,我没有认出[L,或者我可能已经意识到转换失败了,因为它是一个数组......
    • 这是一件很容易错过的事情,一直发生在我身上:)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-04-20
    • 1970-01-01
    • 2019-09-21
    • 1970-01-01
    • 1970-01-01
    • 2019-05-25
    相关资源
    最近更新 更多