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!

2 Answers 2

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.

4
  • 11
    But why? Why was it designed this way?
    – prompteus
    Commented May 27, 2017 at 17:35
  • How would I invoke deserialize assuming I adopt this model? Commented Jun 10, 2018 at 3:00
  • @DavidBerry Just like you would any static method in Java: C.deserialize() Commented Apr 15, 2019 at 10:33
  • 2
    The companion object can implement an interface? This is freakin' awsome!
    – t3chb0t
    Commented Jan 22, 2022 at 20:43
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.

1
  • 1
    Oh that's very nice; I like it a lot
    – Ky -
    Commented Aug 11, 2023 at 1:48

Not the answer you're looking for? Browse other questions tagged or ask your own question.