In Java sehen wir viele Stellen, an denen das final
Schlüsselwort verwendet werden kann, aber seine Verwendung ist ungewöhnlich.
Zum Beispiel:
String str = "abc";
System.out.println(str);
Im obigen Fall str
kann sein, final
aber dies wird üblicherweise weggelassen.
Wenn eine Methode niemals überschrieben wird, können wir das Schlüsselwort final verwenden. Ähnliches gilt für eine Klasse, die nicht vererbt wird.
Verbessert die Verwendung des endgültigen Schlüsselworts in einem oder allen dieser Fälle die Leistung wirklich? Wenn ja, wie dann? Bitte erkläre. final
Welche Gewohnheiten sollte ein Java-Programmierer entwickeln, um das Schlüsselwort optimal zu nutzen, wenn die ordnungsgemäße Verwendung wirklich für die Leistung von Bedeutung ist?
Antworten:
Normalerweise nicht. Bei virtuellen Methoden verfolgt HotSpot, ob die Methode tatsächlich überschrieben wurde, und kann Optimierungen wie Inlining unter der Annahme durchführen, dass eine Methode nicht überschrieben wurde - bis zu diesem Zeitpunkt eine Klasse geladen wird, die die Methode überschreibt Diese Optimierungen können rückgängig gemacht (oder teilweise rückgängig gemacht) werden.
(Dies setzt natürlich voraus, dass Sie HotSpot verwenden - aber es ist bei weitem die häufigste JVM, also ...)
Meiner Meinung nach sollten Sie es
final
eher aus Gründen des klaren Designs und der Lesbarkeit als aus Leistungsgründen verwenden. Wenn Sie aus Leistungsgründen Änderungen vornehmen möchten, sollten Sie geeignete Messungen durchführen, bevor Sie den klarsten Code aus der Form bringen. Auf diese Weise können Sie entscheiden, ob eine zusätzliche Leistung die schlechtere Lesbarkeit / das schlechtere Design wert ist. (Nach meiner Erfahrung ist es das fast nie wert; YMMV.)EDIT: Da die letzten Felder erwähnt wurden, ist es erwähnenswert, dass sie in Bezug auf ein klares Design oft eine gute Idee sind. Sie ändern auch das garantierte Verhalten in Bezug auf die Cross-Thread-Sichtbarkeit: Nach Abschluss eines Konstruktors sind alle endgültigen Felder garantiert sofort in anderen Threads sichtbar. Dies ist wahrscheinlich die häufigste Verwendung von
final
meiner Erfahrung nach , wenn auch als Fan von Josh Blochs „Design für die Vererbung oder verbietet es“ Faustregel ich wahrscheinlich verwenden solltefinal
öfter für die Klassen ...quelle
final
wird im Allgemeinen empfohlen, da es das Verständnis von Code erleichtert und das Auffinden von Fehlern erleichtert (weil dadurch die Absicht des Programmierers explizit wird). PMD empfiehlt wahrscheinlich die Verwendungfinal
aufgrund dieser Stilprobleme, nicht aus Leistungsgründen.Immutable classes are easier to design, implement, and use than mutable classes. They are less prone to error and are more secure.
. Des WeiterenAn immutable object can be in exactly one state, the state in which it was created.
vsMutable objects, on the other hand, can have arbitrarily complex state spaces.
. Aus meiner persönlichen Erfahrung sollte die Verwendung des Schlüsselwortsfinal
die Absicht des Entwicklers hervorheben, sich der Unveränderlichkeit zuzuwenden und den Code nicht zu "optimieren". Ich ermutige Sie, dieses Kapitel zu lesen, faszinierend!final
Schlüsselworts für Variablen die Menge an Bytecode verringern kann, was sich auf die Leistung auswirken kann.Kurze Antwort: Mach dir keine Sorgen!
Lange Antwort:
Wenn Sie über endgültige lokale Variablen sprechen , denken Sie daran, dass die Verwendung des Schlüsselworts
final
dem Compiler hilft, den Code statisch zu optimieren , was letztendlich zu schnellerem Code führen kann. Beispielsweise werden die letzten Zeichenfolgena + b
im folgenden Beispiel statisch (zur Kompilierungszeit) verkettet.Das Ergebnis?
Getestet auf Java Hotspot VM 1.7.0_45-b18.
Wie hoch ist die tatsächliche Leistungsverbesserung? Ich wage es nicht zu sagen. In den meisten Fällen wahrscheinlich marginal (~ 270 ns in diesem synthetischen Test , weil die String - Verkettung ganz vermieden wird - ein seltener Fall), aber in hoch optimierten Code Dienstprogramm es könnte ein Faktor sein. In jedem Fall lautet die Antwort auf die ursprüngliche Frage Ja, dies könnte die Leistung verbessern, aber bestenfalls geringfügig .
Abgesehen von den Vorteilen bei der Kompilierung konnte ich keine Beweise dafür finden, dass die Verwendung des Schlüsselworts
final
messbare Auswirkungen auf die Leistung hat.quelle
String
Verkettung ist viel teurer als das HinzufügenIntegers
. Statisch (wenn möglich) zu arbeiten ist effizienter, das zeigt der Test.Ja, kann es. Hier ist ein Fall, in dem final die Leistung steigern kann:
Die bedingte Kompilierung ist eine Technik, bei der Codezeilen nicht basierend auf einer bestimmten Bedingung in die Klassendatei kompiliert werden. Dies kann verwendet werden, um Tonnen von Debugging-Code in einem Produktionsbuild zu entfernen.
Folgendes berücksichtigen:
Durch Konvertieren des doSomething-Attributs in ein endgültiges Attribut haben Sie dem Compiler mitgeteilt, dass er doSomething immer dann, wenn er es sieht, gemäß den Ersetzungsregeln zur Kompilierungszeit durch false ersetzen sollte. Der erste Durchlauf des Compilers ändert den Code etwas wie folgt aus :
Sobald dies erledigt ist, wirft der Compiler einen weiteren Blick darauf und stellt fest, dass der Code nicht erreichbare Anweisungen enthält. Da Sie mit einem Compiler von höchster Qualität arbeiten, werden all diese nicht erreichbaren Bytecodes nicht gemocht. Also werden sie entfernt und Sie erhalten Folgendes:
Dadurch werden übermäßige Codes oder unnötige bedingte Überprüfungen reduziert.
Bearbeiten: Nehmen wir als Beispiel den folgenden Code:
Beim Kompilieren dieses Codes mit Java 8 und Dekompilieren mit erhalten
javap -c Test.class
wir:Wir können feststellen, dass kompilierter Code nur die nicht endgültige Variable enthält
x
. Dies beweist, dass endgültige Variablen zumindest für diesen einfachen Fall Auswirkungen auf die Leistung haben.quelle
Laut IBM gilt dies nicht für Klassen oder Methoden.
http://www.ibm.com/developerworks/java/library/j-jtp04223.html
quelle
Ich bin erstaunt, dass niemand einen echten Code gepostet hat, der dekompiliert wurde, um zu beweisen, dass es zumindest einen kleinen Unterschied gibt.
Als Referenz wurde dies gegen
javac
Version getestet8
,9
und10
.Angenommen, diese Methode:
Wenn Sie diesen Code so kompilieren, wie er ist, wird genau derselbe Bytecode erzeugt, als wenn
final
er vorhanden gewesen wäre (final Object left = new Object();
).Aber dieser:
Produziert:
Weggehen
final
Gegenwart erzeugt werden:Der Code ist so gut wie selbsterklärend. Wenn es eine Kompilierungszeitkonstante gibt, wird er direkt auf den Operandenstapel geladen (er wird nicht wie im vorherigen Beispiel über in einem Array mit lokalen Variablen gespeichert
bipush 12; istore_0; iload_0
) - was sinnvoll ist da kann es niemand ändern.Auf der anderen Seite
istore_0 ... iload_0
ist es mir ein Rätsel, warum der Compiler im zweiten Fall nicht produziert . Es ist nicht so, dass dieser Slot0
in irgendeiner Weise verwendet wird (es könnte das Variablenarray auf diese Weise verkleinern, aber möglicherweise fehlen mir einige interne Details, kann nicht sicher erzählen)Ich war überrascht, eine solche Optimierung zu sehen, wenn man bedenkt, wie wenig es
javac
tut. Wie sollen wir immer verwendenfinal
? Ich werde nicht einmal einenJMH
Test schreiben (den ich anfangs wollte), ich bin mir sicher, dass der Unterschied in der Reihenfolge liegtns
(wenn möglich überhaupt erfasst werden). Der einzige Ort, an dem dies ein Problem sein könnte, ist, wenn eine Methode aufgrund ihrer Größe nicht inline gesetzt werden konnte (und das Deklarierenfinal
diese Größe um einige Bytes verkleinern würde).Es gibt zwei weitere
final
, die angegangen werden müssen. Erstens, wenn eine Methodefinal
(aus einerJIT
Perspektive) ist, ist eine solche Methode monomorph - und dies sind die beliebtesten von derJVM
.Dann gibt es
final
Instanzvariablen (die in jedem Konstruktor festgelegt werden müssen); Diese sind wichtig, da sie eine korrekt veröffentlichte Referenz garantieren, wie hier ein wenig berührt und auch genau von derJLS
.quelle
javac -g FinalTest.java
) kompiliert, den Code mit dekompiliertjavap -c FinalTest.class
und nicht die gleichen Ergebnisse erzielt (mitfinal int left=12
, die ich erhalten habebipush 11; istore_0; bipush 12; istore_1; bipush 11; iload_1; iadd; ireturn
). Ja, der generierte Bytecode hängt von vielen Faktoren ab, und es ist schwer zu sagen, ob sich diesfinal
auf die Leistung auswirkt oder nicht. Da der Bytecode jedoch unterschiedlich ist, kann es zu Leistungsunterschieden kommen.Sie fragen wirklich nach zwei (mindestens) verschiedenen Fällen:
final
für lokale Variablenfinal
für Methoden / KlassenJon Skeet hat bereits geantwortet 2). Über 1):
Ich denke nicht, dass es einen Unterschied macht; Bei lokalen Variablen kann der Compiler ableiten, ob die Variable endgültig ist oder nicht (indem er einfach überprüft, ob sie mehr als einmal zugewiesen wurde). Wenn der Compiler also Variablen optimieren wollte, die nur einmal zugewiesen wurden, kann er dies tun, unabhängig davon, ob die Variable tatsächlich deklariert ist
final
oder nicht.final
könnte für geschützte / öffentliche Klassenfelder einen Unterschied machen; Dort ist es für den Compiler sehr schwierig herauszufinden, ob das Feld mehr als einmal festgelegt wird, da dies von einer anderen Klasse stammen kann (die möglicherweise noch nicht einmal geladen wurde). Aber selbst dann könnte die JVM die von Jon beschriebene Technik verwenden (optimistisch optimieren, zurücksetzen, wenn eine Klasse geladen wird, die das Feld ändert).Zusammenfassend sehe ich keinen Grund, warum es die Leistung verbessern sollte. Daher ist es unwahrscheinlich, dass diese Art der Mikrooptimierung hilft. Sie könnten versuchen, es zu vergleichen, um sicherzugehen, aber ich bezweifle, dass es einen Unterschied machen wird.
Bearbeiten:
Laut der Antwort von Timo Westkämper
final
kann dies in einigen Fällen die Leistung für Klassenfelder verbessern. Ich stehe korrigiert.quelle
final
, um sicherzustellen, dass Sie sie nicht zweimal zuweisen. Soweit ich sehen kann, kann (und wird) dieselbe Überprüfungslogik verwendet werden, um zu überprüfen, ob eine Variable sein könntefinal
.javac
, besser zu optimieren. Dies ist jedoch nur Spekulation.final
Variable von einem primitiven Typ oder Typ istString
und sofort mit einer Kompilierungszeitkonstante wie im Beispiel der Frage belegt wird, muss der Compiler diese einbinden, da die Variable eine Kompilierungszeitkonstante pro Spezifikation ist. In den meisten Anwendungsfällen sieht der Code möglicherweise anders aus, es spielt jedoch keine Rolle, ob die Konstante in Bezug auf die Leistung inline ist oder von einer lokalen Variablen gelesen wird.Hinweis: Kein Java-Experte
Wenn ich mich richtig an mein Java erinnere, gibt es kaum eine Möglichkeit, die Leistung mit dem Schlüsselwort final zu verbessern. Ich habe immer gewusst, dass es für "guten Code" existiert - Design und Lesbarkeit.
quelle
Ich bin kein Experte, aber ich nehme an, Sie sollten
final
der Klasse oder Methode ein Schlüsselwort hinzufügen , wenn es nicht überschrieben wird, und Variablen in Ruhe lassen. Wenn es eine Möglichkeit gibt, solche Dinge zu optimieren, wird der Compiler dies für Sie tun.quelle
Beim Testen von OpenGL-Code stellte ich fest, dass die Verwendung des endgültigen Modifikators in einem privaten Feld die Leistung beeinträchtigen kann . Hier ist der Beginn der Klasse, die ich getestet habe:
Mit dieser Methode habe ich die Leistung verschiedener Alternativen getestet, darunter die ShaderInput-Klasse:
Nach der dritten Iteration, bei der die VM aufgewärmt war, erhielt ich diese Zahlen ohne das letzte Schlüsselwort:
Mit dem letzten Schlüsselwort:
Beachten Sie die Zahlen für den ShaderInput-Test.
Es war egal, ob ich die Felder öffentlich oder privat machte.
Übrigens gibt es noch ein paar verwirrende Dinge. Die ShaderInput-Klasse übertrifft alle anderen Varianten, auch mit dem endgültigen Schlüsselwort. Dies ist bemerkenswert, da es sich im Grunde genommen um eine Klasse handelt, die ein Float-Array umschließt, während die anderen direkt testen das Array manipulieren. Muss das herausfinden. Kann etwas mit der fließenden Benutzeroberfläche von ShaderInput zu tun haben.
Außerdem ist System.arrayCopy für kleine Arrays anscheinend etwas langsamer als das einfache Kopieren von Elementen von einem Array in das andere in einer for-Schleife. Und die Verwendung von sun.misc.Unsafe (sowie von direktem java.nio.FloatBuffer, der hier nicht gezeigt wird) ist miserabel.
quelle
Final (zumindest für Mitgliedsvariablen und -parameter) ist mehr für den Menschen als für die Maschine.
Es wird empfohlen, Variablen nach Möglichkeit endgültig zu machen. Ich wünschte, Java hätte "Variablen" standardmäßig endgültig gemacht und ein "veränderbares" Schlüsselwort, um Änderungen zuzulassen. Unveränderliche Klassen führen zu viel besserem Thread-Code, und ein Blick auf eine Klasse mit "final" vor jedem Mitglied zeigt schnell, dass sie unveränderlich ist.
Ein anderer Fall: Ich habe viel Code konvertiert, um @ NonNull / @ Nullable-Annotationen zu verwenden. (Sie können sagen, dass ein Methodenparameter nicht null sein darf, dann kann die IDE Sie an jeder Stelle warnen, an der Sie eine Variable übergeben, die nicht mit @ gekennzeichnet ist NonNull - das Ganze verbreitet sich in einem lächerlichen Ausmaß). Es ist viel einfacher zu beweisen, dass eine Mitgliedsvariable oder ein Elementparameter nicht null sein kann, wenn sie als endgültig markiert ist, da Sie wissen, dass sie nirgendwo anders neu zugewiesen werden.
Mein Vorschlag ist, sich daran zu gewöhnen, standardmäßig final für Mitglieder und Parameter anzuwenden. Es sind nur ein paar Zeichen, aber Sie werden dazu angeregt, Ihren Codierungsstil zu verbessern, wenn nichts anderes.
Final für Methoden oder Klassen ist ein weiteres Konzept, da es eine sehr gültige Form der Wiederverwendung nicht zulässt und dem Leser nicht wirklich viel sagt. Die beste Verwendung ist wahrscheinlich die Art und Weise, wie sie String und die anderen intrinsischen Typen endgültig gemacht haben, sodass Sie sich überall auf konsistentes Verhalten verlassen können - das hat viele Fehler verhindert (obwohl es Zeiten gab, in denen ich es geliebt hätte, String zu erweitern ... oh, das Möglichkeiten)
quelle
Mit final deklarierte Mitglieder sind während des gesamten Programms verfügbar, da diese Mitglieder im Gegensatz zu nicht endgültigen Mitgliedern, wenn sie nicht im Programm verwendet wurden, von Garbage Collector nicht behandelt werden und daher aufgrund einer schlechten Speicherverwaltung Leistungsprobleme verursachen können.
quelle
final
Das Schlüsselwort kann in Java auf fünf Arten verwendet werden.Eine Klasse ist endgültig: Eine Klasse ist endgültig bedeutet, dass wir nicht erweitert werden können, oder Vererbung bedeutet, dass Vererbung nicht möglich ist.
Ähnlich - Ein Objekt ist endgültig: Manchmal ändern wir den internen Status des Objekts nicht, sodass wir in diesem Fall angeben können, dass das Objekt das endgültige Objekt ist. Objekt endgültig bedeutet nicht variabel, auch endgültig.
Sobald die Referenzvariable endgültig ist, kann sie keinem anderen Objekt zugewiesen werden. Kann aber den Inhalt des Objekts ändern, solange seine Felder nicht endgültig sind
quelle
final
bedeutet, sondern ob die Verwendungfinal
Einfluss auf die Leistung hat.