I have a class which contains several inner classes. I would like to generate additional inner classes that interact with the compile-time private inner classes using the ASM library. My code looks like:
public class Parent {
public void generateClass() {
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
cw.visit(49, Opcodes.ACC_PUBLIC, "Parent$OtherChild", null,
Type.getInternalName(Child.class), new String[]{});
// .. generate the class
byte[] bytes = cw.toByteArray();
Class<?> genClass = myClassLoader.defineClass("Parent$OtherChild", bytes);
}
private static class Child {
}
}
As shown, a simple example of interaction is inheritance - I am trying to gnerate class OtherChild that extends the private inner class Child. I get this error message while the class loader is verifying the class definition:
IllegalAccessError: class Parent$OtherChild cannot access its superclass Parent$Child
Is there a way to generate inner classes that can interact with other private inner classes? You can assume that this is performed from the "safe zone" where the private inner class is accessible.
thank you
Accessing the Private Members Write an inner class in it, return the private members from a method within the inner class, say, getValue(), and finally from another class (from which you want to access the private members) call the getValue() method of the inner class.
A nested class is a member of its enclosing class. Non-static nested classes (inner classes) have access to other members of the enclosing class, even if they are declared private.
Yes, you can instantiate a private inner class with Java reflection. To do that, you need to have an instance of outer class and invoke the inner class constructor which will use outer class instance in its first argument. @popgalop Inner classes are the same as methods.
They are accessed using the enclosing class name. To instantiate an inner class, you must first instantiate the outer class. Then, create the inner object within the outer object with this syntax: OuterClass.
The rule that inner and outer classes can access their private
members is a pure Java programming language construct which isn’t reflected by the JVM’s access checks. When inner classes were introduced in Java 1.1, they were introduced in a way that didn’t require changes to the JVM. From the JVM’s point of view, nested classes are ordinary (top level) classes with some additional, ignorable meta information.
When an inner class is declared private
, it’s ordinary class access level is “default” aka package-private. When it’s declared protected
, it will be public
on the JVM level.
When nested classes access each other’s private
fields or methods, the compiler will generate synthetic helper methods with package-private access in the target class, providing the desired access.
So from the JVM’s point of view, you are trying to subclass a package-private class and the dollar in the name is just an ordinary name character. The generated class has a matching qualified name, but you are trying to define it in a different class loader, so the JVM considers these packages not identical at runtime, despite their identical name.
You can verify that the package level access works, if you define the class within the same class loader. Change the line
Class<?> genClass = myClassLoader.defineClass("Parent$OtherChild", bytes);
to
Method m=ClassLoader.class.getDeclaredMethod(
"defineClass", String.class, byte[].class, int.class, int.class);
m.setAccessible(true);
Class<?> genClass=(Class<?>)m.invoke(
Child.class.getClassLoader(), "Parent$OtherChild", bytes, 0, bytes.length);
Alternatively, you could declare Child
as protected
. Since it’s a public
class on the low level then, it will be accessible by other class loaders.
Note that in both cases, you haven’t created a new inner class but just a class named Parent$OtherChild
extending an inner class. The only difference is the meta information about the outer-inner class relationship but if you add that attribute to your generated class claiming that it was an inner class of Parent
, it could happen that it gets rejected by the verifier because the meta information of Parent
doesn’t mention the existence of an inner class OtherChild
. That’s the only place where a JVM might ever look at this attribute.
But besides Reflection reporting inner class relationships, there is no functional difference between top level classes and nested classes anyway. As said, classes actually don’t have the access levels protected
nor private
and for all other member accesses, you’ll have to generate the necessary code yourself anyway. If you can’t modify the code of the existing classes Parent
or Parent$Child
, you can’t access those of their private
members for which these synthetic accessor methods don’t already exist…
Starting with Java 9, there is a standard way to define a new class within an accessible context, which makes the “Reflection with access override” approach shown above obsolete for this use case, e.g. the following works:
public class Parent {
public void generateClass() {
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
String superType = Type.getInternalName(Child.class);
cw.visit(49, Opcodes.ACC_PUBLIC, "Parent$OtherChild", null, superType, null);
MethodVisitor mv = cw.visitMethod(0, "<init>", "()V", null, null);
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, superType, "<init>", "()V", false);
mv.visitInsn(Opcodes.RETURN);
mv.visitMaxs(-1, -1);
mv.visitEnd();
// etc
byte[] bytes = cw.toByteArray();
MethodHandles.Lookup lookup = MethodHandles.lookup();
try {
Class<?> genClass = lookup.defineClass(bytes);
Child ch = (Child)
lookup.findConstructor(genClass, MethodType.methodType(void.class))
.invoke();
System.out.println(ch);
} catch(Throwable ex) {
Logger.getLogger(Parent.class.getName()).log(Level.SEVERE, null, ex);
}
}
private static class Child {
Child() {}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With