Monday, March 27, 2023

OpenJDK 21 Compiler Warning on Constructor Calling Overridable Methods

THe OpenJDK 21 beta 15 early access build (released 23 March 2023) adds an -Xlint warning to the Java compiler to notify Java developers when a class's constructor calls an overridable method. Specifically, changes for JDK-8015831 ("Add lint check for calling overridable methods from a constructor") and JDK-6557145 ("Warn about calling abstract methods in constructors") are avalable in OpenJDK 21 beta 15. CSR JDK-8299995 ("Add lint check for calling overridable methods from a constructor") provides background details and justificatiion for these new warnings.

Two example classes are needed to demonstrate the new warnings with a parent class whose constructor calls overridable methods implemented by the extending class.

ParentWithAbstractMethod: Parent Class with abstract Method Called by Constructor

package dustin.examples.constructorcalls;

public abstract class ParentWithAbstractMethod
{
   /**
    * Constructor to be called by extending classes.
    */
   protected ParentWithAbstractMethod()
   {
      initializeCustomLogic();
   }

   /**
    * Initialize instance's custom logic.
    *
    * Because this {@code abstract} method is called by the constructor,
    * it will trigger the "this-escape" {@code -Xlint} warning.
    */
   protected abstract void initializeCustomLogic();
}

ConstructorCallsDemonstration: The Child/Extending Class

package dustin.examples.constructorcalls;

/**
 * Demonstrate warnings introduced with JDK 21 Early Access Update b15.
 */
public class ConstructorCallsDemonstration extends ParentWithAbstractMethod
{
   public ConstructorCallsDemonstration()
   {
       overridableInitializer();
       privateInitializer();
       finalInitializer();
       initializeCustomLogic();
   }

   /**
    * A child class CAN override this method called by constructor
    * and will trigger "this-escape" {@code -Xlint} warning.
    */
   protected void overridableInitializer()
   {
   }

   /**
    * A child class cannot override this method called by constructor
    * and will NOT trigger "this-escape" {@code -Xlint} warning.
    */
   private void privateInitializer()
   {
   }

   /**
    * A child class cannot override this method called by constructor
    * and will NOT trigger "this-escape" {@code -Xlint} warning.
    */
   protected final void finalInitializer()
   {
   }

   @Override
   protected void initializeCustomLogic()
   {
   }
}

When the above code is compiled with javac -Xlint, the new warnings are seen:

The command "javac -Xlint -sourcepath src -d classes src\dustin\examples\constructorcalls\*.java" generates this output:

src\dustin\examples\constructorcalls\ConstructorCallsDemonstration.java:10: warning: [this-escape] possible 'this' escape before subclass is fully initialized
       overridableInitializer();
                             ^
src\dustin\examples\constructorcalls\ParentWithAbstractMethod.java:10: warning: [this-escape] possible 'this' escape before subclass is fully initialized
      initializeCustomLogic();
                           ^
2 warnings

Executing the two code snippets shown above results in several observations:

  • An abstract method called from a constructor will lead to the new -Xlint this-escape warning.
  • A concrete method that is overridable (not private or final) and called from a constructor will lead to the new -Xlint this-escape warning.
  • A final method will not cause the -Xlint this-escape warning to appear because sub-classes cannot override a final method.
  • A private method will not cause the -Xlint this-escape warning to appear because sub-classes cannot override (or even "see") a private method.

The associated CSR points out that just the new -Xlint this-escape warning can be enabled when running javac by specifying -Xlint:this-escape and this warning can be suppressed by applying the annotation @SuppressWarnings("this-escape") to the code for which the new warning should be suppressed.