29

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!

Ky -
  • 30,724
  • 51
  • 192
  • 308

2 Answers2

26

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.

Community
  • 1
  • 1
hotkey
  • 140,743
  • 39
  • 371
  • 326
5

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.

Omnieboer
  • 51
  • 2
  • 4