What is the advantage of "companion object" vs static keyword

Again, newby to Kotlin here. Generally, the changes from Java are great improvements. That said…

Why was the companion object concept created instead of using the static keyword for class methods and fields? I find it cumbersome, and it clashes with my style of writing code which is to put public methods at the top of a class and private methods at the bottom. I also understand that accessing methods in the companion object from instance methods is more expensive within a class, as compared with Java.

Am I missing something? Is there some advantage that I do not know about?

3 Likes

If you follow the style of writing the statics at the top of the class in Java, then I suggest to try to consider writing them at the top level (e.g. before class declaration) in Kotlin. For many use-cases it works better than introducing a companion object.

13 Likes

Kotlin has “class” for classes that have multiple instances, and “object” for singletons. I believe Scala makes the same distinction?

“companion object” is an extension of the concept of “object”: an object that is a companion to a particular class, and thus has access to it’s private level methods and properties.

Using the companion object adds consistency to the language design, whereas “static” is not consistent with the rest of the language design.

3 Likes

Java statics are not related to the concept of singletons. A Java static is actually closer to the concept of functions outside of classes in Kotlin. That is, a Java static method or field is a hard reference to method or field that has all of the namespace collision protections of classes and interfaces, and all of the implicit references within the class, that is simply not an instance of the class, but is otherwise a useful paradigm to be bundled with the class. One of the really nice features of Java is that you don’t need to add the class name reference to a static method or field that is within the same class, both from other statics as well as instance methods and initializers. One of Swift’s most glaring failures is the namespace problem - there are no packages, and there are places in the language that require explicit references to the context, resulting in bloated source code that still has namespace collision issues. Java’s solution to the namespace organization and collision issue of large systems was groundbreaking 25 years ago. Why did Kotlin decide to change it? (I assume that a possible answer to that question is this - ‘if it ain’t broke, don’t fix it’ is obviously an anti-pattern for programming language designers.)

I do not agree that somehow a “companion object” is consistent with the design of Kotlin. What design paradigm is it compatible with that statics are not? But then again I’m new here, so maybe I just don’t get it.

Again, I ask, are there any advantages of “companion object” vs statics that I don’t understand?

Is “companion object” a feature of functional programming languages? Is Scala where the construct originated? FWIW, I’ve never encountered an actual large application built with Scala … or Haskell for that matter. Functional programming is a fetish that seems cool but is detrimental to creation, understanding and maintenance of large systems, IMO.

2 Likes

One advantage that companion objects have over static members is that they can inherit from other classes or implement interfaces and generally behave like any other singleton.

However, unless they need access to the private members of a class, the truth is that there’s really no need for static members at all when you can declare ‘top level’ properties and functions in Kotlin.

1 Like

alanfo - you make my point that statics are not singletons. They behave differently, and I like how the behave. I don’t want them to have hidden inherited behavior. They are contained, and useful and cohesive as part of a class’s functionality. Java statics inherit nothing, and that’s a good thing.

The very real advantage in my codebase for less verbosity is the implied reference of the class name. In Java syntax, instead of having to write

WorkerBee.checkValidity(value)

I can just write

checkValidity(value)

when called from any method in the class, static or instance.

1 Like

You don’t need to qualify companion objects with the class name either if you’re calling them from within the same class. For example the following compiles and runs fine:

class SomeClass() {

    val id: Int
     
    init {
       id = nextId++       
    }

    private companion object {
       var nextId = 1
    }
}

fun main(args: Array<String>) {
    repeat(2) { 
        println(SomeClass().id)
    }
} 

However, if you’d still prefer to have ‘real’ static members, you might be interested to know that a proposal to introduce them was included in the recent ‘future features’ poll - see item 17 in this list..

It finished 8th out of 20 in the poll so there was reasonable support even though there were quite a lot of people against it.

2 Likes

Thanks for the information about the list. Implicit enum type qualifiers, package-private access, and static members would be my big three from that list.

What was the argument by those who opposed adding static members? Purity? Such idealism is the quickest path to the junk heap for all of the history of programming languages.

