Skip to content

JEP 484

Brian S. O'Neill edited this page Aug 28, 2024 · 5 revisions

JEP-484 adds a Classfile API to the JDK. Here's the "hello world" example:

byte[] bytes = ClassFile.of().build(ClassDesc.of("Hello"), cb -> {
    cb.withFlags(ClassFile.ACC_PUBLIC);
    cb.withMethod("<init>", MethodTypeDesc.of(ConstantDescs.CD_void), ClassFile.ACC_PUBLIC,
                  mb -> mb.withCode(b -> b.aload(0)
                                    .invokespecial(ConstantDescs.CD_Object, "<init>",
                                                   MethodTypeDesc.of
                                                   (ConstantDescs.CD_void))
                                    .return_(TypeKind.VoidType)
                                    )
                  )
        .withMethod("main", MethodTypeDesc.of(ConstantDescs.CD_void,
                                              ConstantDescs.CD_String.arrayType()),
                    ClassFile.ACC_STATIC | ClassFile.ACC_PUBLIC,
                    mb -> mb.withCode(b -> b.getstatic
                                      (ClassDesc.of("java.lang.System"), "out",
                                       ClassDesc.of("java.io.PrintStream"))
                                      .loadConstant(Opcode.LDC, "Hello World")
                                      .invokevirtual(ClassDesc.of("java.io.PrintStream"),
                                                     "println",
                                                     MethodTypeDesc.of
                                                     (ConstantDescs.CD_void,
                                                      ConstantDescs.CD_String))
                                      .return_(TypeKind.VoidType)
                                      ));
});

Here's the equivalent example using the Cojen/Maker API:

ClassMaker cm = ClassMaker.beginExternal("Hello").public_();
cm.addConstructor().public_();
MethodMaker mm = cm.addMethod(void.class, "main", String[].class).public_().static_();
mm.var(System.class).field("out").invoke("println", "Hello World");
byte[] bytes = cm.finishBytes();

As I see it, the main problem with the JEP Classfile API is that it depends on callbacks/lambdas. A design which requires the use of lambdas (or helper classes) is always more difficult to use, but it makes sense in a few cases. The first case is the stream API, which is designed such that the lambdas can be defined very concisely. In the second case, by necessity, an async API also relies on lambdas, but this is part of the reason why async APIs are difficult to use.

One thing that I've found useful when generating code that uses a low-level API like Cojen or ASM is that I can implement multiple methods concurrently. Not in separate threads, but rather that instructions can be appended incrementally. With a callback-based design, an extra step is required to pre-buffer all the instructions in a custom intermediate format. This just adds unnecessary complexity and overhead.

The only argument I've found in favor of the callback design is that it somehow helps with dealing with wide branch offsets. Such a fixup only requires that the affected byte instructions be re-emitted, which is fairly cheap. With callbacks, the internal objects representing the byte instructions need to be regenerated as well, which has higher overhead. Of course there's also the odd case when the callback isn't truly stateless, and so it might produce different instructions on subsequent passes.

Clone this wiki locally