Home > Blockchain >  How to create a new instance from within abstract class in an instance method?
How to create a new instance from within abstract class in an instance method?

Time:10-01

I would like to be able to create a new instance of a class when it's in a variable whose type is abstract.

class Scratch {
    public static void main(String[] args) {
        Implementation implementation = new Implementation();
        // ...Later in the code Implementation is abstracted away and it's AbstractClass
        AbstractClass implementedAbstractClass = implementation;
        AbstractClass newInstance = implementedAbstractClass.newInstance();
    }
}

abstract class AbstractClass {
    protected abstract AbstractClass createNewInstance();
    
    public AbstractClass newInstance() {
        return createNewInstance();
    }
}

class Implementation extends AbstractClass {
    @Override
    protected AbstractClass createNewInstance() {
        return new Implementation();
    }
}

This works, but I would like to avoid the boiler plate of having to implement createNewInstance() in every implementation I make.

I tried changing AbstractClass to accept a type parameter: AbstractClass<T extends AbstractClass<T>>, then Implementation extends AbstractClass<Implementation> and then calling new T() but of course that doesn't work either because Java doesn't know what T is at compile time so it can't add a construct instruction into the class file.

Afterwards I tried calling AbstractClass.class.getDeclaredConstructor().newInstance() but that made even less sense since I'd just be attemping to construct an instance of AbstractClass which isn't possible since it's abstract nor is it what I needed.

CodePudding user response:

The attempt of AbstractClass.class.newInstance() was very close.

To construct an instance of the implementing class we need to get its own class using the this keyword. Since abstract classes cannot be instantiated, using this within the instance methods of the abstract class will refer to the implementing class.

The correct code to call will be this.getClass().getDeclaredConstructor().newInstance().

Then the createNewInstance() method can be removed:

class Scratch {
    public static void main(String[] args) {
        Implementation implementation = new Implementation();
        // ...Later in the code Implementation is abstracted away and it's AbstractClass
        AbstractClass implementedAbstractClass = implementation;
        AbstractClass newInstance = implementedAbstractClass.newInstance();
    }
}

abstract class AbstractClass {
    public AbstractClass newInstance() {
        try {
            return this.getClass().getDeclaredConstructor().newInstance();
        } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
            throw new RuntimeException("Failed to construct instance of "   this, e);
        }
    }
}

class Implementation extends AbstractClass {
}

CodePudding user response:

Constructors are not inherited, so there's no safe way to reference them in the abstract. But you can pass them as method references:

abstract class AbstractClass {
    private final Supplier<AbstractClass> constructor;

    protected AbstractClass(Supplier<AbstractClass> constructor) {
        this.constructor = constructor;
    }

    public AbstractClass newInstance() {
        return constructor.get();
    }
}

class Implementation extends AbstractClass {
    public Implementation() {
        super(Implementation::new);
    }
}

Personally, I find it a little questionable to keep an extra reference on each instance to store what's essentially static information. But it can make the subclass a little cleaner (if you're declaring a constructor anyway).

You can also extend this a little further and make the superclass generic (as you suggested in the question) so that Implementation.newInstance() returns Implementation instead of AbstractClass:

abstract class AbstractClass<T extends AbstractClass<T>> {
    private final Supplier<T> constructor;

    protected AbstractClass(Supplier<T> constructor) {
        this.constructor = constructor;
    }

    public T newInstance() {
        return constructor.get();
    }
}

class Implementation extends AbstractClass<Implementation> {
    public Implementation() {
        super(Implementation::new);
    }
}
  • Related