@@ -58,9 +58,24 @@ public AClassWithoutADefaultConstructor(final int i) {
5858 }
5959 }
6060
61+ // Marker type a caller might validate a resolved Class against before touching it.
62+ public interface Plugin {
63+ }
64+
65+ // A Plugin whose static initializer has an observable side effect.
66+ public static class PluginImpl implements Plugin {
67+
68+ static {
69+ pluginInitializerRan = true ;
70+ }
71+ }
72+
6173 // Set by the static initializer of AClassWithAStaticInitializer; readable without initializing that class.
6274 private static boolean classInitializerRan ;
6375
76+ // Set by the static initializer of PluginImpl; readable without initializing that class.
77+ private static boolean pluginInitializerRan ;
78+
6479 private static Stream <Arguments > numberTestParameters () {
6580 final List <Arguments > lst = new ArrayList <>();
6681 lst .add (Arguments .of ("123" , Long .valueOf ("123" )));
@@ -94,6 +109,18 @@ void testClassDoesNotInitialize() throws Exception {
94109 assertTrue (classInitializerRan );
95110 }
96111
112+ @ Test
113+ void testClassValidatedBeforeInitialization () throws Exception {
114+ // The caller pattern this enables: resolve the option, gate it with isAssignableFrom, then instantiate.
115+ // The assignability check must not run the class's static initializer; only newInstance() should.
116+ final Class <?> cls = Converter .CLASS .apply (PluginImpl .class .getName ());
117+ assertTrue (Plugin .class .isAssignableFrom (cls ));
118+ assertFalse (pluginInitializerRan );
119+ final Object instance = cls .getConstructor ().newInstance ();
120+ assertTrue (instance instanceof Plugin );
121+ assertTrue (pluginInitializerRan );
122+ }
123+
97124 @ Test
98125 void testDate () throws Exception {
99126 assertThrows (java .text .ParseException .class , () -> Converter .DATE .apply ("whatever" ));
0 commit comments