Hi here, what is the use case for the companion ob...
# getting-started
g
Hi here, what is the use case for the companion objects? Coming from the java world, the normal way (to me ) would be to instantiate a singleton (using a container) and then provide it as a dependency to the caller code. Companion objects seem to work as a hardcoded dependencies, essentially not different from the static method calls.
c
Yes, the analog to a Companion Object in the Java world is static functions/properties. One of the main advantages of companion objects over statics, is that they can be passed around like a normal object, and they can implement interfaces like a class. You can see this feature used a lot of #ktor, where the companion object acts as a “key” for using a particular feature in your HTTP server/client
g
@Casey Brooks right. But I am just trying to understand where to use and where not to use them (I would even put this broader: for all object declarations). To be more precise: I have a data class that is used in some places, some of which want to validate it in a certain way. How I would normally do it, I would create a few validators and pass them as a dependency to the caller, so that caller could use them to validate the data object. What I can also do, is declare a companion object to my data class and then use it for validations. But then I guess I am a) effectively doing what I would do by using a static method in java b) introduce “hidden” uncontrollable and unmockable dependencies.
c
Generally, the choice to use a companion object, a top-level object, top-level function, etc. is just a stylistic choice. They will all pretty much work the same way, which ultimately is generally the same way you’d think about using static functions in Java. You generally wouldn’t think of a companion object as a “dependency” (as in, dependency injection). It’s always a singleton object, you cannot instantiate it or swap out its implementation, so it’s generally not suited for that type of behavior. If you need a validator class that’s associated to a data class, consider making it a nested class instead, which can be instantiated and helps separate the two concepts. For example, this snippet would be a more appropriate way to describe a validator as a dependency of a data class:
Copy code
interface ObjectValidator<T> { 
    fun isValid(instance: T): Boolean
}

data class Person(val name: String) {
    class Validator : ObjectValidator<Person> { 
        override fun isValid(instance: Person) = return instance.name.isNotBlank()
    }
}

fun main() {
    val person = Person("John Doe")
    val personValidator = Person.Validator()
    
    val isValid = personValidator.isValid(person)
}
Compare this to using the companion object to implement
ObjectValidator
, which is much more confusing to read when it’s used
Copy code
fun main() {
    val person = Person("John Doe")
    val personValidator = Person
    
    val isValid = personValidator.isValid(person)
}
And note that even if we use the companion object to implement
ObjectValidator
, it doesn’t automatically associate itself to the
Person
class. There’s no real relation between a companion object and the class it’s with, other than syntax (and reflection). A class kinda knows about its companion object, but a companion object doesn’t really know anything about the class it’s a companion to
Copy code
data class Person(val name: String) {
    companion object : ObjectValidator<Person> { // still needs the <Person> type parameter
        override fun isValid(instance: Person) = instance.name.isNotBlank()
    }
}
❤️ 1
g
@Casey Brooks talking about nested classes, they are not anyhow attached to the enclosing class either, right?
c
Correct. A regular nested class in Kotlin is akin to a Java static inner class. You’d have to declare an
inner class
in Kotlin for it to work like a normal (non-static) Java inner class
g
@Casey Brooks so, basically, for my understanding, companion objects are to be used wherever I would normally use static code in java (never)
c
Basically, yes, use companion objects when you would normally have used static functions in Java. I probably wouldn’t go as far as saying “never use them”, but many use-cases are better served by things like top-level functions or moving it to a fully separate class/object instead.
g
@Casey Brooks I normally don’t write any statics in my java code (except for constants) (as they are untestable). Should I then refrain from using top-level functions and singletons(objects) altogether?
c
With that general mindset, yes. Companion objects, normal (singleton) objects, and top-level functions serve similar purposes, and are mainly just different syntax for what would otherwise have been Java static functions. I will note though, that the general Kotlin community is much more favorable top-level functions and things like that, than Java is to static functions. There’s not necessarily one right or wrong way to do it, so just use whatever style you’re most comfortable with, and what will provide you the best way for managing the complexity of your codebase.
g
@Casey Brooks so with kotlin community mindset, what would be the valid use-case for top-level functions or singleton objects?
c
Things like utility functions, especially extension functions. Generally-speaking, Kotlin prefers to keep classes small and move a lot of logic that normally would have been private functions in Java, into top-level-type-functions. This keeps the functions within a class specific to those that actually change its internal, private members, and anything else is moved into those utility functions as a way of saying “here’s some additional things you can do with this class”. Also, function references and lambdas can accomplish a lot of what you might have used dedicated interfaces for in Java. Hopefully I’m not just confusing you even more with all this 😅 For starting out, I’d first work on just getting comfortable with the language itself, learning how the various Kotlin concepts map to your expectations from Java. As you use it more, the places where Kotlin clearly deviates from just “a better Java” will start to become more apparent, but you don’t need to necessarily worry yourself with all that right at the beginning. The more you use Kotlin, the more you see how libraries are designed, the more you’ll notice these things for yourself. But ultimately, like I said earlier, there’s no absolute right or wrong way to use Kotlin. It’s better to just write the code that’s the easiest for you to read and maintain, rather than worrying too much about whether you’re doing things “the right way”
💯 1
❤️ 1
g
@Casey Brooks thanks. so going back to the validator example. In kotlin “mindset” it would probably be a good candidate for the top level functions or singleton object
c
I’d probably put it as a top-level extension function
t
another side note: you mention not using much static code in Java because of issues with testing/mocking. Mocking static code isn’t really a problem anymore nowadays and you can easily mock (companion) objects and top level functions in Kotlin (and also in Java, e.g. with Mockito since version 3.4.0). And in general, if you’re mocking in Kotlin tests, I personally suggest Mockk over Mockito.
g
@Tobias Berger whatever problems they may (or may not) be with mocking, what I don’t like more, is that using static methods creates an implicit unobvious dependencies (on that static code). So I am not ready yet to feel comfortable with it. Normally, I explicitly define dependencies in constructors, how small they also may be. I wonder what is the kotlin community standpoint on that
but thanks a lot for your suggestions
144 Views