Variablen final
zu machen kann sinnvoll sein.
Fields sollten wann immer möglich final
sein. (Also private final
ist der default, und jede Abweichung davon muss gut gerechtfertigt sein - POJOs/Beans natürlich außen vor gelassen).
Methodenparameter sollten nicht verändert werden. Das könnte man erzwingen, indem man sie final
macht, aber das ist IMHO häßlich - da sollte man eher die Warnings in der IDE passend einstellen (wenn man nicht die „Disziplin“ hat, die man braucht, um das sowieso einzuhalten).
Lokale Variablen kann man mal final
machen. Das hat aber eher den Charakter von „Dokumentation“ - eben dass man weiß, dass die Variable nicht mehr verändert wird.
Der JVM ist das aber wurscht. Es gibt praktisch keinen Fall, wo ein final
für Variablen wirklich einen Unterschied macht. Man sieht zwar einen Unterschied im bytecode (wie @neoexpert ja gepostet hat). Aber … der Bytecode hat mit dem, was die Maschine macht, nicht viel zu tun.
Wenn man mal folgendes Programm anschaut…
public class FinalAgain
{
public static void main(String args[])
{
runTest();
}
private static void runTest()
{
int sumA = 0;
int sumB = 0;
for (int i=0; i<10000; i++)
{
sumA += fooWithFinal();
sumB += fooWithoutFinal();
}
System.out.println(sumA);
System.out.println(sumB);
}
public static int fooWithFinal()
{
final int a=5;
final int b=7;
int c=a+b;
return c;
}
public static int fooWithoutFinal()
{
int a=5;
int b=7;
int c=a+b;
return c;
}
}
und sich mit javap -c FinalAgain.class
den passenden bytecode ansieht
Compiled from "FinalAgain.java"
public class FinalAgain {
public FinalAgain();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: invokestatic #2 // Method runTest:()V
3: return
public static int fooWithFinal();
Code:
0: bipush 12
2: istore_2
3: iload_2
4: ireturn
public static int fooWithoutFinal();
Code:
0: iconst_5
1: istore_0
2: bipush 7
4: istore_1
5: iload_0
6: iload_1
7: iadd
8: istore_2
9: iload_2
10: ireturn
}
sieht man einen Unterschied zwischen den Methoden.
Wenn man das ganze aber mal in einer Hotspot-Disassembler-VM mit java -server -XX:+UnlockDiagnosticVMOptions -XX:+TraceClassLoading -XX:+LogCompilation -XX:+PrintAssembly -XX:+PrintInlining FinalAgain
startet und den ganzen Kram durchnudeln läßt, kommen folgende Ergebnisse für die beiden Methoden raus:
Für fooWithFinal
:
Decoding compiled method 0x000001a59b7e1610:
Code:
[Entry Point]
[Verified Entry Point]
[Constants]
# {method} {0x000001a5bd960468} 'fooWithFinal' '()I' in 'FinalAgain'
# [sp+0x40] (sp of caller)
0x000001a59b7e1760: mov dword ptr [rsp-0x6000],eax
0x000001a59b7e1767: push rbp
0x000001a59b7e1768: sub rsp,0x30
0x000001a59b7e176c: movabs rax,0x1a5bd9606a8 ; {metadata(method data for {method} {0x000001a5bd960468} 'fooWithFinal' '()I' in 'FinalAgain')}
0x000001a59b7e1776: mov esi,dword ptr [rax+0xdc]
0x000001a59b7e177c: add esi,0x8
0x000001a59b7e177f: mov dword ptr [rax+0xdc],esi
0x000001a59b7e1785: movabs rax,0x1a5bd960460 ; {metadata({method} {0x000001a5bd960468} 'fooWithFinal' '()I' in 'FinalAgain')}
0x000001a59b7e178f: and esi,0x1ff8
0x000001a59b7e1795: cmp esi,0x0
0x000001a59b7e1798: je 0x000001a59b7e17af ;*bipush
; - FinalAgain::fooWithFinal@0 (line 27)
0x000001a59b7e179e: mov eax,0xc
0x000001a59b7e17a3: add rsp,0x30
0x000001a59b7e17a7: pop rbp
0x000001a59b7e17a8: test dword ptr [rip+0xfffffffffe70e952],eax # 0x000001a599ef0100
; {poll_return}
0x000001a59b7e17ae: ret
0x000001a59b7e17af: mov qword ptr [rsp+0x8],rax
0x000001a59b7e17b4: mov qword ptr [rsp],0xffffffffffffffff
0x000001a59b7e17bc: call 0x000001a59b7d05a0 ; OopMap{off=97}
;*synchronization entry
; - FinalAgain::fooWithFinal@-1 (line 27)
; {runtime_call}
0x000001a59b7e17c1: jmp 0x000001a59b7e179e
0x000001a59b7e17c3: nop
0x000001a59b7e17c4: nop
0x000001a59b7e17c5: mov rax,qword ptr [r15+0x2a8]
0x000001a59b7e17cc: movabs r10,0x0
0x000001a59b7e17d6: mov qword ptr [r15+0x2a8],r10
0x000001a59b7e17dd: movabs r10,0x0
0x000001a59b7e17e7: mov qword ptr [r15+0x2b0],r10
0x000001a59b7e17ee: add rsp,0x30
0x000001a59b7e17f2: pop rbp
0x000001a59b7e17f3: jmp 0x000001a59b740620 ; {runtime_call}
Für fooWithoutFinal
:
Decoding compiled method 0x000001a59b7e1950:
Code:
[Entry Point]
[Verified Entry Point]
[Constants]
# {method} {0x000001a5bd960508} 'fooWithoutFinal' '()I' in 'FinalAgain'
# [sp+0x40] (sp of caller)
0x000001a59b7e1aa0: mov dword ptr [rsp-0x6000],eax
0x000001a59b7e1aa7: push rbp
0x000001a59b7e1aa8: sub rsp,0x30
0x000001a59b7e1aac: movabs rax,0x1a5bd960688
0x000001a59b7e1ab6: mov esi,dword ptr [rax+0x8]
0x000001a59b7e1ab9: add esi,0x8
0x000001a59b7e1abc: mov dword ptr [rax+0x8],esi
0x000001a59b7e1abf: movabs rax,0x1a5bd960500 ; {metadata({method} {0x000001a5bd960508} 'fooWithoutFinal' '()I' in 'FinalAgain')}
0x000001a59b7e1ac9: and esi,0x3ff8
0x000001a59b7e1acf: cmp esi,0x0
0x000001a59b7e1ad2: je 0x000001a59b7e1ae9 ;*iconst_5
; - FinalAgain::fooWithoutFinal@0 (line 33)
0x000001a59b7e1ad8: mov eax,0xc
0x000001a59b7e1add: add rsp,0x30
0x000001a59b7e1ae1: pop rbp
0x000001a59b7e1ae2: test dword ptr [rip+0xfffffffffe70e618],eax # 0x000001a599ef0100
; {poll_return}
0x000001a59b7e1ae8: ret
0x000001a59b7e1ae9: mov qword ptr [rsp+0x8],rax
0x000001a59b7e1aee: mov qword ptr [rsp],0xffffffffffffffff
0x000001a59b7e1af6: call 0x000001a59b7d05a0 ; OopMap{off=91}
;*synchronization entry
; - FinalAgain::fooWithoutFinal@-1 (line 33)
; {runtime_call}
0x000001a59b7e1afb: jmp 0x000001a59b7e1ad8
0x000001a59b7e1afd: nop
0x000001a59b7e1afe: nop
0x000001a59b7e1aff: mov rax,qword ptr [r15+0x2a8]
0x000001a59b7e1b06: movabs r10,0x0
0x000001a59b7e1b10: mov qword ptr [r15+0x2a8],r10
0x000001a59b7e1b17: movabs r10,0x0
0x000001a59b7e1b21: mov qword ptr [r15+0x2b0],r10
0x000001a59b7e1b28: add rsp,0x30
0x000001a59b7e1b2c: pop rbp
0x000001a59b7e1b2d: jmp 0x000001a59b740620 ; {runtime_call}
Ja: Die sind gleich.
Mag sein, dass die JVM sowas auch optimieren kann, aber es stört ja nicht, wenn die Methoden von vorne hinein schon etwas weniger bytes an Speicher verbrauchen.
Das stimmt bis zu einem geissen Grad. In den tiefsten Innereien der Concurrency-Klassen wird teilweise ein „obskur“ erscheinender Stil verwendet. Dort werden z.B. Fields auch nochmal in (finalen) lokalen Variablen gespeichert. Also z.B. nicht
class HashMap {
Entry table[];
void doSomething() {
...
this.table[i].key = k;
this.table[i].value = v;
}
}
sondern
class HashMap {
Entry table[];
void doSomething() {
final Entry t[] = this.table;
...
t[i].key = k;
t[i].value = v;
}
}
(Doug Lea ist da „bekannt“ dafür…). Aber das bezieht sich auf riesige, performance-kritischSTe Kern-Klassen, wo die Bytecode-Größe vielleicht eine Rolle spielt.
In „normalem“ Code sollte man das machen, was am besten lesbar und nachvollziehbar ist. Da kann auch ein final
für lokale Variablen dazugehören, aber … man sollte nicht sagen „Ich mach’ das wegen der höheren Performance“ - das Argument ist einfach zu dünn, wenn man sich’s genau anschaut.