0

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.

  • Related to https://stackoverflow.com/questions/56385734/java-how-to-create-new-child-class-instance-from-abstract-parent-class but not a duplicate because that question wants to instantiate from a static context. – CausingUnderflowsEverywhere Sep 30 '21 at 23:04
  • 1
    You could pass the constructor as a method reference: `super(Implementation::new)`. But there's no way to safely reference a constructor in the abstract, because constructors are not inherited. – shmosel Sep 30 '21 at 23:28
  • @shmosel that worked as well and I learned something new about functional interfaces. Would you mind if I transformed your comment into an answer? – CausingUnderflowsEverywhere Oct 01 '21 at 00:15
  • I actually started writing it as an answer but changed it to a comment because it felt a little unsatisfying. But I'll post it for you. – shmosel Oct 01 '21 at 00:23
  • It took me a couple steps and some research to get it implemented in my example code which is why it would be useful if it were a full answer and showed the implementation. Im sure it'll be a great answer! :) – CausingUnderflowsEverywhere Oct 01 '21 at 00:26

2 Answers2

1

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 {
}
  • Although capable of causing a runtime exception if constructor is inaccessible I like this way better because it doesn't introduce a responsibility to the implementing class which may not make sense at all if used in an API. If the exception matters there could be startup checks to make sure all implementers have an accessible constructor etc. Or just use reflection to set the constructor as accessible. – CausingUnderflowsEverywhere Oct 02 '21 at 14:45
1

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);
    }
}
shmosel
  • 49,289
  • 6
  • 73
  • 138
  • 1
    That's exactly the code I came up with for your comment! This is a good solution because it's type safe. There won't be gotchas like how there are in reflection with a constructor not being there. – CausingUnderflowsEverywhere Oct 01 '21 at 00:37