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

  1. package dustin.examples.constructorcalls;  
  2.   
  3. public abstract class ParentWithAbstractMethod  
  4. {  
  5.    /** 
  6.     * Constructor to be called by extending classes. 
  7.     */  
  8.    protected ParentWithAbstractMethod()  
  9.    {  
  10.       initializeCustomLogic();  
  11.    }  
  12.   
  13.    /** 
  14.     * Initialize instance's custom logic. 
  15.     * 
  16.     * Because this {@code abstract} method is called by the constructor, 
  17.     * it will trigger the "this-escape" {@code -Xlint} warning. 
  18.     */  
  19.    protected abstract void initializeCustomLogic();  
  20. }  

ConstructorCallsDemonstration: The Child/Extending Class

  1. package dustin.examples.constructorcalls;  
  2.   
  3. /** 
  4.  * Demonstrate warnings introduced with JDK 21 Early Access Update b15. 
  5.  */  
  6. public class ConstructorCallsDemonstration extends ParentWithAbstractMethod  
  7. {  
  8.    public ConstructorCallsDemonstration()  
  9.    {  
  10.        overridableInitializer();  
  11.        privateInitializer();  
  12.        finalInitializer();  
  13.        initializeCustomLogic();  
  14.    }  
  15.   
  16.    /** 
  17.     * A child class CAN override this method called by constructor 
  18.     * and will trigger "this-escape" {@code -Xlint} warning. 
  19.     */  
  20.    protected void overridableInitializer()  
  21.    {  
  22.    }  
  23.   
  24.    /** 
  25.     * A child class cannot override this method called by constructor 
  26.     * and will NOT trigger "this-escape" {@code -Xlint} warning. 
  27.     */  
  28.    private void privateInitializer()  
  29.    {  
  30.    }  
  31.   
  32.    /** 
  33.     * A child class cannot override this method called by constructor 
  34.     * and will NOT trigger "this-escape" {@code -Xlint} warning. 
  35.     */  
  36.    protected final void finalInitializer()  
  37.    {  
  38.    }  
  39.   
  40.    @Override  
  41.    protected void initializeCustomLogic()  
  42.    {  
  43.    }  
  44. }  

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.