DEV Community

Adam Bennett
Adam Bennett

Posted on

Synthetic Accessors in Kotlin

So this is a pretty nerdy blog and should be a quick one, but I just wanted to share my findings.

As you may know, accessing private methods or fields in Java from nested or anonymous inner classes results in the creation of synthetic accessor methods. Jake Wharton’s talk on Java’s hidden costs is worth a watch here if you have no idea what I’m talking about.

These synthetic accessors are generated by the compiler and have big drawbacks on Android — potentially adding hundreds if not thousands of unnecessary methods across a project, each contributing to hitting the dex limit. Direct access to these methods or fields can also be up to 7 times faster than using a getter, which is nothing to sniff at.

Consequently at Blockchain we use the Thunk annotation to signal to developers that we’ve changed the scope of a field or method for the sake of avoiding synthetic accessor creation. There’s also a handy inspection built into Android Studio to highlight cases that you might have missed:

I highly recommend enabling this particular inspection.

This is pretty well known. But is this an issue in the new hotness, Kotlin?

In short, yes.

Ultimately this shouldn’t be too surprising: Kotlin compiles down to the equivalent Java bytecode, so this is indeed a problem here too. I figured it would be but I wanted to check, so here’s what I found:

class SyntheticAccessorTest {

    private var counter = 0

    fun main(args: Array<String>) {

        val someClass = SomeClass(object : SomeInterface {
            override fun doSomething() {
                printSomething()
            }

            override fun doSomethingElse() {
                counter++
            }
        })
    }

    private fun printSomething() {
        print("Something")
    }
}

class SomeClass(var listener: SomeInterface)

interface SomeInterface {

    fun doSomething()

    fun doSomethingElse()

}
Enter fullscreen mode Exit fullscreen mode

Here we have a simple test class which invokes the constructor of another simple class and takes an anonymous implementation of an interface. In one of the callbacks, we invoke a function which is private, in another we increment a counter which is also private. Unfortunately because of this, the resulting .class file will absolutely have synthetic accessor methods, and once we look into it, it’s easy to see why.

In Android Studio, if we select Kotlin Bytecode and then Decompile, we get this Java (I’ve simplified this a bit for readability):

public final class SyntheticAccessorTest {
   private int counter;

   public final void main(@NotNull String[] args) {
      new SomeClass(new SomeInterface() {
         public void doSomething() {
            SyntheticAccessorTest.this.printSomething();
         }

         public void doSomethingElse() {
            int var1;
            SyntheticAccessorTest.this.counter = (var1 = SyntheticAccessorTest.this.counter) + 1;
         }
      });
   }

   private final void printSomething() {
      String var1 = "Something";
      System.out.print(var1);
   }
}
Enter fullscreen mode Exit fullscreen mode

If we then decompile the file with javap -p -c SyntheticAccessorTest.class, we then get this:

public final class SyntheticAccessorTest {
  private int counter;
public final void main(java.lang.String[]);
    Code:
       [...]
private final void printSomething();
    Code:
       [...]
public SyntheticAccessorTest();
    Code:
       [...]
public static final void access$printSomething(SyntheticAccessorTest);
    Code:
       [...]
public static final int access$getCounter$p(SyntheticAccessorTest);
    Code:
       [...]
public static final void access$setCounter$p(SyntheticAccessorTest, int);
    Code:
       [...]
}
Enter fullscreen mode Exit fullscreen mode

And there they are: it’s these access$ methods that are the problem. In fact we get two for the counter field; one for set, one for get. You can easily see how this starts to add up quickly in an Android project.

If we change both counter and printSomething() to internal, we get this instead:

public final class SyntheticAccessorTest {
  private int counter;
public final int getCounter$Test_Project();
    Code:
       [...]
public final void setCounter$Test_Project(int);
    Code:
       [...]
public final void main(java.lang.String[]);
    Code:
       [...]
public final void printSomething$Test_Project();
    Code:
       [...]
public SyntheticAccessorTest();
    Code:
       [...]
}
Enter fullscreen mode Exit fullscreen mode

Better. But what is interesting here is that whilst we lose the synthetic accessor for printSomething(), we still get two methods for setting and getting the counter property. This is due to Kotlin automagically adding getters and setters to properties, and indeed if we check the generated Java, we see:

public final int getCounter$production_sources_for_module_Test_Project() {
   return this.counter;
}

public final void setCounter$production_sources_for_module_Test_Project(int var1) {
   this.counter = var1;
}
Enter fullscreen mode Exit fullscreen mode

Whether or not this seems unnecessary is up to you. In most Java classes you’d likely have written setters and getters anyway, so you haven’t really gained any unneeded methods in many situations. However in this case as the property is being accessed inside the parent class: these are obviously superfluous. Applying the same inspection to the equivalent Java code results in just 3 methods; two fewer than Kotlin.

If this is a huge concern for you there’s a simple fix; add the @JvmField annotation to the property and the getter/setter won’t be generated (thanks to Kirill Rakhman for the heads up).

So yes, you do need to be careful about accessing private functions in Kotlin for the same reason as you do in Java, and there appears to be a hidden cost in simply using properties. Sadly there isn’t yet an inspection for this yet, but I’m hoping that IntelliJ/Google will add this fairly soon. In the meantime, check your code carefully if the dex method count is something you worry about.

Top comments (0)