And yes, I know I can bury the companion object in the class to avoid the explicit class reference, but as I said in my first post, the standard for class organization for programs I deal with is to put all fields (properties) at top of the class with statics and non-statics grouped, to be followed by constructors, which is then followed by public methods again grouped by statics vs. non-statics, then followed by private, protected and package-private methods. I have to abandon this organization for my Kotlin classes because of the requirement to group all “static” things inside an obliquely named block, only one of which can exist in the class.

This is not a deal breaker for me, but it certainly is a drawback that quite simply has no reason to exist - both traditional usage and support in targets (including JS and native) is quite easy for Kotlin compiler implementors. The Kotlin language owners should be striving to make it easier for folks like me to migrate from Java without bludgeoning existing practice, at least without a good reason.

And as I said, the designers of Kotlin got a lot of things right. Why they decided to omit this feature is incomprehensible to me, just as is the Swift designers decision to remove prefix and postfix incrementers/decrementers from Swift. Are today’s new programming language designers trying to push folks to C#??? (I do not want to go there because of M$, but a lot of folks will, particularly if the alternatives like Kotlin are less attractive.)

1 Like

It was my first thought about global functions and companion objects, but later I found in fact that global functions are indeed limited to the namespace. But it is not the class namespace, but package namespace. It is a bit unfamiliar for a Java user, but it is really convenient after you get used to it a bit.

The only inconvenience I found so far is that IDE autocomplete has to many variants, but it is not so bad. On the good side, many language features are emulated by global functions and in my opinion it much more important.

2 Likes

I think there is a confusion between ‘class methods’ and the concept of a ‘class object’ as represented in kotlin by companion object.

The term ‘class method’, in java and in most languages, generally refers to: an otherwise described as ‘static’ method with no corresponding object instance accessible from the method, but included with the class because the functionality is useful within the class.

There is no exact equivalent to the java ‘class method’ in kotlin, the functions in the same namespace but external to the class are the closest equivalent.

The companion object introduces functionality not supported at a language level in Java, and for this reason all functionality of the companion object is not equivalent to any functionality of the java language. If you also program in languages like python or ruby, you encounter ‘class variables’, which are shared by all members of the class. These are possible because the class itself is an instance of an object and exists at run time, while the java class has no runtime object corresponding with the class itself. The companion object introduces a runtime object corresponding to the class itself, in addition to the objects corresponding to instances of the class.

Methods of the companion object as have access to the this ‘class runtime object instance’, making them quite distinct from ‘static’ methods which have direct access to no object. Extra functionality comes with a ‘cost’. So companion object methods can have functionality beyond what is possible with static methods, but are not a direct equivalent.

Think of each member of the ‘real class’, being instanced with a reference to the same singleton of a special ‘secondary class’ designed to hold data shared by all members of the ‘main class’. You are actually calling a method the secondary class with calling a companion object method, and not actually having a static method at all.

It is an interesting idea to have companion object methods that access no companion object data, detected and optimised to compile as static methods, but whether including static methods through this path is simple or obscure would need to be debated.

3 Likes

Java has a runtime representation of a class (i.e., Class<X>) which is just more cumbersome to work with than the python equivalent, in addition java has static fields and therefore has support for python’s “class fields”

I think that the main advantage that a companion object has is in inheritance i.e., it can help with situations like logging - where in java you will have to remember creating a log instance in every class and cannot add specialized methods that uses it in base classes, using the companion object you can do the following:

abstract class Log {
    val LOG: Logger

    init {
        val cls = this::class
        if (!cls.isCompanion) throw  UnsupportedOperationException("Log object must be a companion object")
        //getting the acompanied - I really wish that there was a better way to do so..
        val acompanied = Class.forName(cls.java.name.substringBeforeLast('$'))
        LOG = LoggerFactory.getLogger(acompanied)
    }

    inline fun linfo(msg: () -> String) {
        if (LOG.isInfoEnabled) LOG.info(msg())
    }

    inline fun lerror(err: Throwable, msg: () -> String) {
        if (LOG.isErrorEnabled) LOG.error(msg(), err)
    }
    
}

class Test {
    companion object : Log()
    
