-
Notifications
You must be signed in to change notification settings - Fork 2
JEP 484
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. One such case is the stream API, which is designed such that the lambdas can be defined very concisely. For another 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.
The Classfile API contains over 2,000 publicly declared classes, interfaces, methods, fields, etc. By comparison, the Cojen/Maker API contains a bit over 250 such items. By this measurement, the Classfile API is eight times more complicated to use. Part of this complexity is due to the fact that it also supports classfile transformations, but there's no convenient way of filtering out this API when reading the documentation.