Is it possible to specify a static function in a Kotlin interface?
Asked Answered
R

2

31

I want to do something like this:

interface Serializable<FromType, ToType> {
    fun serialize(): ToType
    companion object {
        abstract fun deserialize(serialized: ToType): FromType
    }
}

or even this would work for me:

interface Serializable<ToType> {
    fun serialize(): ToType
    constructor(serialized: ToType)
}

but neither compiles. Is there a syntax for this, or will I be forced to use make this an interface for a factory? Or is there another answer? 😮 That'd be neat!

Remake answered 2/11, 2016 at 0:2 Comment(0)
T
28

Basically, nothing in a companion object can be abstract or open (and thus be overridden), and there's no way to require the implementations' companion objects to have a method or to define/require a constructor in an interface.

A possible solution for you is to separate these two functions into two interfaces:

interface Serializable<ToType> {
    fun serialize(): ToType
}

interface Deserializer<FromType, ToType> {
    fun deserialize(serialized: ToType): FromType
}

This way, you will be able to implement the first interface in a class and make its companion object implement the other one:

class C: Serializable<String> {
    override fun serialize(): String = "..."

    companion object : Deserializer<C, String> {
        override fun deserialize(serialized: String): C = C()
    }
}

Also, there's a severe limitation that only a single generic specialization of a type can be used as a supertype, so this model of serializing through the interface implementation may turn out not scalable enough, not allowing multiple implementations with different ToTypes.

Toothsome answered 2/11, 2016 at 6:27 Comment(4)
But why? Why was it designed this way? – Attitudinarian
How would I invoke deserialize assuming I adopt this model? – Krishna
@DavidBerry Just like you would any static method in Java: C.deserialize() – Spumescent
The companion object can implement an interface? This is freakin' awsome! – Trevar
T
6

For future uses, it's also possible to give the child class to a function as a receiver parameter:

val encodableClass = EncodableClass("Some Value")
//The encode function is accessed like a member function on an instance
val stringRepresentation = encodableClass.encode()
//The decode function is accessed statically
val decodedClass = EncodableClass.decode(stringRepresentation)

interface Encodable<T> {
    fun T.encode(): String
    fun decode(stringRepresentation: String): T
}

class EncodableClass(private val someValue: String) {
    // This is the remaining awkwardness, 
    // you have to give the containing class as a Type Parameter 
    // to its own Companion Object
    companion object : Encodable<EncodableClass> {
        override fun EncodableClass.encode(): String {
            //You can access (private) fields here
            return "This is a string representation of the class with value: $someValue"
        }
        override fun decode(stringRepresentation: String): EncodableClass {
            return EncodableClass(stringRepresentation)
        }
    }
}


//You also have to import the encode function separately: 
// import codingProtocol.EncodableClass.Companion.encode

This is the more optimal use case for me. Instead of one function in the instanced object and the other in the companion object like your example, we move both functions to the companion object and extend the instance.

Tamarau answered 19/7, 2022 at 12:35 Comment(1)
Oh that's very nice; I like it a lot – Remake

© 2022 - 2024 — McMap. All rights reserved.