Technically?
Well, we have to look at what prevents doing that:
You can't access a constructor that is not visible - loading a class that tries that would be rejected by the JVM.
Javac will always create a constructor - if you do not explicitly create one, it will create the default constructor. And it's a compile time error if the super class doesn't have a visible constructor without any arguments.
So Javac is out for now.
But what about creating the bytecode yourself?
Well, every constructor either needs to call a constructor of the super class or an other constructor of the same class.
We can't call the constructor of the parent class - because it's not visible.
And calling an other constructor of our class is also not useful - as the other constructor again needs to call an other constructor - which would result in a stack overflow.
But we could simply leave out the constructor.
The downside is - now we can't create any instance of our class.
But we have a subclass.
But is the constructor really not accessible by any other class?
Well - Java 11 introduced Nest-Based Access Controls.
A class in the same nest could access the private constructor.
But the list of nestmates is static - well, was, until Java 15.
Java 15 introduced Lookup.defineHiddenClass - which allows us to load a class as nestmate of an other class.
There is still no way to compile a subclass without changing Parent, so we create the bytecode by hand. In the end:
package test.se17;
import static java.lang.invoke.MethodType.methodType;
import static org.objectweb.asm.Opcodes.*;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup.ClassOption;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
public class InheritParent {
private static final String PARENT = "test/se17/Parent";
public static void main(String[] args) throws Throwable {
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
cw.visit(V17, ACC_PUBLIC, "test/se17/Child", null, PARENT, null);
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL, PARENT, "<init>", "()V", false);
mv.visitInsn(RETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
cw.visitEnd();
MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(Parent.class, MethodHandles.lookup());
MethodHandles.Lookup childLookup = lookup.defineHiddenClass(cw.toByteArray(), true, ClassOption.NESTMATE);
Parent child = (Parent) childLookup.findConstructor(childLookup.lookupClass(), methodType(void.class)).asType(methodType(Parent.class)).invokeExact();
System.out.println(child);
System.out.println(child.getClass());
System.out.println(child instanceof Parent);
}
}
This will create, load and instantiate a subclass of Parent.
Note: In my code, Parent is in the package test.se17.
So, yes, it is technically possible to create a subclass of Parent.
Is it a good idea? Probably not.