[Companion objects](https://www.baeldung.com/kotlin/companion-object)
is an interesting feature in Kotlin, meant to replace the concept of static members found in languages like Java, C#, and Python. To understand its benefits, let’s examine how static members work in these other languages.
In Java, static members (fields and methods) are accessible from both the class itself and instances of the class (though accessing them through instances isn’t recommended). They are inherited, but static methods are not virtual. Resolution happens at compile-time based on the compile-time type, which is important when calling them through an instance. If a subclass defines a static method with the same signature as a parent class, invoking it through a parent variable referencing a child instance will execute the parent’s method due to compile-time resolution and the lack of polymorphism for static members. More information can be found here.
C# addresses this Java behavior by allowing access to static members only through the class, not instances. Inheritance remains (a child class can access a parent’s static member), and you can redefine a static method in a subclass using the new
modifier.
Modern JavaScript, with its addition of static members (though classes are essentially syntactic sugar on top of its prototypical nature), handles static members similarly to C#. Access is restricted to the class, inheritance is preserved, and redefinition in subclasses is allowed.
|
|
This implementation likely involves setting properties directly on the class object (e.g., Person.shout = function(){};
). Inheritance works because a child “class” prototype ([[Prototype]]
) points to the parent.
Interestingly, static methods can and should access other static methods within the same class using this
. This makes sense because this
is dynamic, representing the “receiver,” which is the class itself in a static context. Using this
instead of the class name enables a form of polymorphism, as demonstrated below:
|
|
Note how using this
calls Child.shout()
, while kick()
remains tied to the parent’s implementation.
Python handles static/class members uniquely. Attributes declared in a standard class belong to the class by default. Static data attributes require no special keywords, while static/class methods use the @classmethod
decorator (if interacting with other class methods) or @staticmethod
decorator otherwise. Method invocation in Python uses an attribute lookup algorithm. explained here Functions are data descriptors with a __get__
method. When fetched, this method executes, creating a bound method object (bound to the instance or class) or a staticmethod object (unbound) depending on the decorator. Consequently, class/static methods can be invoked through the class or an instance, inheritance is preserved, and polymorphism, similar to JavaScript, functions correctly.
Here’s an example:
|
|
While you can read a static attribute (e.g., planet
) via the class or an instance, modifying it through an instance adds it to the instance instead of updating the class.
Lastly, when using dataclasses where instance members are declared at the class level, use the ClassVar
type annotation (e.g., cvar: ClassVar[float] = 0.5
) to declare static/class attributes.