6

I have a class holding a large a mount of generated constants as such:

public class Constants extends SomeBaseClass {

  // init() is defined in some base class...
  public static final XXX KEY1 = init(...);
  public static final XXX KEY2 = init(...);
  public static final XXX KEY3 = init(...);

  // ...
  public static final XXX KEY2000 = init(...);
}

When the number of generated constants is very high, this results in a static initialiser that is larger than the upper limit for Java method sizes (i.e. > 64kb), resulting in a compiler error. One solution is to create several "block initialisation methods" for blocks that can be guaranteed to produce less than 64kb of byte-code, such that they fit into a method:

public class Constants extends SomeBaseClass {

  public static XXX KEY1;
  public static XXX KEY2;
  public static XXX KEY3;

  // ...
  public static XXX KEY2000;

  static {
    initialise0001To1000();
    initialise1001To2000();
  }

  private static void initialise0001To1000() {
    KEY1 = init(...);
    KEY2 = init(...);
    KEY3 = init(...);
    // ...
  }

  private static void initialise1001To2000() {
    // ...
    KEY2000 = init(...);
  }
}

The drawback of this is that I can no longer declare the constants as final, because they are now no longer initialised directly in the static initialiser.

My question is, how can I circumvent that compiler / JVM limitation in a way that I can still generate static final constants?

Lukas Eder
  • 211,314
  • 129
  • 689
  • 1,509
  • How did you end up running into this problem? Is this code autogenerated from another file? – templatetypedef May 31 '12 at 21:54
  • @templatetypedef: This is an actual bug in the source-code generator of [jOOQ](http://www.jooq.org). It generates primary keys, unique keys and foreign keys as constant objects from a database. It seems that 2000 keys is too much for jOOQ to handle: https://groups.google.com/d/topic/jooq-user/2g96fI1Yrj8/discussion – Lukas Eder May 31 '12 at 21:57
  • Could you use "dummy" inheritance layers for this? Have a base class with some non-public-use name which contains 1,000 constants and has a static initializer set them up. Then a derived class which adds 1,000 more, a subderived class which adds another 1,000, etc.? Only the most derived class would ever be used for any purpose except for the derivation of the other classes in the assembly. – supercat Jun 13 '14 at 18:38
  • @supercat: I think you can. The limitation is really imposed by the various 16bit (two-word) header fields in the class headers. So, I'm guessing this could be circumvented by inheritance... You'll have to verify that in bytecode, though (e.g. by using [`javap`](http://docs.oracle.com/javase/8/docs/technotes/tools/windows/javap.html)) – Lukas Eder Jun 13 '14 at 22:10

4 Answers4

7

One option would be to use inheritance - have a series of classes Constants1, Constants2, ..., ConstantsN that all define the constants, then have each one inherit from the previous one. The final class Constants can then directly inherit from the last of them. This also lets you mark everything final.

Out of curiosity, how did you end up with a file so large that you couldn't fit the initialization code into the 64KB limit?

Hope this helps!

templatetypedef
  • 362,284
  • 104
  • 897
  • 1,065
  • Hmm, yes that would be a valid workaround, nice thinking. I could also place all constants in interfaces, and let `Constants` implement all of them... – Lukas Eder May 31 '12 at 21:50
  • 1
    You might be able to do this with nested classes as well, which would allow you to put everything in a single source file. – David R Tribble May 31 '12 at 22:40
  • @Loadmaster: That's even better! Those nested classes could be private, the top-level class could then reference the constants from the nested classes in order to expose them, which would probably lead to a much smaller initialiser in the top level class - maybe allowing for around 200k constants. I think this comment deserves its own answer – Lukas Eder Jun 01 '12 at 07:56
  • @Loadmaster: I have documented your idea in a [separate, newly accepted answer](http://stackoverflow.com/questions/10841732/how-to-circumvent-the-size-limit-of-a-static-initialiser-in-java-when-initialisi/10869901#10869901). While this answer here is more general (i.e. supports even more constants), I prefer your idea, as I can hide these workaround implementation details from the outside world – Lukas Eder Jun 03 '12 at 11:25
1

I finally went for a solution involving nested classes. This was suggested in a comment to this answer here by user Loadmaster. Nested classes have two advantages:

  • They allow for hiding these workaround implementation details from the outside world by being private nested classes
  • They allow for keeping constants final

But they also have a disadvantage compared to templatetypedef's solution:

  • I will run into the same problem again with much larger numbers of constants

Right now, however, this seems to be the most suitable solution:

public class Constants {

  public static XXX KEY1    = Constants1.KEY1;
  public static XXX KEY2    = Constants1.KEY2;
  public static XXX KEY3    = Constants1.KEY3;

  // ...
  public static XXX KEY2000 = Constants2.KEY2000;

  // Nested class holding 1000 constants
  private static class Constants1 extends SomeBaseClass {
    KEY1 = init(...);
    KEY2 = init(...);
    KEY3 = init(...);
    // ...
  }

  // Nested class holding the next 1000 constants
  private static class Constants2 extends SomeBaseClass {
    // ...
    KEY2000 = init(...);
  }

  // Keep generating nested classes for more constants...
  private static class Constants3 ... {}
}
Community
  • 1
  • 1
Lukas Eder
  • 211,314
  • 129
  • 689
  • 1,509
0

Wouldn't this work? NO. See comments for why this kind of answer will not solve the problem.

It would allow you to keep the static variables final and it would be easier to autogenerate. However java collapses all the static init blocks into one giant static init block and thus the problem is not solved.

public class Constants extends SomeBaseClass {

  // init() is defined in some base class...
  public static final XXX KEY1 ;
  static
  {
       KEY1 = init(...);
  }
  public static final XXX KEY2 ;
  static
  {
       KEY2 = init(...);
  }
  public static final XXX KEY3 ;
  static
  {
       KEY3 = init(...);
  }

  // ...

}
emory
  • 10,725
  • 2
  • 30
  • 58
  • There had already been an answer like that (in the mean time, deleted). It seems that a byte-code class only has one static initialiser. The compiler combines all initialisation statements to a single one, I think – Lukas Eder Jun 01 '12 at 07:53
  • That is a shame. Would the same thing happen if you used an enum? Disadvantages XXX has to be the same class and you would have to rework the code so that init() is a call to the enum constructor. – emory Jun 01 '12 at 16:45
  • I cannot use an enum. The real world type `XXX` has generic type parameters... :-/ But I'd imagine that under the hood, enums share the same static initialiser logic with regular classes... – Lukas Eder Jun 01 '12 at 16:58
  • 1
    Yes. I did some review. The same is true for enums. This strategy would also not work if you used instance instead of static variables. I will leave this answer instead of deleting it (even though it is wrong) b/c it may be useful to others to know this approach will not work. – emory Jun 01 '12 at 18:22
  • I agree with leaving this answer. Along with the comments, it's also valuable – Lukas Eder Jun 01 '12 at 19:36
-2

You can have as many static initialization blocks as you want.

Stripies
  • 1,267
  • 5
  • 19
  • 29