Search code examples
kotlinklaxon

some public functions apparently not accessible on class instance; Klaxon example


I'm having trouble understanding why some public functions are apparently not available, although at least one other function is. The example I have is from Klaxon, but I suspect there's some mismatch between what I'm expecting and how Kotlin actually works, and it's not specific to Klaxon.

I am working with the command line interpreter on a Ubuntu 16.04 system. $j is the path to a jar file containing Klaxon and a bunch of other stuff. As you can see, I am working with kotlin 1.9.20. The file foo.json contains just { "foo" : 1234, "bar": null, "baz": [11, 22, 33, 44] }.

$ kotlin -cp $j
Welcome to Kotlin version 1.9.20 (JRE 17.0.8+9-LTS-211)
Type :help for help, :quit for quit
>>> val x = com.beust.klaxon.Parser.default ().parse ("foo.json")
>>> val y = x as com.beust.klaxon.JsonObject
>>> y.get ("baz")
res2: kotlin.Any = JsonArray(value=[11, 22, 33, 44])

So far, so good; that's just what I was hoping to see. Now try some other getSomething functions.

>>> y.getKeys ()
error: unresolved reference: getKeys
y.getKeys ()
  ^
>>> y.getSize ()
error: unresolved reference: getSize
y.getSize ()
  ^
>>> y.getMap ()
error: unresolved reference: getMap
y.getMap ()
  ^

I don' understand -- I have an object of the expected class,

>>> y::class
res6: kotlin.reflect.KClass<out com.beust.klaxon.JsonObject> = class com.beust.klaxon.JsonObject (Kotlin reflection is not available)
>>> 

and the methods I named are all present in the class from what I can tell:

$ javap -cp $j com.beust.klaxon.JsonObject | grep get
  public final java.util.Map<java.lang.String, java.lang.Object> getMap();
  public java.util.Set<java.util.Map$Entry<java.lang.String, java.lang.Object>> getEntries();
  public java.util.Set<java.lang.String> getKeys();
  public int getSize();
  public java.util.Collection<java.lang.Object> getValues();
  public java.lang.Object get(java.lang.String);
  public final java.lang.Object get(java.lang.Object);

Can someone explain what I'm seeing here? How should I call getKeys, getMap, etc.?


Solution

  • Java getters and setters are accessed as properties in Kotlin

    When you introspect the shape of your class using javap you are looking at the Java representation of the class. Thus we need to think about the interaction of Kotlin and Java.

    Kotlin is fully interoperable with Java, but sometimes you call a method or access a property in a different way in Kotlin.

    In particular, methods that follow the Java getter and setter conventions (ie those that start with get and set) are accessed as properties in Kotlin (documentation). This is the case for the getKeys(), getSize() and getMap() methods you see in your javap analysis.

    So if you try y.keys, y.size or y.map in Kotlin you should get the results you expect.

    Note the original source was Kotlin

    In fact, there is a further step going on here. I note that the class com.beust.klaxon.JsonObject is written in Kotlin (see GitHub). You can see in the source it has a map property, and it inherits keys and size from the MutableMap interface that it implements. This source has been compiled to JVM bytecode by representing those Kotlin properties as Java getters and setters, following another published rule.

    This is the reverse of the rule noted above used to call Java code from Kotlin, which we would expect; otherwise there would be unexpected results when using compiled Kotlin libraries from Kotlin (as you are).