2

What I want is this:

interface base {
  abstract static fun foo()
}

class impl : base { 
  override static fun foo()
}

Normally, Kotlin solves problems using companion objects rather than static functions. But an interface can't define a requirement for a companion object with function. So how can I accomplish this? The code that uses this would look like

fun <T : base> bar() {
  T.foo()
}

Any other way to get this behavior? Namely, that I can execute a function of a derivative of T, without knowing the specific type, and not assuming the derivative has a default constructor?

Edit

I was able to get this to do what I want by using value parameters of types that can be set on the companion objects of the classes I want to work with. An illustrative example of what I want to use this technique for.

import kotlin.reflect.full.*

interface DynamicBuilder {
    fun build(sides: Int): Shape?
}

interface Shape {
    companion object : DynamicBuilder {
        override fun build(sides: Int) = null
    }
}

abstract class Shape2D : Shape {
    companion object : DynamicBuilder {
        override fun build(sides: Int) = if(sides > 0) Square() else Circle()
    }
}

abstract class Shape3D : Shape {
    companion object : DynamicBuilder {
        override fun build(sides: Int) = if(sides > 0) Cube() else Sphere()
    }
}

class Square : Shape2D()
class Circle : Shape2D()
class Sphere : Shape3D()
class Cube : Shape3D()

fun Build(sides: Int, builder: DynamicBuilder): Shape? {
    return builder.build(sides)
}

inline fun <reified T : Shape> Build(sides: Int): Shape? {
    return Build(sides, T::class.companionObjectInstance as DynamicBuilder)
}

fun main() {
    println(Build(0, Shape2D))
    println(Build(4, Shape2D))

    println(Build<Shape3D>(0))
    println(Build<Shape3D>(6))
}

The goal is that I can create a new entire class of Shape, and have all the logic related to how it builds the concrete object contained in that file, rather than having some monolithic shared switch statement.

Rollie
  • 4,391
  • 3
  • 33
  • 55
  • Possible duplicate: https://stackoverflow.com/questions/51266313/is-it-possible-to-override-static-method-in-kotlin – Mafor Dec 20 '19 at 11:35
  • 1
    This seems like an [XY problem](https://en.wikipedia.org/wiki/XY_problem). Maybe you wish to pass a method reference to your method bar? – findusl Dec 20 '19 at 12:27
  • @Rollie Isn't it a bit over-engineered? – Mafor Dec 20 '19 at 13:05
  • @Mafor It's a minimal example of the technique, and isn't otherwise related to how I intend to use it. – Rollie Dec 20 '19 at 13:10

3 Answers3

4

An interface can define a requirement for some object with function, and you can suggest it to be the companion object even if you can't force it to be.

interface BaseCompanion {
    fun foo(): Unit
}

interface Base {
    companion object : BaseCompanion {
        fun foo() { println("in Base") }
    }

    fun companion(): BaseCompanion = Base
}

interface Derived : Base {
    companion object : BaseCompanion {
        fun foo() { println("in Derived") }
    }

    override fun companion() = Derived
}

// value parameter, not type parameter
fun bar(companion: BaseCompanion) {
    companion.foo()
}

bar(Base)
bar(Derived)

The companion() function isn't actually used in this case, it's for when you want to access the companion from a Base instance:

fun baz(x: Base) {
    x.companion().foo()
}

Another (unsafe) option is to define companion() using reflection.

fun companion() = this::class.companionObjectInstance as BaseCompanion

Plus: no need to explicitly override it in Derived; minuses: 1. will crash at runtime if you forget to create the companion or to extend BaseCompanion; 2. slower than non-reflection definition.

Alexey Romanov
  • 167,066
  • 35
  • 309
  • 487
  • Ah very nice! I didn't know types could be used an auto-cast to their companion types like that. I'll update my question with the working version closer to what I'm actually doing. Thank you :) – Rollie Dec 20 '19 at 12:35
  • There's no auto-casting here, `Base` when used as a value just refers to the companion object. – Alexey Romanov Dec 20 '19 at 12:37
  • Understood. Still wrapping my head around some of the concepts/nomenclature, but getting better :) – Rollie Dec 20 '19 at 12:41
  • 1
    It is just a regular inheritance, mangled with additional abstractions. In the first example what is passed to the `bar` function is a `BaseCompanion` **instance**, `Base` and `Derived` could be removed completely. In the second example, the `companion` method is overridden, companion objects could be removed. – Mafor Dec 20 '19 at 12:50
  • "It is just a regular inheritance" Yes. The benefit of Kotlin here is exactly that regular inheritance is available for companion objects where it isn't for Java's static methods. By "the second example" I am not sure whether you mean `baz` or the reflection-based definition of `companion()`, but in neither case companion objects can be removed. – Alexey Romanov Dec 20 '19 at 13:04
  • `companion()` can be overridden not to return a companion object, but this is mentioned at the beginning. – Alexey Romanov Dec 20 '19 at 13:08
  • 1
    My point is that in both cases there is an object instance involved, so it does not fulfill the original question requirements: *not assuming the derivative has a default constructor* – Mafor Dec 20 '19 at 13:11
0

TL;TR:

  • How can I enforce that a class has a companion object?
  • You cannot.

Kotlin has no static methods. Even if it had them, they wouldn't be overridable, as they are not in Java. The same holds for companion objects. Kotlin code is eventually compiled to Java byte code, so what is not possible in Java won't be possible in Kotlin either.

Edit:

It's interesting to see what the compiler has to say about it. Consider the following snippet:

open class Base {
    companion object {
        fun test() {}
    }
}

inline fun <reified T : Base> staticCall() {
    T.test() // <-- ERROR
}

