Search code examples
javaabstract-classinstantiationinstanceof

Abstract class : Why newInstance() is not giving compilation error but constructor call gives error?


Compiler knows that AbstractDemo is an abstract class and Abstract classes can't be instantiated.

But when I call newInstance() method, why it did not give a compile time error?

import java.lang.reflect.Constructor;

public abstract class AbstractDemo{
    public AbstractDemo(){
        System.out.println("Default constructor");
    }
    public static void main(String args[]){
        try{
            /* No compilation error for this statement */
            AbstractDemo demo = AbstractDemo.class.newInstance(); 

            Constructor[] ctors = AbstractDemo.class.getDeclaredConstructors();
            for ( int i=0; i < ctors.length; i++){
                System.out.println(ctors[i]);
                /* No compilation error for this statement too */
                AbstractDemo demo1 = (AbstractDemo) ctors[i].newInstance();
            }
            /* Compilation error here */
            // AbstractDemo demo2 = new AbstractDemo(); 
        }catch(Exception err){
            err.printStackTrace();
        }
    }
}

Output when I ran this program: ( I know that error will come since I can't create instance for abstract class. But why it was not given at compile time is surprising me)

D:\Study\Java>java AbstractDemo

java.lang.InstantiationException
        at sun.reflect.InstantiationExceptionConstructorAccessorImpl.newInstance(InstantiationExceptionConstructorAccessorImpl.java:48)
        at java.lang.reflect.Constructor.newInstance(Constructor.java:526)
        at java.lang.Class.newInstance(Class.java:374)
        at AbstractDemo.main(AbstractDemo.java:10)

EDIT:

Compiler is intelligent to give error for this statement:

AbstractDemo demo2 = new AbstractDemo(); 

But not for this statement

AbstractDemo demo = AbstractDemo.class.newInstance(); 

Am I missing any key lesson here?


Solution

  • The compiler's job is to check compile-time rules (er, and compile the code). The method you're calling is Class#newInstance, not anything related (directly) to AbstractDemo. The fact that Class#newInstance will throw (because the instance of Class on which you call it is for an abstract class) is a runtime concern.

    While it might, in theory, be possible sometimes to determine at compile-time that a particular reference to a particular instance of Class refers to an abstract class (such as AbstractDemo.class), usually it won't be possible, for instance:

    void someMethodInMyOwnClass(Class c) {
        Object o = c.newInstance();
    }
    

    And even if it were, then we'd need some kind of built-in rule or annotation system (e.g., compile-time information) saying "This method of this class can't be called if the Class instance refers to an abstract class."

    So we're talking about non-trivial work, and there's no real value in doing that work, making it sometimes a compile-time error and other times a runtime error.

    Consider: The compiler could also work out that this throws an NPE:

    String s = null;
    if (s.equalsIgnoreCase("foo")) {
        // ...
    }
    

    Or that the body of this loop will never be executed:

    int x = 10;
    while (x < 10) {
        System.out.println("Never gets here");
    }
    

    But we don't have it do so; those are runtime concerns.