比方说,我想定义一个接口来描述包含相似对象的对象的任何树形结构。一个明显的实现是:
interface HasChildren {
val children: Sequence<HasChildren>
}
class Branch(val children: Sequence<Branch>) : HasChildren
与此有关的一个或多或少的明显问题是,尽管Branch
的子代必须始终是Branch
,但是它已经通过接口丢失了,我必须显式地对它们进行类型转换。回来。
有没有一种方法,类似于Swift中的Self
:
protocol HasChildren {
let children : Sequence<Self> { get }
}
不丢失实现类的类型?
答案 0 :(得分:2)
这叫做F-bounded polymorphism(本文是为Scala写的,但这没关系;我找不到我喜欢使用Java或Kotlin的解释,但是概念完全相同):>
interface HasChildren<T : HasChildren<T>> {
val children: Sequence<T>
}
class Branch(override val children: Sequence<Branch>) : HasChildren<Branch>
如果您熟悉C ++,则在这里称为"curiously recurring template pattern"。
答案 1 :(得分:1)
我认为您可能想看看sealed class
es。使用它们的树看起来像这样:
sealed class Node<out T> {
class Branch<T>(val left: Node<T>, val right: Node<T>) : Node<T>()
class Leaf<T>(val value: T) : Node<T>()
object Empty : Node<Nothing>()
}
sealed class
支持使用when
进行类型检查:
when(node) {
is Branch-> {
// ...
}
is Leaf -> {
// ...
}
is Empty -> {
// ...
}
}
用法:
val tree = Branch(
Leaf("Foo"),
Branch(Leaf("baz"), Empty))
official docs也是一个很好的起点。
编辑:AFAIK,如果没有类型转换,您将无法做到这一点。我已经将一个简单的程序与一个hack结合在一起:
object Playground {
@JvmStatic
fun main(args: Array<String>) {
val tree = Branch(
Leaf("Foo"),
Branch(Leaf("baz"), Empty))
// works
val foo = tree.left.castTo<Node.Leaf<String>>()
println(foo)
// oops
tree.left.castTo<Node.Empty>()
}
}
sealed class Node<out T> {
data class Branch<T>(val left: Node<T>, val right: Node<T>) : Node<T>()
data class Leaf<T>(val value: T) : Node<T>()
object Empty : Node<Nothing>()
}
inline fun <reified U : Node<Any>> Node<Any>.castTo(): U {
require(this is U) {
"Node '${this::class.simpleName}' is not of required type '${U::class.simpleName}'."
}
return this as U
}
我真的很想看到一个更强大的解决方案,因为这很丑陋,但是到目前为止我还没有找到更好的解决方案(我有时也会遇到这个问题)。