Your companion object implements a Java interface (e.g. companion object : Comparator<Foo>). How does Java pass that companion instance to APIs expecting the interface, and how does that differ from calling a @JvmStatic factory?
answer
- Companion is an object -> can implement interfaces
- Pass Foo.Companion where Comparator is expected
- @JvmStatic gives methods, not a passable instance
- Name the companion for a clean handle (Foo.Order)
- @JvmField val INSTANCE for an explicit handle
basics
~10 sJava grabs the singleton via Foo.Companion and passes it where the interface is expected, e.g. Collections.sort(list, Foo.Companion). A @JvmStatic factory is a static method you invoke (Foo.create()), not an instance you hand off.
solid answer
~40 sA companion object is a real object instance, so it can implement interfaces. When `companion object : Comparator<Foo>` exists, Java accesses the singleton through the `public static final Foo$Companion Companion` field and passes IT as the interface value: `Collections.sort(list, Foo.Companion)`. That's fundamentally different from `@JvmStatic` factory methods, which expose a static METHOD you call (`Foo.create()`) — there's no object to pass. The distinction matters for interop design: if Java needs an interface IMPLEMENTATION (Comparator, Runnable, a callback), expose the companion instance (often via the `Companion` field or a `@JvmField val INSTANCE`); if Java needs to invoke behavior, expose `@JvmStatic` methods. You can't put `@JvmStatic` on the whole companion to make it 'become' the interface — the instance is the carrier.
code
kotlin · 9 linesclass Foo(val rank: Int) {
companion object Order : Comparator<Foo> {
override fun compare(a: Foo, b: Foo) = a.rank - b.rank
@JvmStatic fun create(rank: Int) = Foo(rank)
}
}
// Java:
// Collections.sort(list, Foo.Order); // pass the instance
// Foo f = Foo.create(5); // invoke the static methodgo deeper
Recognizes the companion is an object and is reached via Foo.Companion.
Knows the companion can implement an interface and be passed as that interface value to Java.
Clearly separates 'pass an interface instance' from '@JvmStatic method invocation' and designs the surface accordingly.
Establishes interop patterns (named companion / @JvmField INSTANCE) so Java consumers get stable, discoverable interface handles.
## Companion as an interface implementation Because a `companion object` is an actual singleton object, it can extend classes and implement interfaces: ```kotlin class Foo(val rank: Int) { companion object : Comparator<Foo> { override fun compare(a: Foo, b: Foo): Int = a.rank - b.rank @JvmStatic fun create(rank: Int): Foo = Foo(rank) } } ``` ### Passing the instance to Java APIs The companion is reachable as the static `Companion` field, which IS a `Comparator<Foo>`: ```java List<Foo> list = ...; Collections.sort(list, Foo.Companion); // Companion implements Comparator ``` No `@JvmStatic` is needed for THIS — you're handing over the singleton object, not calling a static method. If you want a cleaner name, expose it: ```kotlin companion object Order : Comparator<Foo> { ... } // Foo.Order in Java ``` then `Collections.sort(list, Foo.Order)`. ## Contrast with @JvmStatic factories `@JvmStatic fun create(rank: Int)` produces a static METHOD: ```java Foo f = Foo.create(5); // invoking behavior ``` You cannot 'pass' `Foo.create` as an object (pre-method-reference semantics differ; Java method refs would be `Foo::create`, a functional-interface adaptation, not the companion instance). ## Decision guide for interop - **Need an interface value (Comparator, Runnable, callback)?** Expose the companion instance: via `Foo.Companion`, a named companion `Foo.Order`, or a `@JvmField val INSTANCE` for an explicit handle. - **Need to invoke a class-level operation?** Use `@JvmStatic` methods so Java writes `Foo.create()`. ## Subtleties - `@JvmStatic` cannot be applied to the companion object itself to 'flatten' the interface; the interface contract lives on the instance. - For SAM (single-abstract-method) Java interfaces, Java can also pass a lambda, but if you specifically want Foo's canonical implementation, the companion instance is the carrier. - Mixing both is common: a companion that both implements `Comparator` and exposes `@JvmStatic` factories. ## Key terms - **singleton instance**: the one object the companion represents; carries interface implementations. - **`Companion` field**: the static handle Java uses to reach that instance. - **`@JvmStatic`**: surfaces methods, not instances.
- Does the Comparator-passing case need @JvmStatic?No. You pass the companion instance via the Companion (or named) field; @JvmStatic only matters for surfacing methods as statics.
- How would you give Java a more explicit handle than 'Companion'?Name the companion (Foo.Order) or expose a @JvmField val INSTANCE pointing at the implementation.
The companion instance is a fully built tool you can hand someone (the Comparator); a @JvmStatic method is a button on the wall you press but can't hand over.
saying these in an interview costs you the question
- Saying you need @JvmStatic to pass the companion as a Comparator
- Claiming a companion can't implement interfaces
- Confusing passing the instance with calling a static method
- Trying to apply @JvmStatic to the whole companion object
- Assuming Foo.create can be passed as a Comparator