-
Notifications
You must be signed in to change notification settings - Fork 2
Coding patterns
This page shows how some common coding patterns can be implemented.
A class which consists solely of static methods and fields doesn't need a constructor, but a class which can be instantiated needs one. Constructors aren't added automatically, but this is the easiest way to do it:
cm.addConstructor().public_();
A constructor must call the super
or this
constructor exactly once in the code body, or else class generation will fail. If no explicit call is made, a call to super()
is inserted automatically, which is why the above example works (assuming the superclass has a no-arg constructor).
This example shows explicit calls to the super
or this
constructor:
MethodMaker ctor1 = cm.addConstructor(String.class).private_();
ctor1.field("message").set(ctor1.param(0));
// Invoke the super class constructor. Note that it doesn't need to be the very first thing.
ctor1.invokeSuperConstructor();
MethodMaker ctor2 = cm.addConstructor().public_();
// Invoke the constructor defined above.
ctor2.invokeThisConstructor("hello");
if (a < b) {
<code>
}
The key here is to flip the if
condition:
MethodMethod mm = ...
Variable a, b = ...
Label else_ = mm.label();
a.ifGe(b, else_); // flipped from < to >=
<code>
else_.here();
if (a < b) {
<code>
} else {
<more code>
}
The flipped if
condition pattern can be used here too:
MethodMethod mm = ...
Variable a, b = ...
Label else_ = mm.label();
a.ifGe(b, else_); // flipped from < to >=
<code>
Label cont = mm.label().goto_();
else_.here();
<more code>
cont.here();
The original if
condition can also remain the same, but then the code sections must be flipped:
Label then = mm.label();
a.ifLt(b, then);
<more code>
Label cont = mm.label().goto_();
then.here();
<code>
cont.here();
Loops always follow the same basic pattern.
while (true) {
<code>
if (a < b) break;
<more code>
}
Note that the if
condition doesn't need to be flipped to break out of the loop:
MethodMethod mm = ...
Variable a, b = ...
Label start = mm.label().here();
Label end = mm.label();
<code>
a.ifLt(b, end);
<more code>
mm.goto_(start);
end.here();
while (a < b) {
<code>
}
This is similar to the simple loop, but with a flipped condition and one code section:
MethodMethod mm = ...
Variable a, b = ...
Label start = mm.label().here();
Label end = mm.label();
a.ifGe(b, end); // flipped from < to >=
<code>
mm.goto_(start);
end.here();
do {
<code>
} while (a < b);
This is the simplest loop to translate. There's only one label, and the condition isn't flipped:
MethodMethod mm = ...
Variable a, b = ...
Label start = mm.label().here();
<code>
a.ifLt(b, start);
for (int i = 0; i < 10; i++) {
<code>
if (a < b) continue;
<more code>
}
This is first logically translated to this:
int i = 0;
while (i < 10) {
<code>
if (a >= b) {
<more code>
}
i++;
}
And then it can be translated using the usual if and loop patterns:
MethodMethod mm = ...
Variable a, b = ...
Variable i = mm.var(int.class).set(0);
Label start = mm.label().here();
Label end = mm.label();
Label cont = mm.label();
i.ifGe(10, end); // flipped from < to >=
<code>
a.ifLt(b, cont); // flipped back to original condition
<more code>
cont.here();
i.inc(1);
mm.goto_(start);
end.here();
try {
<code>
} catch (Exception e) {
<handler code>
}
When implementing a catch
block, it's important that the handled code doesn't flow into the handler.
MethodMaker mm = ...
Label tryStart = mm.label().here();
<code>
Label cont = mm.label().goto_(); // branch past the handler
Label tryEnd = mm.label().here();
var e = mm.catch_(tryStart, tryEnd, Exception.class);
<handler code>
cont.here();
An easier technique can be used which uses a callback:
MethodMaker mm = ...
Label tryStart = mm.label().here();
<code>
mm.catch_(tryStart, Exception.class, e -> {
<handler code>
});
// non-exception case continues here
lock.lock();
try {
<code>
if (a < b) return;
<more code>
} finally {
lock.unlock();
}
With finally
statements, every path which can exit the try
block must be handled. The
built-in finally_
method makes this pattern automatically.
MethodMaker mm = ...
Variable lock, a, b = ...
lock.invoke("lock");
Label tryStart = mm.label().here();
<code>
Label cont = mm.label();
a.ifGe(b, cont);
mm.return_();
cont.here();
<more code>
mm.finally_(tryStart, () -> lock.invoke("unlock"));
Consider referencing variables as final. This doesn't prevent their modification, but it does prevent mistakes like this:
MethodMaker mm = ...
var a = mm.param(0);
var b = mm.param(1);
Label skip = mm.label();
b.ifLt(0, skip);
a = a.add(b); // oops
skip.here();
mm.return_(a);
The above code causes a VerifyError
because there's two a
variables, which aren't guaranteed
to be assigned for all execution paths. By declaring the variables as final, the original code
won't compile, and the correct version is:
MethodMaker mm = ...
final var a = mm.param(0);
final var b = mm.param(1);
Label skip = mm.label();
b.ifLt(0, skip);
a.set(a.add(b)); // correct
skip.here();
mm.return_(a);
The correct version assigns a new value to the existing a
variable instead of replacing it.
Although a temporary variable is still created by the add
operation, it doesn't appear in
the generated code.