Java: Dinge, die einem erfahrenen Entwickler neugierig erscheinen mögen

Gute Tageszeit!

Der Artikel wurde im Anschluss an die Veröffentlichung „Dinge, die Sie [vielleicht] nicht über Java wussten“ von einem anderen Autor verfasst, den ich als „für Anfänger“ einstufen würde. Als ich es las und kommentierte, stellte ich fest, dass es eine Reihe interessanter Dinge gibt, die ich gelernt habe und die ich bereits seit mehr als einem Jahr in Java programmiere. Vielleicht scheinen diese Dinge jemand anderem neugierig zu sein.

Die Tatsachen, die aus meiner Sicht für Anfänger nützlich sein könnten, habe ich in den "Spoilern" beseitigt. Einige Dinge könnten für erfahrene noch interessant sein. Zum Beispiel wusste ich selbst erst zum Zeitpunkt des Schreibens, dass Boolean.hashCode (true) == 1231 und Boolean.hashCode (false) == 1237.

Für Anfänger
  • Boolean.hashCode (true) == 1231
  • Boolean.hashCode (false) == 1237
  • Float.hashCode (Wert) == Float.floatToIntBits (Wert)
  • Double.hashCode (value) - xor des ersten und zweiten 32-Bit-Halbworts Double.doubleToLongBits (value)

Object.hashCode () ist nicht länger die Adresse eines Objekts im Speicher


Haftungsausschluss: Dies ist ein JVM-Detail von Oracle (HotSpot).

Es war einmal so.
Aus jdk1.2.1 / docs / api / java / lang / Object.html # hashCode ():
Die von der Klasse Object definierte hashCode-Methode gibt, soweit dies sinnvoll ist, unterschiedliche Ganzzahlen für unterschiedliche Objekte zurück. (Dies wird normalerweise durch Konvertieren der internen Adresse des Objekts in eine Ganzzahl implementiert. Diese Implementierungstechnik wird jedoch von der Programmiersprache JavaTM nicht benötigt.)

Dann lehnten sie es ab. Dies ist, was Javadoc für JDK 12 sagt.

vladimir_dolzhenko schlug vor, dass altes Verhalten mit -XX wiederhergestellt werden kann: hashCode = 4. Und die Verhaltensänderung selbst war fast von Java 1.2-Version.

Integer.valueOf (15) == Integer.valueOf (15); Integer.valueOf (128)! = Integer.valueOf (128)


Haftungsausschluss: Dies ist ein Teil von jls .

Es ist klar, dass beim Vergleich zweier Wrapper mit dem Operator == (! =) Kein Autoboxing auftritt. Im Allgemeinen ist es die erste Gleichheit, die verwirrt. Fakt ist, dass für Integer-Werte i: -129 <i <128 Integer-Wrapper-Objekte zwischengespeichert werden. Daher erstellt Integer.valueOf (i) für i aus diesem Bereich nicht jedes Mal ein neues Objekt, sondern gibt ein bereits erstelltes zurück. Für i, die nicht in diesen Bereich fallen, erstellt Integer.valueOf (i) immer ein neues Objekt. Wenn Sie nicht genau überwachen, was genau und wie genau es verglichen wird, können Sie Code schreiben, der zu funktionieren scheint und sogar von Tests abgedeckt wird, aber gleichzeitig einen solchen "Rechen" enthält.

In jvm von Oracle (HotSpot) kann die obere Grenze des Cachings über die Eigenschaft "java.lang.Integer.IntegerCache.high" geändert werden.

In einigen Fällen werden die Werte von primitiven oder endgültigen statischen Feldern einer anderen Klasse zur Kompilierungszeit aufgelöst


Das klingt verwirrend und die Aussage ist etwas lang. Die Bedeutung ist dies. Wenn wir eine Klasse haben, die Konstanten primitiver Typen oder Zeichenfolgen als endgültige statische Felder mit sofortiger Initialisierung definiert,
class AnotherClass { public final static String CASE_1 = "case_1"; public final static String CASE_2 = "case_2"; } 

