技术上是否有可能派生出一个 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。
这是个好主意吗?应该不是。
我在这个网站上找到了很多关于它的答案,但是大多数都是基于修改需求或者修改父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.
仍然没有办法在不改变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。
这是个好主意吗?应该不是。