// ~/env/JAVA16_HOME/bin/javac FunnyStatic.java && ~/env/JAVA16_HOME/bin/java FunnyStatic // As of Java 16, the language allows statics inside of inner classes, // including anonymous ones in the middle of expressions. This allows // us to create two new kinds of variables at any point in a Java // program. // // (Whether we want to do so is debatable, but it could be a useful // way to express the semantics of language features which use condy // and indy.) // // Both kinds are encapsulated, effectively anonymous static // variables, akin to C++ block-local statics (which are initialized // when control flow first reaches them). // // Both kinds allow arbitrarily complex computations at "preflight" // time, which is just before the first time the variable is used. // (Yes, that's link time for statics, under a different name.) import java.util.*; import java.lang.invoke.*; class FunnyStatic { static String getBy(int x, String y) { return (( // The first kind of new variable is final. It // provides a "hook" to build a JIT-time constant at // any point in the program. A really smart compiler // might boil the whole thing down to a condy, by // turning the of the nested class into a // BSM, and having the BSM finish by picking out the // values desired. A multi-variable version would // have a shared BSM, but extract the different // values from different spots in a shared buffer. new Object() { static final String Z; static { System.out.println("link time in AC/HC; this could be a private preflight!"); var when = new Random().nextBoolean() ? "always" : "sometimes"; var help = MyFriends.A_LITTLE_HELP; //shared constant Z = String.format("I %s have %s for you.", when, help); } }.Z + // The second kind of new variable is mutable. It // provides a "hook" to maintain some bits of state // at any point in the program. This kind variable // seems less useful because of its unruliness. new Object() { static int ticks; //mutable local state! static String f(int x, String y) { var fmt = " We talked %d times before; now I see %s."; var xy = indyConcatDemo(x, y); return String.format(fmt, ticks++, xy); } }.f(x, y) )); } // You may have noticed that I get by with a little help from my friends. static class MyFriends { // This is an older relative of the above new variables. // It can also have arbitrary initialization code. // But it must be given an name and is shared, just // like a file-scope C++ static, or a global variable. static final String A_LITTLE_HELP; static { System.out.println("link time in API class; this could be a shared preflight!"); var cash = new Random().nextDouble() * 100; A_LITTLE_HELP = String.format("$%.2f.", cash); } } public static void main(String... av) { System.out.println(getBy(42, "forty-two")); System.out.println(getBy((int)1e99, "a bazillion")); condyRNGDemoTest(); } // Standalone demo of indy-based string concat (on int+", "+String). // The bootstrap logic transliterates directly into an anonymous // inner class with one static field, a method handle that holds // the result of spinning a string-concat instance. For grins // there is also some helper logic, but that could just as easily // go into the code surrounding the anonymous inner class. static String indyConcatDemo(int x, String y) { var s1 = x + ", " + y; /* -- javap output: InvokeDynamic makeConcatWithConstants:(int,String)String BSM: #60 REF_invokeStatic StringConcatFactory.makeConcatWithConstants :(MethodHandles$Lookup,String,MethodType,String,Object[])CallSite Method arguments: "\u0001, \u0001" -- */ var s2 = new Object() { static MethodHandle bsmLogic() throws StringConcatException { var lookup = MethodHandles.lookup(); var mt = MethodType.methodType(String.class, int.class, String.class); var fs = "\u0001, \u0001"; var cs = StringConcatFactory.makeConcatWithConstants(lookup, "", mt, fs); System.out.println("BSM for string concat returns "+cs+cs.type()); return cs.dynamicInvoker(); } static final MethodHandle MH; static { try { MH = bsmLogic(); // following catches are not required in bytecode } catch (RuntimeException|Error ex) { throw ex; } catch (Throwable ex) { throw new AssertionError(ex); } } static String concat(int x, String y) { try { return (String) MH.invokeExact(x, y); // following catches are not required in bytecode } catch (RuntimeException|Error ex) { throw ex; } catch (Throwable ex) { throw new AssertionError(ex); } } }.concat(x, y); if (!s1.equals(s2)) throw new AssertionError(); return s2; } // isolated demo of a workalike for condy static double condyRNGDemo() { // workalike for C++: """ static const double Z = drand48(); """ var Z = new Object() { static final double Z = new Random().nextDouble(); // mark it final so JIT can take it to the bank }.Z; return Z; } static void condyRNGDemoTest() { if (condyRNGDemo() != condyRNGDemo()) throw new AssertionError("not like condy"); } } /*-- link time in AC/HC; this could be a private preflight! link time in API class; this could be a shared preflight! BSM for string concat returns java.lang.invoke.ConstantCallSite@6bc7c054(int,String)String I sometimes have $65.96. for you. We talked 0 times before; now I see 42, forty-two. I sometimes have $65.96. for you. We talked 1 times before; now I see 2147483647, a bazillion. --*/