技术上是否有可能派生出一个 class,它在 Java 中只有一个私有构造函数?

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

我在这个网站上找到了很多关于它的答案,但是大多数都是基于修改需求或者修改父class的代码来做的。

不讨论需求,不修改parent的代码class,是否可以通过反射等方式获取其构造函数并派生?

public class Parent {
    private Parent() {
    }
}

技术上?

好吧,我们必须看看是什么阻止了这样做:
您无法访问不可见的构造函数 - 加载一个 class 尝试将被 JVM 拒绝。

Javac 将始终创建一个构造函数 - 如果您不显式创建一个构造函数,它将创建默认构造函数。如果 super class 没有不带任何参数的可见构造函数,则会出现编译时错误。
所以 Javac 暂时没有了。

但是自己创建字节码呢?

好吧,每个构造函数都需要调用超级 class 的构造函数或相同 class.
的其他构造函数 我们不能调用父级的构造函数 class - 因为它不可见。
并且调用我们 class 的另一个构造函数也没有用 - 因为另一个构造函数再次需要调用另一个构造函数 - 这会导致堆栈溢出。

但我们可以简单地省略构造函数。
缺点是 - 现在我们无法创建 class.
的任何实例 但是我们有一个subclass.

但是构造函数真的不能被其他任何人访问吗class?

嗯~Java11介绍Nest-Based Access Controls.
同一个嵌套中的 class 可以访问私有构造函数。
但是 nestmates 列表是静态的 - 好吧,直到 Java 15.

Java 15 引入了 Lookup.defineHiddenClass - 这允许我们加载一个 class 作为另一个 class.

的 nestmate

仍然没有办法在不改变Parent的情况下编译子class,所以我们手工创建字节码。最后:

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);
    }
}

这将创建、加载和实例化 Parent 的子class。
注意:在我的代码中,Parent 在包 test.se17.

所以,是的,技术上可以创建 Parent 的子class。
这是个好主意吗?应该不是。