final (Java) - final (Java)
In der Programmiersprache Java wird das final Schlüsselwort in mehreren Kontexten verwendet, um eine Entität zu definieren, die nur einmal zugewiesen werden kann.
Sobald eine final Variable zugewiesen wurde, enthält sie immer denselben Wert. Wenn eine final Variable einen Verweis auf ein Objekt enthält, kann der Status des Objekts durch Operationen am Objekt geändert werden. Die Variable verweist jedoch immer auf dasselbe Objekt (diese Eigenschaft von final wird als Nicht-Transitivität bezeichnet ). Dies gilt auch für Arrays, da Arrays Objekte sind. Wenn eine final Variable einen Verweis auf ein Array enthält, können die Komponenten des Arrays durch Operationen am Array geändert werden, die Variable verweist jedoch immer auf dasselbe Array.
Abschlussklassen
Eine letzte Klasse kann nicht unterklassiert werden. Da dies Sicherheits- und Effizienzvorteile bringen kann, sind viele der Java-Standardbibliotheksklassen endgültig, wie z. B. java.lang.System und java.lang.String .
Beispiel:
public final class MyFinalClass {...}
public class ThisIsWrong extends MyFinalClass {...} // forbidden
Endgültige Methoden
Eine endgültige Methode kann von Unterklassen nicht überschrieben oder ausgeblendet werden. Dies wird verwendet, um unerwartetes Verhalten einer Unterklasse zu verhindern, die eine Methode ändert, die für die Funktion oder Konsistenz der Klasse von entscheidender Bedeutung sein kann.
Beispiel:
public class Base
{
public void m1() {...}
public final void m2() {...}
public static void m3() {...}
public static final void m4() {...}
}
public class Derived extends Base
{
public void m1() {...} // OK, overriding Base#m1()
public void m2() {...} // forbidden
public static void m3() {...} // OK, hiding Base#m3()
public static void m4() {...} // forbidden
}
Ein häufiges Missverständnis ist, dass das Deklarieren einer Methode final die Effizienz verbessert, indem der Compiler die Methode direkt dort einfügen kann, wo sie aufgerufen wird (siehe Inline-Erweiterung ). Da die Methode zur Laufzeit geladen wird , können Compiler dies nicht tun. Nur die Laufzeitumgebung und der JIT- Compiler wissen genau, welche Klassen geladen wurden, und nur sie können entscheiden, wann sie inline sind, ob die Methode endgültig ist oder nicht.
Eine Ausnahme bilden Maschinencode-Compiler, die direkt ausführbaren, plattformspezifischen Maschinencode generieren . Bei Verwendung der statischen Verknüpfung kann der Compiler davon ausgehen, dass Methoden und Variablen, die zur Kompilierungszeit berechenbar sind, inline sind.
Letzte Variablen
Eine endgültige Variable kann nur einmal initialisiert werden, entweder über einen Initialisierer oder eine Zuweisungsanweisung. Es muss zum Zeitpunkt der Deklaration nicht initialisiert werden: Dies wird als "leere endgültige" Variable bezeichnet. In jedem Konstruktor der Klasse, in der sie deklariert ist, muss definitiv eine leere endgültige Instanzvariable einer Klasse zugewiesen werden. In ähnlicher Weise muss eine leere endgültige statische Variable in einem statischen Initialisierer der Klasse, in der sie deklariert ist, definitiv zugewiesen werden. Andernfalls tritt in beiden Fällen ein Fehler bei der Kompilierung auf. (Hinweis: Wenn die Variable eine Referenz ist, bedeutet dies, dass die Variable nicht erneut gebunden werden kann, um auf ein anderes Objekt zu verweisen. Das Objekt, auf das sie verweist, ist jedoch weiterhin veränderbar , wenn es ursprünglich veränderbar war.)
Im Gegensatz zum Wert einer Konstanten ist der Wert einer endgültigen Variablen zur Kompilierungszeit nicht unbedingt bekannt. Es wird als bewährte Methode angesehen, Endkonstanten in Großbuchstaben darzustellen und Unterstriche zum Trennen von Wörtern zu verwenden.
Beispiel:
public class Sphere {
// pi is a universal constant, about as constant as anything can be.
public static final double PI = 3.141592653589793;
public final double radius;
public final double xPos;
public final double yPos;
public final double zPos;
Sphere(double x, double y, double z, double r) {
radius = r;
xPos = x;
yPos = y;
zPos = z;
}
[...]
}
Jeder Versuch, ,, oder neu zuzuweisen radius , xPos führt zu einem Kompilierungsfehler. Selbst wenn der Konstruktor keine endgültige Variable festlegt, führt der Versuch, sie außerhalb des Konstruktors festzulegen, zu einem Kompilierungsfehler.
yPoszPos
Um zu veranschaulichen, dass die Endgültigkeit keine Unveränderlichkeit garantiert: Nehmen wir an, wir ersetzen die drei Positionsvariablen durch eine einzige:
public final Position pos;
wo pos ist ein Objekt mit drei Eigenschaften pos.x , pos.y und pos.z . Dann pos kann nicht zugewiesen werden, aber die drei Eigenschaften können, es sei denn, sie sind selbst endgültig.
Wie die vollständige Unveränderlichkeit hat die Verwendung von endgültigen Variablen große Vorteile, insbesondere bei der Optimierung. Zum Beispiel Sphere wird wahrscheinlich eine Funktion ihre Lautstärke zurückgeben; Wenn wir wissen, dass sein Radius konstant ist, können wir uns das berechnete Volumen merken . Wenn wir relativ wenige Sphere s haben und deren Volumen sehr oft benötigen, kann der Leistungsgewinn erheblich sein. Wenn Sie den Radius von a Sphere final festlegen, werden Entwickler und Compiler darüber informiert, dass diese Art der Optimierung in allen Codes möglich ist, die Sphere s verwenden.
Obwohl es den final Grundsatz zu verletzen scheint , ist das Folgende eine rechtliche Erklärung:
for (final SomeObject obj : someList) {
// do something with obj
}
Da die obj-Variable bei jeder Iteration der Schleife den Gültigkeitsbereich verlässt, wird sie tatsächlich bei jeder Iteration neu deklariert, sodass dasselbe Token (dh obj ) zur Darstellung mehrerer Variablen verwendet werden kann.
Endvariablen in verschachtelten Objekten
Endgültige Variablen können verwendet werden, um Bäume unveränderlicher Objekte zu erstellen. Einmal konstruiert, werden sich diese Objekte garantiert nicht mehr ändern. Um dies zu erreichen, darf eine unveränderliche Klasse nur endgültige Felder haben, und diese endgültigen Felder dürfen selbst nur unveränderliche Typen haben. Die primitiven Typen von Java sind unveränderlich, ebenso wie Zeichenfolgen und mehrere andere Klassen.
Wenn die obige Konstruktion verletzt wird, indem ein Objekt im Baum vorhanden ist, das nicht unveränderlich ist, gilt die Erwartung nicht, dass alles, was über die endgültige Variable erreichbar ist, konstant ist. Der folgende Code definiert beispielsweise ein Koordinatensystem, dessen Ursprung immer bei (0, 0) liegen sollte. Der Ursprung wird mit a java.awt.Point obwohl implementiert , und diese Klasse definiert ihre Felder als öffentlich und veränderbar. Dies bedeutet origin , dass dieses Objekt auch dann geändert werden kann, wenn das Objekt über einen Zugriffspfad mit nur endgültigen Variablen erreicht wird, wie der folgende Beispielcode zeigt.
import java.awt.Point;
public class FinalDemo {
static class CoordinateSystem {
private final Point origin = new Point(0, 0);
public Point getOrigin() { return origin; }
}
public static void main(String[] args) {
CoordinateSystem coordinateSystem = new CoordinateSystem();
coordinateSystem.getOrigin().x = 15;
assert coordinateSystem.getOrigin().getX() == 0;
}
}
Der Grund dafür ist, dass das Deklarieren einer Variablen final nur bedeutet, dass diese Variable jederzeit auf dasselbe Objekt verweist. Das Objekt, auf das die Variable zeigt, wird jedoch nicht von dieser endgültigen Variablen beeinflusst. Im obigen Beispiel können die x- und y-Koordinaten des Ursprungs frei geändert werden.
Um diese unerwünschte Situation zu verhindern, müssen häufig alle Felder eines unveränderlichen Objekts endgültig sein und die Typen dieser Felder müssen selbst unveränderlich sein. Dies disqualifiziert java.util.Date und java.awt.Point und mehrere andere Klassen von der Verwendung in solchen unveränderlichen Objekten.
Letzte und innere Klassen
Wenn eine anonyme innere Klasse im Hauptteil einer Methode definiert ist, kann auf alle final im Bereich dieser Methode deklarierten Variablen innerhalb der inneren Klasse zugegriffen werden. Bei skalaren Werten kann sich der Wert der final Variablen nach der Zuweisung nicht ändern. Bei Objektwerten kann sich die Referenz nicht ändern. Auf diese Weise kann der Java-Compiler den Wert der Variablen zur Laufzeit "erfassen" und eine Kopie als Feld in der inneren Klasse speichern. Sobald die äußere Methode beendet und ihr Stapelrahmen entfernt wurde, ist die ursprüngliche Variable verschwunden, aber die private Kopie der inneren Klasse bleibt im eigenen Speicher der Klasse erhalten.
import javax.swing.*;
public class FooGUI {
public static void main(String[] args) {
//initialize GUI components
final JFrame jf = new JFrame("Hello world!"); //allows jf to be accessed from inner class body
jf.add(new JButton("Click me"));
// pack and make visible on the Event-Dispatch Thread
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
jf.pack(); //this would be a compile-time error if jf were not final
jf.setLocationRelativeTo(null);
jf.setVisible(true);
}
});
}
}
Leeres Finale
Das leere Finale , das in Java 1.1 eingeführt wurde, ist eine endgültige Variable, deren Deklaration keinen Initialisierer enthält. Vor Java 1.1 war eine endgültige Variable erforderlich, um einen Initialisierer zu haben. Ein leeres Finale kann per Definition von "final" nur einmal vergeben werden. dh es muss nicht zugewiesen sein, wenn eine Zuweisung erfolgt. Zu diesem Zweck führt ein Java-Compiler eine Flussanalyse durch, um sicherzustellen, dass für jede Zuweisung zu einer leeren endgültigen Variablen die Variable vor der Zuweisung definitiv nicht zugewiesen wird. Andernfalls tritt ein Fehler bei der Kompilierung auf.
final boolean hasTwoDigits;
if (number >= 10 && number < 100) {
hasTwoDigits = true;
}
if (number > -100 && number <= -10) {
hasTwoDigits = true; // compile-error because the final variable might already be assigned.
}
Außerdem muss vor dem Zugriff ein leeres Finale definitiv zugewiesen werden.
final boolean isEven;
if (number % 2 == 0) {
isEven = true;
}
System.out.println(isEven); // compile-error because the variable was not assigned in the else-case.
Beachten Sie jedoch, dass vor dem Zugriff auch eine nicht endgültige lokale Variable definitiv zugewiesen werden muss.
boolean isEven; // *not* final
if (number % 2 == 0) {
isEven = true;
}
System.out.println(isEven); // Same compile-error because the non-final variable was not assigned in the else-case.
C / C ++ Analog der endgültigen Variablen
In C und C ++ ist das analoge Konstrukt das const Schlüsselwort . Dies unterscheidet sich erheblich von final Java, vor allem dadurch, dass es sich um ein Typqualifikationsmerkmal handelt : Es const ist Teil des Typs und nicht nur Teil des Bezeichners (der Variablen). Dies bedeutet auch, dass die Konstanz eines Werts durch Casting (explizite Typkonvertierung) geändert werden kann, in diesem Fall als "Const Casting" bezeichnet. Das Wegwerfen von Konstanz und das anschließende Ändern des Objekts führt jedoch zu einem undefinierten Verhalten, wenn das Objekt ursprünglich deklariert wurde const . Java final ist eine strenge Regel, so dass es unmöglich ist, Code zu kompilieren, der die endgültigen Einschränkungen direkt verletzt oder umgeht. Mithilfe der Reflexion ist es jedoch häufig möglich, die endgültigen Variablen noch zu ändern. Diese Funktion wird hauptsächlich beim Deserialisieren von Objekten mit endgültigen Elementen verwendet.
Da C und C ++ Zeiger und Referenzen direkt verfügbar machen, wird ferner unterschieden, ob der Zeiger selbst konstant ist und ob die Daten, auf die der Zeiger zeigt, konstant sind. Das Anwenden const auf einen Zeiger selbst wie in SomeClass * const ptr bedeutet, dass der Inhalt, auf den verwiesen wird, geändert werden kann, die Referenz selbst jedoch nicht (ohne Umwandlung). Diese Verwendung führt zu einem Verhalten, das das Verhalten einer final Variablenreferenz in Java nachahmt . Wenn Sie dagegen const nur auf die referenzierten Daten anwenden, wie in const SomeClass * ptr , kann der Inhalt nicht geändert werden (ohne Casting), aber die Referenz selbst kann. Sowohl die Referenz als auch der Inhalt, auf den verwiesen wird, können als deklariert werden const .
C # -Analoga für das endgültige Schlüsselwort
C # kann in Bezug auf seine Sprachfunktionen und seine grundlegende Syntax als Java ähnlich angesehen werden: Java hat JVM, C # hat .Net Framework; Java hat Bytecode, C # hat MSIL; Java unterstützt keine Zeiger (realer Speicher), C # ist dasselbe.
In Bezug auf das endgültige Schlüsselwort hat C # zwei verwandte Schlüsselwörter:
- Das entsprechende Schlüsselwort für Methoden und Klassen lautet
sealed - Das entsprechende Schlüsselwort für Variablen lautet
readonly
Beachten Sie, dass ein Hauptunterschied zwischen dem von C / C ++ abgeleiteten Schlüsselwort const und dem C # -Schlüsselwort darin readonly besteht, dass const es zur Kompilierungszeit ausgewertet wird, während readonly es zur Laufzeit ausgewertet wird, und daher einen Ausdruck haben kann, der erst später (zur Laufzeit) berechnet und behoben wird.