Superclass of companion seem unable to use generic parameter of class


#1

Say I have this code layout.

class CompanionSuperclass<T>

class MainGenericClass<T>{
    companion object : CompanionSuperclass<T>
}

The last occurrence of T in that snippet is marked by IDEA as an unresolved reference, whereas it stand to reason that it shouldn’t be.


#2

Why? There is no reason to assume that type parameter in class MainGenericClass is the same as in the compoanion object, especially considering objects cannot have parameter types.

I suspect what you want to achieve is this:

abstract class CompanionSuperclass<T>

class MainGenericClass<T>{
    companion object<T> : CompanionSuperclass<T>()
}

but it makes no sense as there is only one instance of the companion object for all instances of MainGenericClass and its subclasses - therefore the companion object must have a concrete type.

If you want to have a generic parameter in companion object’s functions you can get it like this:

abstract class CompanionSuperclass {
    fun <T> p(input: T) = print(input)
}

open class MainGenericClass<T>{
    companion object : CompanionSuperclass()
}

class SubClass : MainGenericClass<String>() {
    init {
        p<String>("a")
    }
}

although in this simple example you can omit <String> in invocation of function p as it can be inferred form the argument.


#3

A specific, slightly scenario of what I want: I have several classes that share some common “static” functionality, but are otherwise non-related by inheritance. So, let’s say I have classes Pet and Car. I want each of these classes to have a class-wide list with all instances of that class. So I want a List and a List. Furthermore, whenever a pet or car is created, it should be added to the respective list.

To generalise the behaviour and reuse code, I wanted to do something like this:

class CensusCompanion<T> {
    val list = ArrayList<T>()
    val count : int
        get() = list.count
    fun <T> T.addToCensus() { list.add(this) }
}

Then I could hve

class Pet {
    init { addToCensus() }
    companion object : CensusCompanion<Pet>
}         

And similarly with car.

But then let’s say I want my ontology to also include a generic License type (to encompass things such as driving licenses and permits for having animals), and for this class I also want the census static functionality. So that would naturally read as

class License<T>{
    init { addToCensus() }
    companion object : CensusCompanion<License<T>>
}         

What would instead be the idiomatic way to achieve this?

P.S. Please forgive any possible erors in my snippets of code.


#4

Your problem is that you seem to think that companion object is simply a storage container for static stuff. It’s not. It is a singleton object bound to a class. With that in mind this is what I came up with as a solution of your problem:

import java.util.*

open class CensusCompanion<T> {
    val list = ArrayList<T>()
    val count : Int
        get() = list.size
    fun T.addToCensus() { list.add(this) }
}

open class License<T>{
    init { addToCensus() }
    companion object : CensusCompanion<License<*>>()
}

class Pet
class Driver

class PetLicense : License<Pet>() {
    override fun toString(): String = "PetLicense"
}

class DriverLicense : License<Driver>() {
    override fun toString(): String = "DriverLicense"
}

fun main() {
  val pl = PetLicense()
  val dl = DriverLicense()
  println(License.list[0])
  println(License.list[1])
}

#5

There is only one companion shared by all instances of the class. So the list would be shared with mixed instances of License, License, License all combined. This is due to the erased generics. If you want to split that out, you either have subclasses of License that specify the type: class DrivingLicense: License<Driving> or you manually maintain separate lists for the different content types based on their runtime type.