如何在Kotlin中指定"自己的类型"作为返回类型

Alf*_*nso 13 kotlin

有没有办法将函数的返回类型指定为被调用对象的类型?

例如

trait Foo {
    fun bar(): <??> /* what to put here? */ {
        return this
    }
}

class FooClassA : Foo {
    fun a() {}
}

class FooClassB : Foo {
    fun b() {}
}

// this is the desired effect:
val a = FooClassA().bar() // should be of type FooClassA
a.a()                     // so this would work

val b = FooClassB().bar() // should be of type FooClassB
b.b()                     // so this would work
Run Code Online (Sandbox Code Playgroud)

实际上,这与instancetypeObjective-C或SelfSwift中的大致相同.

And*_*lav 16

没有支持此功能的语言功能,但您始终可以使用递归泛型(这是许多库使用的模式):

// Define a recursive generic parameter Me
trait Foo<Me: Foo<Me>> {
    fun bar(): Me {
        // Here we have to cast, because the compiler does not know that Me is the same as this class
        return this as Me
    }
}

// In subclasses, pass itself to the superclass as an argument:
class FooClassA : Foo<FooClassA> {
    fun a() {}
}

class FooClassB : Foo<FooClassB> {
    fun b() {}
}
Run Code Online (Sandbox Code Playgroud)

  • 在Kotlin M12中已弃用了Trait,我们将改用关键字interface:https://blog.jetbrains.com/kotlin/2015/05/kotlin-m12-is-out/ (3认同)

Pat*_*ick 11

您可以使用扩展功能返回自己的类型.

interface ExampleInterface
// Everything that implements ExampleInterface will have this method.
fun <T : ExampleInterface> T.doSomething(): T {
    return this
}

class ClassA : ExampleInterface {
    fun classASpecificMethod() {}
}

class ClassB : ExampleInterface {
    fun classBSpecificMethod() {}
}

fun example() {
    // doSomething() returns ClassA!
    ClassA().doSomething().classASpecificMethod()
    // doSomething() returns ClassB!
    ClassB().doSomething().classBSpecificMethod()
}
Run Code Online (Sandbox Code Playgroud)


Yog*_*ity 7

递归类型绑定

您在问题中显示的模式在 JVM 世界中被称为递归类型绑定。递归类型是一种包含使用该类型本身作为其参数或其返回值的类型的函数的类型。在您的示例中,您通过说 来使用相同的类型作为返回值return this


例子

让我们通过一个简单而真实的例子来理解这一点。我们将从trait您的示例中替换为interface因为trait现在在 Kotlin 中已弃用。在此示例中,接口VitaminSource返回不同维生素来源的不同实现。

在下面interface,您可以看到它的类型参数以自身作为上限。这就是为什么它被称为递归类型绑定:

维生素来源.kt

interface VitaminSource<T: VitaminSource<T>> {
    fun getSource(): T {
        @Suppress("UNCHECKED_CAST")
        return this as T
    }
}
Run Code Online (Sandbox Code Playgroud)

我们抑制UNCHECKED_CAST警告,因为编译器不可能知道我们是否传递了相同的类名作为类型参数。

然后我们用具体的实现来扩展interface

胡萝卜.kt

class Carrot : VitaminSource<Carrot> {
    fun getVitaminA() = println("Vitamin A")
}
Run Code Online (Sandbox Code Playgroud)

香蕉.kt

class Banana : VitaminSource<Banana> {
    fun getVitaminB() = println("Vitamin B")
}
Run Code Online (Sandbox Code Playgroud)

在扩展类时,您必须确保将相同的类传递给接口,否则您将ClassCastException在运行时得到:

class Banana : VitaminSource<Banana>    // OK
class Banana : VitaminSource<Carrot>    // No compiler error but exception at runtime
Run Code Online (Sandbox Code Playgroud)

测试.kt

fun main() {
    val carrot = Carrot().getSource()
    carrot.getVitaminA()

    val banana = Banana().getSource()
    banana.getVitaminB()
}
Run Code Online (Sandbox Code Playgroud)

就是这样!希望有帮助。


Bob*_*obV 5

您可以使用扩展方法来实现"返回相同类型"效果.这是一个快速示例,显示了具有多个类型参数的基类型和一个扩展方法,该方法接受一个对所述类型的实例进行操作的函数:

public abstract class BuilderBase<A, B> {}

public fun <B : BuilderBase<*, *>> B.doIt(): B {
  // Do something
  return this
}

public class MyBuilder : BuilderBase<Int,String>() {}

public fun demo() {
  val b : MyBuilder = MyBuilder().doIt()
}
Run Code Online (Sandbox Code Playgroud)

由于扩展方法是静态解析的(至少从M12开始),如果需要this特定于类型的行为,您可能需要让扩展委托实际的实现.