Assume that we have a following class:
final class Impl implements Gateway3 {
private final Sensor sensor1;
private final Sensor sensor2;
private final Sensor sensor3;
private final Alarm alarm;
public Impl(Sensor sensor1, Sensor sensor2, Sensor sensor3, Alarm alarm) {
this.sensor1 = sensor1;
this.sensor2 = sensor2;
this.sensor3 = sensor3;
this.alarm = alarm;
}
@Override
public Temperature averageTemp() {
final Temperature temp1 = sensor1.temperature();
final Temperature temp2 = sensor2.temperature();
final Temperature temp3 = sensor3.temperature();
final Average tempAvg = new Average.Impl(temp1, temp2, temp3);
final Temperature result = tempAvg.result();
return result;
}
@Override
public void poll() {
final Temperature avgTemp = this.averageTemp();
this.alarm.trigger(avgTemp);
}
This class widely uses local variables and all of them are final.
If we look at the bytecode generated for, let's say, averageTemp
method, we'll see the following bytecode:
0: aload_0
1: getfield #2 // Field sensor1:Lru/mera/avral/script/bytecode/demo/Sensor;
4: invokeinterface #6, 1 // InterfaceMethod ru/mera/avral/script/bytecode/demo/Sensor.temperature:()Lru/mera/avral/script/bytecode/demo/Temperature;
9: astore_1
10: aload_0
11: getfield #3 // Field sensor2:Lru/mera/avral/script/bytecode/demo/Sensor;
14: invokeinterface #6, 1 // InterfaceMethod ru/mera/avral/script/bytecode/demo/Sensor.temperature:()Lru/mera/avral/script/bytecode/demo/Temperature;
19: astore_2
20: aload_0
21: getfield #4 // Field sensor3:Lru/mera/avral/script/bytecode/demo/Sensor;
24: invokeinterface #6, 1 // InterfaceMethod ru/mera/avral/script/bytecode/demo/Sensor.temperature:()Lru/mera/avral/script/bytecode/demo/Temperature;
29: astore_3
30: new #7 // class ru/mera/avral/script/bytecode/demo/Average$Impl
33: dup
34: aload_1
35: aload_2
36: aload_3
37: invokespecial #8 // Method ru/mera/avral/script/bytecode/demo/Average$Impl."<init>":(Lru/mera/avral/script/bytecode/demo/Temperature;Lru/mera/avral/script/bytecode/demo/Temperature;Lru/mera/avral/script/bytecode/demo/Temperature;)V
40: astore 4
42: aload 4
44: invokeinterface #9, 1 // InterfaceMethod ru/mera/avral/script/bytecode/demo/Average.result:()Lru/mera/avral/script/bytecode/demo/Temperature;
49: astore 5
51: aload 5
53: areturn
There are plenty of astore opcodes.
Now, assume that using bytecode generation library, I generated the following bytecode for the same method:
0: new #18 // class ru/mera/avral/script/bytecode/demo/Average$Impl
3: dup
4: aload_0
5: getfield #20 // Field sensor1:Lru/mera/avral/script/bytecode/demo/Sensor;
8: invokeinterface #25, 1 // InterfaceMethod ru/mera/avral/script/bytecode/demo/Sensor.temperature:()Lru/mera/avral/script/bytecode/demo/Temperature;
13: aload_0
14: getfield #27 // Field sensor2:Lru/mera/avral/script/bytecode/demo/Sensor;
17: invokeinterface #25, 1 // InterfaceMethod ru/mera/avral/script/bytecode/demo/Sensor.temperature:()Lru/mera/avral/script/bytecode/demo/Temperature;
22: aload_0
23: getfield #29 // Field sensor3:Lru/mera/avral/script/bytecode/demo/Sensor;
26: invokeinterface #25, 1 // InterfaceMethod ru/mera/avral/script/bytecode/demo/Sensor.temperature:()Lru/mera/avral/script/bytecode/demo/Temperature;
31: invokespecial #33 // Method ru/mera/avral/script/bytecode/demo/Average$Impl."<init>":(Lru/mera/avral/script/bytecode/demo/Temperature;Lru/mera/avral/script/bytecode/demo/Temperature;Lru/mera/avral/script/bytecode/demo/Temperature;)V
34: invokevirtual #36 // Method ru/mera/avral/script/bytecode/demo/Average$Impl.result:()Lru/mera/avral/script/bytecode/demo/Temperature;
37: areturn
Semantically, this new method implementation has the same meaning comparing to the old one - it still takes the temperature value from three sensors, make an average from them and returns it. But instead of putting intermediate values to variables, it does all the calculations on stack. I can rewrite it that way since all my local variables and fields are final.
Now there is a question: if I am doing some bytecode-generation-related magic and follow this "all calculations on stack" approach everywhere (assuming that all my variables and fields are final), what potential pitfalls may I face?
NOTE: I have no intention to rewrite bytecode for existing Java classes in the way I described. The example class is given here just to show the method semantics I want to achieve in my bytecode.