Nate Ebel
2 min readNov 17, 2017

--

I’ll start by clarifying that we don’t avoid CompanionObjects at all cost, we’ve just found a few scenarios where we’ve been able to remove them. Additionally, there are still use cases where they are very applicable.

In our Java code, we would commonly have things like the following:

private static final String SOME_VALUE = "foo"class LoggingHelper {
public static void logTheThing() {...}
}
public class Foo {

public static void createFoo() {}

private String id;
public Foo(String id) { this.id = id; }
}

In the first case, this would likely be converted into a val on a CompanionOjbect of the containing class. In many cases this is the only reason a companion exists. We’ve felt this is using a companion unnecessarily because we can declare the value as a private const val within the file. We’ve found this to be more readable and more immediately obvious.

In the case of LoggingHelper this would actually be converted into an object which may, or may not, be agreeable for your project. We’ve had a number of cases like this where we chose to use extension functions or top-level functions instead of keeping an object class around. In the case of top-level functions, it removes the need to make a scoped call (from Kotlin) which we find syntactically more appealing.

In the last case, again the conversion tool will create a CompanionOjbect. From Kotlin, we can use the method as Foo.createFoo() like we are familiar from Java. From Java however, the syntax is more cumbersone: Foo.Companion.createFoo().

If we can write a top-level function to handle the same functionality then we can access it from Java as FooKt.createFoo(). This looks a little more pleasant to us. It’s also not something we have to deal with too often as we move more towards Kotlin.

There are also some hidden costs associated with CompanionObjects if things aren’t declared appropriately. I think this has played into our convention of avoiding companions when we can.

One thing I do want to point out is that while we’ve been able to use top-level functions pretty freely (and thereby avoid using CompanionObject is some places) this approach doesn’t necessarily work for all projects.

With top-level functions, you run the risk of polluting the global namespace, whereas with CompanionObjects you can leverage class scoping. When working on a large team, a library, or framework this scoping is likely much more important.

--

--

Nate Ebel
Nate Ebel

Written by Nate Ebel

Building great software and helping others do the same.

No responses yet