2

This is a question about language design rather than trying to solve a specific problem.

I noted two oddities about object instances inside companion objects:

  • object instances can't be referenced without the .Companion
  • object instances cannot be annotated @JvmStatic

As shown below, neither constraint applies to functions defined within companion objects. The distinction for apparently for what appear to be sibling declarations is a bit of mental overhead I'd rather not have to deal with.

Is there an example that demonstrates why these constraints were necessary? Presumably for Java interop, but I have yet to think of the case.

class MyClass {
    companion object {
        @JvmStatic
        fun myFunc() {}
       
        //@JvmStatic // Error: This annotation is not applicable to target 'standalone object'
        object MyObject {}
    }
}

fun testingReferencing() {
    MyClass.myFunc()
    MyClass.Companion.myFunc()  // Awkward and redundant, but works.
    
    //val obj1 = MyClass.MyObject  // Error: Unresolved reference: MyObject
    val obj2 = MyClass.Companion.MyObject
}

https://pl.kotl.in/LJQLiBe6m

Anm
  • 3,291
  • 2
  • 29
  • 40
  • The difference is that `object`s are more like classes than like fields. In this case, simply add a `@JvmStatic val myObject = MyObject` in the `companion`, and you'll be able to do what you want. – Jorn May 04 '23 at 14:47

2 Answers2

3

object instances can't be referenced without the .Companion

This "restriction" is certainly not "necessary", but enabling you to access object declarations this way would probably lead to more complexity in the compiler, to not only implement this, but also handle the edge cases, and various other features that might interact with the change.

And anyway, this is how type names work all along. The fact that you can access properties and methods without Companion is the exception.

MyClass.MyObject refers to an object/type called MyObject, nested in MyClass. And MyClass.Companion.MyObject refers to an object/type called MyObject nested in a type called Companion, which is itself nested in a type called MyClass.

This is just the way nested type names (whether they are objects, classes, or interfaces) are qualified.

object instances cannot be annotated @JvmStatic

JvmStatic is just unnecessary on type declarations. If you look at the annotation's declaration, it is only available on functions and properties.

An object (or class, or enum class) declaration nested in another type already translates to a static nested class in Java. e.g.

class Foo {
    object Bar
}

translates to something like

public class Foo {
    public static class Bar {
        public static final INSTANCE = new Bar();
        private Bar() {}
    }
}

Whether or not it is in a companion object doesn't matter. companion object, just like class, translates to a Java class, where the class for the object is nested.

If you want a non-static JVM class, that is an inner class in Kotlin.

Sweeper
  • 213,210
  • 22
  • 193
  • 313
  • It never occurred to me that there is a Companion class for the companion object. – Anm May 31 '23 at 12:21
0

Such a feature would need good justification for any drawbacks since it is kind of a rare situation. (I have never had good reason to define public static methods in a nested Java class before, nor have I ever seen any in any popular public libraries.) And it does have several drawbacks.

To make it work, @JvmStatic applied to an object nested in a companion object would have to produce in Java a static member named MyObject in the parent class that is initialized with Companion.MyObject.INSTANCE.

Doing this would open up some ambiguities:

  • In Java, MyClass.MyObject would be a static field that violates naming convention rather than a class name used to access static functions. This is confusing. Also, through it you can access non-static methods like toString() and hashcode(), which is also sort of confusing.
  • At first glance, you might think applying @JvmStatic to this nested object has similar meaning to static class in Java because of the overload of the term "static". But it has completely different meaning.
  • Or, you might expect @JvmStatic to have a similar behavior regardless of its target. So, since applying it to a function gives the function a static alias in the parent class, you might expect that applying it to a whole object simply applies that behavior to all of its contained functions. (This is not actually possible because an object has member functions that cannot be static due to inheritance.)

Also, the implementation in the compiler might have to be somewhat complicated to guarantee thread-safe singleton behavior for this extra field.

Tenfour04
  • 83,111
  • 11
  • 94
  • 154