在技​​术上是否可以派生出一个只有 Java 中私有构造函数的类?

发布于 01-15 01:29 字数 226 浏览 2 评论 0原文

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

在不讨论需求、不修改父类代码的情况下,我们是否可以通过反射等方式获取它的构造函数并派生呢​​?

public class Parent {
    private Parent() {
    }
}

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() {
    }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

一抹微笑2025-01-22 01:29:13

从技术上讲?

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

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

但是自己创建字节码怎么样?

那么,每个构造函数都需要调用超类的构造函数或同一类的其他构造函数。
我们无法调用父类的构造函数 - 因为它不可见。
并且调用我们类的其他构造函数也是没有用的 - 因为另一个构造函数再次需要调用另一个构造函数 - 这将导致堆栈溢出。

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

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

那么 - Java 11 引入了基于 Nest 的访问控制
同一嵌套中的类可以访问私有构造函数。
但嵌套列表是静态的 - 嗯,直到 Java 15 为止。Java

15 引入了 Lookup.defineHiddenClass - 它允许我们将一个类加载为另一个类的嵌套类。

仍然无法在不更改 Parent 的情况下编译子类,因此我们手动创建字节码。最后:

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 的子类。
注意:在我的代码中,Parent 位于包 test.se17 中。

所以,是的,从技术上来说,创建 Parent 的子类是可能的。
这是个好主意吗?可能不会。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文