wenn in anderen Klassen verwendet,
 class TheClass { // ... public static int getCaseNumber(String caseName) { switch (caseName) { case AnotherClass.CASE_1: return 1; case AnotherClass.CASE_2: return 2; default: throw new IllegalArgumentException("value of the argument caseName does not belong to the allowed value set"); } } } 

Die Werte dieser Konstanten ("case_1", "case_2") werden zur Kompilierzeit aufgelöst. Und sie werden als Werte und nicht als Links in den Code eingefügt. Das heißt, wenn wir solche Konstanten aus der Bibliothek verwenden und dann eine neue Version der Bibliothek erhalten, in der sich die Werte der Konstanten geändert haben, sollten wir das Projekt neu kompilieren. Andernfalls werden möglicherweise weiterhin alte Konstantenwerte im Code verwendet.

Dieses Verhalten wird an allen Stellen beobachtet, an denen konstante Ausdrücke verwendet werden müssen (z. B. switch / case) oder der Compiler Ausdrücke in Konstanten konvertieren darf, und er kann dies tun.

Diese Felder können nicht in konstanten Ausdrücken verwendet werden, sobald die sofortige Initialisierung durch Übertragen der Initialisierung in den statischen Block entfernt wird.

Für Anfänger

Unter bestimmten Umständen wird der Garbage Collector möglicherweise nie ausgeführt.


Infolgedessen wird kein finalize () gestartet. Aus diesem Grund sollten Sie keinen Code schreiben, der auf der Tatsache beruht, dass finalize () immer funktioniert. Ja, und wenn das Objekt vor dem Ende des Programms in den Müll gelangt ist, wird es höchstwahrscheinlich nicht vom Sammler eingesammelt.

Die finalize () -Methode für ein bestimmtes Objekt kann nur einmal aufgerufen werden.


In finalize () können wir das Objekt wieder sichtbar machen und der Garbage Collector wird es dieses Mal nicht "entfernen". Wenn dieses Objekt wieder in den Müll fällt, wird es "kompiliert", ohne finalize () aufzurufen. Wenn in finalize () eine Ausnahme ausgelöst wird und das Objekt für niemanden sichtbar ist, wird es „zusammengesetzt“. Finalize () wird nicht erneut aufgerufen.

Der Stream, in dem finalize () aufgerufen wird, ist nicht im Voraus bekannt


Es wird nur garantiert, dass dieser Thread frei von Sperren ist, die vom Hauptprogramm angezeigt werden.

Das Vorhandensein einer überschriebenen finalize () -Methode für Objekte verlangsamt den Garbage Collection-Prozess


Was auf der Oberfläche liegt, ist die Notwendigkeit, die Barrierefreiheit von Objekten zu überprüfen - einmal vor dem Aufruf von finalize (), einmal in einer der folgenden Garbage Collection-Läufe.

Es ist wirklich schwer, Deadlocks beim Finalisieren zu bekämpfen ()


In nicht-trivial finalize () können Sperren erforderlich sein, die angesichts der oben beschriebenen Besonderheiten nur sehr schwer zu debuggen sind.

Object.finalize () seit Version 9 von Java ist als veraltet markiert!


Was angesichts der oben beschriebenen Besonderheiten nicht überraschend ist.

klassische Lazy-Singleton-Initialisierung: Doppelte Sperrung erforderlich


Zu diesem Thema gibt es ein Missverständnis, dass der folgende Ansatz (Double-Check-Idiom), der sehr logisch aussieht, immer funktioniert:
 public class UnsafeDCLFactory { private Singleton instance; public Singleton get() { if (instance == null) { // read 1, check 1 synchronized (this) { if (instance == null) { // read 2, check 2 instance = new Singleton(); } } } return instance; // read 3 } } 

Wir prüfen, ob das Objekt erstellt wurde (lesen Sie 1, überprüfen Sie 1). Wenn ja, dann senden Sie es zurück. Wenn nicht, setzen Sie die Sperre, stellen Sie sicher, dass das Objekt nicht erstellt wurde, erstellen Sie das Objekt (die Sperre wird entfernt) und geben Sie das Objekt zurück.

Der Ansatz funktioniert aus den folgenden Gründen nicht. (read 1, check 1) und (read 3) sind nicht synchron. Gemäß dem Konzept des Java-Speichermodells sind Änderungen, die in einem anderen Thread vorgenommen wurden, für unseren Thread möglicherweise erst nach der Synchronisierung sichtbar. Danke mk2 für den Kommentar, hier ist die korrekte Beschreibung des Problems:
Ja, read1 und read3 sind nicht synchronisiert, aber das Problem liegt nicht in einem anderen Thread. Und die Tatsache, dass nicht synchronisierte Ablesungen neu angeordnet werden können, d.h. read1! = null, aber read3 == null. Gleichzeitig können wir aufgrund von "instance = new Singleton ();" einen Verweis auf das Objekt erhalten, bevor es vollständig erstellt wurde, und dies ist wirklich ein Synchronisationsproblem mit einem anderen Thread, aber nicht read1 und read3, sondern read3 und access Mitglieder zu Instanz.
Es wird entweder durch Hinzufügen einer Synchronisation während des Lesens oder durch Markieren der Variable, in der sich die Verknüpfung zum Singleton befindet, als flüchtig behandelt. (Die Lösung mit Volatile funktioniert nur mit Java 5+. Davor hatte Java in dieser Situation ein Speichermodell mit Unsicherheit.) Hier ist eine funktionierende Version (mit zusätzlicher Optimierung - die lokale Variable "res" wurde hinzugefügt, um die Anzahl der Lesevorgänge aus dem volatile-Feld zu verringern).
 public class SafeLocalDCLFactory { private volatile Singleton instance; public Singleton getInstance() { Singleton res = instance; // read 1 if (res == null) { // check 1 synchronized (this) { res = instance; // read 2 if (res == null) { // check 2 res = new Singleton(); instance = res; } } } return res; } } 

Der Code stammt von hier aus der Site von Alexei Shipilev. Weitere Details zu diesem Problem finden Sie darauf.

"Initialization-on-Demand Holder Idiom" - eine sehr schöne "faule" Initialisierung von Singleton


Java initialisiert Klassen (Klassenobjekte) nur nach Bedarf und natürlich nur einmal. Und das können Sie nutzen! Der Initialisierungs-On-Demand-Inhaber-Idiom-Mechanismus macht genau das. (Der Code ist von hier .)
 public class Something { private Something() {} private static class LazyHolder { static final Something INSTANCE = new Something(); } public static Something getInstance() { return LazyHolder.INSTANCE; } } 

Die LazyHolder-Klasse wird nur beim ersten Aufruf von Something.getInstance () initialisiert. Jvm stellt sicher, dass dies nur einmal und darüber hinaus sehr effizient geschieht - wenn die Klasse bereits initialisiert ist, entsteht kein Overhead. Dementsprechend wird LazyHolder.INSTANCE auch einmalig "faul" und threadsicher initialisiert.
Stück spec über Overhead
Wenn diese Initialisierungsprozedur normal abgeschlossen wird und das Class-Objekt vollständig initialisiert und einsatzbereit ist, ist der Aufruf der Initialisierungsprozedur nicht mehr erforderlich und kann aus dem Code entfernt werden, z. B. durch Patchen oder anderweitige Neuerstellung des Codes .
Quelle

Singletones gelten im Allgemeinen nicht als Best Practice.

Das Material ist noch nicht vorbei. Wenn also die Hände „reichen“ und das, was bereits geschrieben wurde, gefragt ist, werde ich irgendwie mehr zu diesem Thema schreiben.

Vielen Dank für die konstruktiven Kommentare. Mehrere Stellen im Artikel wurden dank sergey-gornostaev , vladimir_dolzhenko , OlehKurpiak , mk2 erweitert .

Source: https://habr.com/ru/post/de482950/


All Articles