Home > database >  Is it technically possible to derive a class that only has a private constructor in Java?
Is it technically possible to derive a class that only has a private constructor in Java?

Time:03-18

I found a lot of answers about it on this site, but most of them are based on modifying the requirements or modifying the code of the parent class to do so.

Without discussing the requirements and modifying the code of the parent class, can we get its constructor and derive it through reflection and other means?

public class Parent {
    private Parent() {
    }
}

CodePudding user response:

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.

  • Related