Search code examples
javaandroidkotlinjvmdecompiling

Java: object construction/initialization can be deferred?


I'm working on some android project where I'm mixing Java and Kotlin. I have a piece of Kotlin code that I decompiled in Java to see how it is actually converted.

Kotlin code

fun postSettingToServer() {
  val request = CoockieJsonRequest(Request.Method.POST, URLBuilder.GetPushSettings(this), pushModel!!.toJSON(), null, null)
  VolleySingleton.getInstance(applicationContext).addToRequestQueue(request)
}

Android studio created Java equivalent

public final void postSettingToServer() {
    CoockieJsonRequest var10000 = new CoockieJsonRequest;
    String var10003 = URLBuilder.GetPushSettings((Context)this);
    Intrinsics.checkExpressionValueIsNotNull(var10003, "URLBuilder.GetPushSettings(this)");
    PushSettings var10004 = this.pushModel;
    if (var10004 == null) {
      Intrinsics.throwNpe();
    }

    var10000.<init>(1, var10003, var10004.toJSON(), (Listener)null, (ErrorListener)null);
    CoockieJsonRequest request = var10000;
    VolleySingleton.getInstance(this.getApplicationContext()).addToRequestQueue((Request)request);
}

What bothers me is this CoockieJsonRequest var10000 = new CoockieJsonRequest;. So, basically, here we can see that the code allocated the memory to CoockieJsonRequest with new operator, but don't call it constructor(no braces). Instead of this, the code does some other operations(unwrapping pushModel object) and only then initializes CoockieJsonRequest using JVM <init>. This looks really weird to me since I always thought that the object has to be constructed when it is allocated.

So, my questions - it is how it works (construction can be deferred) or something is wrong with Android Studion Kotlin decompiler and it just creates weird decompile outputs?


Solution

  • TL;DR: It's nearly a normal instance construction, but the conditionals confuse the decompiler.

    At bytecode level, it's two different instructions. new creates an uninitialized instance of the given class. invokespecial, referencing the <init> method, initializes the instance using the given constructor. By no means is it obligatory that they immediately follow one another.

    The code you're seeing roughly matches the following java code (after inlining the synthetic variables):

    CoockieJsonRequest request = new CoockieJsonRequest(1, 
        URLBuilder.GetPushSettings((Context)this), 
        this.pushModel.toJSON(), 
        (Listener) null, 
        (ErrorListener) null);
    

    It seems to be quite normal that Java compilers

    • first create the instance with the new instruction,
    • then prepare the arguments for the constructor,
    • and finally call the constructor <init> method using the invokespecial instruction.

    So, with a Java compiler, the Java instance creation from above will probably produce a very similar bytecode sequence, hence a similar decompilation.

    Only the Intrinsics null checks make a difference in the bytecode, and probably confuse the decompiler enough so that it couldn't inline the argument expressions into a "normal" constructor invocation.

    E.g. when trying to inline the var10004 expression, the if construct could at best be replaced with a ternary operator, making the resulting Java code at least complicated, if not impossible. So, it's quite plausible that the decompiler fails here.