    fun someMethod() {
        linfo { "starting some method" }
        
        try {
            //do something 
        } catch (err: Throwable) {
            lerror(err) {"something bad happened..."}
        }
    }
}

Other than that - I don’t believe companion objects has any other advantages - but many disadvantages, i.e., slower access time, confusion with X::class and just X for newbies, memory overhead, etc.

I also think that statics should be added to kotlin and companion objects should be deprecated and replaced with some sort of class templating mechanism (metaclasses?) that support the above use case in a more pragmatic way

2 Likes

thanks @bennyl. I have not worked in java for 10 years, and it is kotlin that has brought me back to the jvm world. So i am current with other languages, and getting to a good level with kotlin, but rusty with java

I certainly don’t think that the Kotlin designers are trying to force people who don’t agree with some of their decisions to use C# though, given that they’re not trying to target .NET, it’s clear that they do have respect for that language.

Language designers naturally want their language to be a success, to attract new users and keep their existing users reasonably happy.

Although I’m quite happy with companion objects myself and haven’t really had any issues with them, it wouldn’t bother me if static members were introduced particularly if there are some Java interop issues with them.

However, I wouldn’t want to see companion objects deprecated or removed from the language altogether which would be a major breaking change.

I don’t see why the two couldn’t co-exist given that companion objects offer some stuff which static members don’t as already discussed in this thread.

This wouldn’t be the first time that we have features which offer similar (but not quite the same) things. I’m thinking here of lambdas and anonymous functions where the latter allow you to specify the return type where there is a need to do so and also offer different behavior as regards the ‘return’ keyword.

1 Like

AFAIK, lambdas are anonymous functions (or at least they should be :slight_smile: )

1 Like

Companion objects can be marked with JvmStatic for efficiency ease of use from Java.

Other than that, if your main objection is that it forces you to order your class members differently, I have two suggestions.

First, Kotlin could introduce a companion modifier for class-level functions and properties, which would add them to the definition of the class’s companion object, without them having to appear lexically within a companion object block.

Secondly, just change your style :wink: I’m joking but not really: I’ve moved between languages, companies, and teams enough times now that I’ve just become used to changing my coding style every few years and now it doesn’t bother me. I’m in the middle of translating a moderately-sized family of Gradle plugins from Groovy to Kotlin and I’m having to change style and code layout a little, but not too much.

1 Like

…global functions are indeed limited to the namespace. But it is not the class namespace, but package namespace. It is a bit unfamiliar for a Java user, but it is really convenient after you get used to it a bit.

No - getting used to a feature that is more limiting than whence I came is a recipe for driving me away.

The difference between class scope and package scope is significant. Name collisions almost never happen within class scope, but can be a problem in a package of any size. An example of static method in the Java API has a name that starts with “setDefault”. For example, “setDefaultPermissions”. When this method exists in a class, the context is obvious. When it exists at the package level (which might contain a hundred or more classes), the name is both confusing as to the context and could have name duplication problems with other classes in the package. This problem is particularly apparent with automatically generated classes.

2 Likes

Global functions are meant to be global. They have a lot of uses (as I already said, replacing language constructs with global functions is very good idea).

I can see the problem with static fields and methods in java - they are partially breaking the ideology, since static method is not really a member of the class, but in fact some global function that uses class as a namespace. From the ideological point of view kotlin way is more consistent. Class is no longer a namespace, but always a collection of fields and methods.

The abundance of global functions in a package could be a problem, and maybe kotlin team may do something to limit the scope of these functions, but maybe the solution is to use package system more intensively. In java it is in fact non functional, but in kotlin it starts to make sense.

2 Likes

…since static method is not really a member of the class…

I disagree.

Words matter. A “member” of a container is whatever the language designer defines it to be. In Java, static fields and methods are members of a class. Why the designers of Kotlin decided differently is incomprehensible to me given the high priority of Java interoperability as a design criterion.

1 Like

private package-level functions are private to their file AFAIK. So there’s no problem if you’re using file per class or file per set of related classes, you can use the same function name with different classes in different files in the same package.

vbezhenar - What you describe would not allow application code in a user created package to call such functions, as my previous example of “setDefaultPermission”.