elect
elect

Reputation: 7190

Kotlin/Java, string array not yet initialized at method call

In one program of mine I got a null exception and debugging the code I found out it was because one of my String array (java):

val FRAGMENTS = arrayOf("phong-lighting", "phong-only", "blinn-lighting", "blinn-only")

was not yet initialized when I needed it in a successive class method (java):

fun initializePrograms(gl: GL3) {

    programs = Array(LightingModel.MAX, {
        ProgramPairs(
                ProgramData(gl, "pn.vert", FRAGMENTS[it] + ".frag"),
                ProgramData(gl, "pcn.vert", FRAGMENTS[it] + ".frag"))
    })
    unlit = UnlitProgData(gl, "pos-transform.vert", "uniform-color.frag")
}

If I move it inside:

fun initializePrograms(gl: GL3) {
    val FRAGMENTS = arrayOf("phong-lighting", "phong-only", "blinn-lighting", "blinn-only")
    ...
}

Then everything fine.. why?

Ps: same code and same behavious also in java

Edit: don't get confused, that init is not the class init, it extends the Framework method here

Edit2: initializePrograms() doesn't get called from the super constructor. It's called from init(GL3 gl) which override Framework.init(GL3 gl) which gets called from Framework.init(GLAutoDrawable autoDrawable), which shall be called from the Animator just once at begin which should be another thread.

Upvotes: 1

Views: 133

Answers (1)

Maciej Ciemięga
Maciej Ciemięga

Reputation: 10713

Reason

If method initializePrograms is invoked in the superclass constructor, the crash is an expected behavior. Properties of the class are initialized after object is constructed, so they are uninitialized until then.

In that case, because the arrayOf function is an inline function, it is clearly visible on decompiled class:

public final class Test {
   @NotNull
   private final String[] FRAGMENTS;
   ...

   public Test() {
      super();
      String[] elements$iv = new String[]{"phong-lighting", "phong-only", "blinn-lighting", "blinn-only"};
      Object[] var4 = (Object[])elements$iv;
      this.FRAGMENTS = (String[])var4;
   }
}

notice the constructor of Test class and how properties are initialized after call to super().

Note that if you would use not-inline function to initialize that property (for example listOf) the initialization will be joined with declaration, but that is effectively the same thing, just not as explicit. In Java this field would also be initialized after constructor has finished.

public final class Test {
   @NotNull
   private final List FRAGMENTS = CollectionsKt.listOf(new String[]{"phong-lighting", "phong-only", "blinn-lighting", "blinn-only"});
   ...
}

Solution

Most likely what you're trying to achieve (considering the all caps FRAGMENTS) is to declare then as a static field. In Kotlin you can do that for example by nesting it in a companion object in particular class.

class Test {
    ...
    companion object {
        val FRAGMENTS = arrayOf("phong-lighting", "phong-only", "blinn-lighting", "blinn-only")
    }
}

then, as you can see on decompiled class, it is declared as a static field and initialized in static block:

public final class Test {
   @NotNull
   private static final String[] FRAGMENTS;
   ...

   static {
      String[] elements$iv = new String[]{"phong-lighting", "phong-only", "blinn-lighting", "blinn-only"};
      FRAGMENTS = (String[])((Object[])elements$iv);
   }
}

Upvotes: 1

Related Questions