The error message:

Type parameter 'T' cannot have or inherit a companion object, so it cannot be on the left hand side of dot

Mafor
  • 9,668
  • 2
  • 21
  • 36
  • So for example, you could achieve this if all derived types were default constructable, create an object of the appropriate type using reflection, then calling the method on that object. But that creates an unnecessary requirement. If it were allowed, you could do `(null as T).foo()`, because by definition, there is no need for `this`. I'm not looking for static methods. I'm looking for a solution to do achieve an effect. – Rollie Dec 20 '19 at 11:57
  • @Rollie then maybe you should rewrite your question. You are writing you want to override static functions. That is not possible in java and therefore not in Kotlin. This answer answers the question as written currently. – findusl Dec 20 '19 at 12:04
  • @findusl Was the title of this question "How do I make a static function in Kotlin? Funny, it looks to me to be "How can I enforce that a class has a companion object?"; and then later go on to ask "Any other way to get this behavior?" For what it's worth, "I want xxx" isn't a question (as suggested by the lack of question mark). – Rollie Dec 20 '19 at 12:12
  • @Rollie `if all derived types were default constructable` - it cannot be forced – Mafor Dec 20 '19 at 12:16
  • @Mafor (responding to your edit) That is interesting - I wonder why. Using reflection to get the companion object *does* work though (as in my edit). – Rollie Dec 20 '19 at 13:02
  • @Rollie I guess the compiler's message relates to the **type parameter** itself, not to the underlying class. – Mafor Dec 20 '19 at 13:16
0

Based on your updated question, it seems like what you want is usually achieved using the factory pattern. Alternatively you could also use dependency injection. There are many options without the usage of reflection.

Why shouldn't you use reflection?

There are a few reasons here and here and you can find more if you google it. Generally reflection was created for a specific purpose, to discover the functionality of a class that was unknown at compile time. You do not use it for this purpose, since your implementation requires you to know the class, in order to pass it as a reified generic parameter. If you do require to discover classes that you don't know at compile time, you can use dependency injection.

The simpler solution for your version is a factory pattern:

interface Shape
class Square : Shape
class Circle : Shape
class Sphere : Shape
class Cube : Shape

object ShapeFactory {
    fun build2DShape(sides: Int): Shape {
        if(sides > 0) Square() else Circle()
    }

    fun build3DShape(sides: Int): Shape {
        if(sides > 0) Cube() else Sphere()
    }
}

fun main() {
    println(ShapeFactory.build2DShape(0))
    println(ShapeFactory.build3DShape(0))
}

In short, Build<Shape3D>(0) is replaced by ShapeFactory.build3DShape(0). The caller still has to know that there are 3DShapes and where they are. The only thing that changed is that you do not require Reflection.

This requires the person calling the function to know of the existence of 2D and 3D shapes. Same as in your implementation with reflection. This way you can have all the logic how to create the shapes in the same file as the shapes. You could even make the factory call some functions in the companion object of the shape if you wish to do so. Your factory knows of the existence of those subclasses. But since you can put the factory in the same file as the subclasses, that doesn't split the logic to somewhere else.

If you want to delegate the deciding whether it is a 2D or a 3D shape to a subclass you can do the following:

interface Shape
class Square : Shape
class Circle : Shape
class Sphere : Shape
class Cube : Shape

object ShapeFactory {
    fun build2DShape(sides: Int): Shape {
        return if(sides > 0) Square() else Circle()
    }

    fun build3DShape(sides: Int): Shape {
        return if(sides > 0) Cube() else Sphere()
    }
}

fun getBuilder(dimensions: Int): (sides: Int) -> Shape {
    if (dimensions == 2)
        return ShapeFactory::build2DShape
    else 
        return ShapeFactory::build3DShape
}

fun main() {
    print (getBuilder(2)(3))
}
findusl
  • 2,454
  • 8
  • 32
  • 51
  • As mentioned, the code I gave is a minimal example. My actual use case is I want to deserialize objects from a MongoDB server without needing to explicitly create different access methods. So the client writes something like `db.metadata.getData(id)`, it gets the json blob for a shared collection, and uses common data to all data classes to infer an expected type. Then I deserialize the json into the desired structure. It's possible the caller knows the type and can pass it in as a generic param, or it's possible it just wants to act as a passthrough. – Rollie Dec 20 '19 at 14:51
  • @Rollie The solution that you have using reflection will always require the caller to know some part of the type. And then the one being called needs to know the exact type. Translated to the factory pattern, you have a function in the factory that takes the database data and knows the exact types and passes it on to those. – findusl Dec 20 '19 at 14:58
  • @Rollie Basically in your solution you have a few companion objects each with a function and in order to call these you need to know the class type that contains the companion object. Instead I propose moving those functions to a factory class and calling them there. That avoids the reflection but keeps the amount of functions same. – findusl Dec 20 '19 at 15:01
  • The caller actually doesn't need to know the type - I can return a `Shape` from the db logic, and then respond to a gRPC request with a serialized version of the `Shape`, all without ever knowing if it was a `Square` or `Circle`. Yes, the class does need to be eventually determined, but I specifically want to break out the logic for each 'subtype' (`Shape2D`, `Shape3D`). Then if I want to add `Shape4D` someday, I just need to implement it, without touching any of the serialization plumbing already in place. – Rollie Dec 20 '19 at 15:21
  • @Rollie you need to touch it to some extend in order to pass it as a reified type. I do not see any case that would be solved with your current setup but can't be solved with a factory. I also added a version with method reference to show you how you can pass the exact type from somewhere else – findusl Dec 20 '19 at 16:30