5 Fehler, die 99 % der Java-Entwickler machen
Quelle:
Medium In diesem Beitrag erfahren Sie mehr über die häufigsten Fehler, die viele Java-Entwickler machen. Als Java-Programmierer weiß ich, wie schlimm es ist, viel Zeit damit zu verbringen, Fehler in Ihrem Code zu beheben. Manchmal dauert dies mehrere Stunden. Viele Fehler entstehen jedoch dadurch, dass der Entwickler grundlegende Regeln ignoriert – das heißt, es handelt sich um Fehler auf sehr niedriger Ebene. Heute schauen wir uns einige häufige Codierungsfehler an und erklären dann, wie man sie behebt. Ich hoffe, dass dies Ihnen hilft, Probleme bei Ihrer täglichen Arbeit zu vermeiden.
Vergleichen von Objekten mit Objects.equals
Ich gehe davon aus, dass Sie mit dieser Methode vertraut sind. Viele Entwickler verwenden es häufig. Diese in JDK 7 eingeführte Technik hilft Ihnen, Objekte schnell zu vergleichen und lästige Nullzeigerprüfungen effektiv zu vermeiden. Diese Methode wird jedoch manchmal falsch angewendet. Folgendes meine ich:
Long longValue = 123L;
System.out.println(longValue==123);
System.out.println(Objects.equals(longValue,123));
Warum würde das Ersetzen
von == durch
Objects.equals() das falsche Ergebnis liefern? Dies liegt daran, dass der
==- Compiler den zugrunde liegenden Datentyp erhält, der dem Verpackungstyp
longValue entspricht , und ihn dann mit diesem zugrunde liegenden Datentyp vergleicht. Dies entspricht der automatischen Konvertierung von Konstanten durch den Compiler in den zugrunde liegenden Vergleichsdatentyp. Nach Verwendung der Methode
Objects.equals() ist der standardmäßige Basisdatentyp der Compilerkonstante
int . Unten finden Sie den Quellcode
für Objects.equals() , wobei
a.equals(b) Long.equals() verwendet und den Typ des Objekts bestimmt.
Dies geschieht, weil der Compiler davon ausgegangen ist, dass die Konstante vom Typ int ist , sodass das Ergebnis des Vergleichs falsch sein muss.
public static boolean equals(Object a, Object b) {
return (a == b) || (a != null && a.equals(b));
}
public boolean equals(Object obj) {
if (obj instanceof Long) {
return value == ((Long)obj).longValue();
}
return false;
}
Wenn man den Grund kennt, ist es sehr einfach, den Fehler zu beheben. Deklarieren Sie einfach den Datentyp der Konstanten, z. B.
Objects.equals(longValue,123L) . Die oben genannten Probleme treten nicht auf, wenn die Logik streng ist. Was wir tun müssen, ist, klare Programmierregeln zu befolgen.
Falsches Datumsformat
In der alltäglichen Entwicklung muss man oft das Datum ändern, aber viele Leute verwenden das falsche Format, was zu unerwarteten Dingen führt. Hier ist ein Beispiel:
Instant instant = Instant.parse("2021-12-31T00:00:00.00Z");
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("YYYY-MM-dd HH:mm:ss")
.withZone(ZoneId.systemDefault());
System.out.println(formatter.format(instant));
Dabei wird das Format
JJJJ-MM-TT verwendet , um das Datum von 2021 auf 2022 zu ändern. Das solltest du nicht tun. Warum? Dies liegt daran, dass
das Java DateTimeFormatter- Muster „YYYY“ auf dem ISO-8601-Standard basiert, der das Jahr als Donnerstag jeder Woche definiert. Da der 31. Dezember 2021 jedoch auf einen Freitag fiel, wird im Programm fälschlicherweise das Jahr 2022 angegeben. Um dies zu vermeiden, müssen Sie zum Formatieren des Datums das Format
jjjj-MM-tt verwenden . Dieser Fehler tritt selten auf, nur zu Beginn des neuen Jahres. Aber in meinem Unternehmen kam es zu einem Produktionsausfall.
Verwenden von ThreadLocal in ThreadPool
Wenn Sie
eine ThreadLocal-Variable erstellen , erstellt ein Thread, der auf diese Variable zugreift, eine Thread-lokale Variable. Auf diese Weise können Sie Thread-Sicherheitsprobleme vermeiden.
Wenn Sie ThreadLocal jedoch für
einen Thread-Pool verwenden , müssen Sie vorsichtig sein. Ihr Code kann zu unerwarteten Ergebnissen führen. Nehmen wir als einfaches Beispiel an, wir haben eine E-Commerce-Plattform und Benutzer müssen eine E-Mail senden, um den abgeschlossenen Kauf von Produkten zu bestätigen.
private ThreadLocal<User> currentUser = ThreadLocal.withInitial(() -> null);
private ExecutorService executorService = Executors.newFixedThreadPool(4);
public void executor() {
executorService.submit(()->{
User user = currentUser.get();
Integer userId = user.getId();
sendEmail(userId);
});
}
Wenn wir ThreadLocal zum Speichern von Benutzerinformationen verwenden , wird ein versteckter Fehler angezeigt. Da ein Thread-Pool verwendet wird und Threads wiederverwendet werden können, werden bei Verwendung
von ThreadLocal zum Abrufen von Benutzerinformationen möglicherweise fälschlicherweise die Informationen einer anderen Person angezeigt. Um dieses Problem zu lösen, sollten Sie Sitzungen verwenden.
Verwenden Sie HashSet, um doppelte Daten zu entfernen
Beim Codieren ist häufig eine Deduplizierung erforderlich. Wenn man an Deduplizierung denkt, denken viele Leute zuerst an die Verwendung
eines HashSets . Allerdings kann die unvorsichtige Verwendung
von HashSet dazu führen, dass die Deduplizierung fehlschlägt.
User user1 = new User();
user1.setUsername("test");
User user2 = new User();
user2.setUsername("test");
List<User> users = Arrays.asList(user1, user2);
HashSet<User> sets = new HashSet<>(users);
System.out.println(sets.size());
Einige aufmerksame Leser dürften den Grund für das Scheitern erraten können.
HashSet verwendet einen Hash-Code, um auf die Hash-Tabelle zuzugreifen, und verwendet die Methode „equals“, um zu bestimmen, ob Objekte gleich sind. Wenn das benutzerdefinierte Objekt die Hashcode-Methode und
die Equals- Methode nicht überschreibt , werden standardmäßig die Hashcode-Methode und die
Equals- Methode des übergeordneten Objekts verwendet. Dies führt dazu, dass
HashSet davon ausgeht, dass es sich um zwei verschiedene Objekte handelt, was dazu führt, dass die Deduplizierung fehlschlägt.
Eliminieren eines „gefressenen“ Pool-Threads
ExecutorService executorService = Executors.newFixedThreadPool(1);
executorService.submit(()->{
double result = 10/0;
});
Der obige Code simuliert ein Szenario, in dem eine Ausnahme im Thread-Pool ausgelöst wird.
Geschäftscode muss verschiedene Situationen annehmen, daher ist es sehr wahrscheinlich, dass er aus irgendeinem Grund eine RuntimeException auslöst . Wenn hier jedoch keine spezielle Behandlung erfolgt, wird diese Ausnahme vom Thread-Pool „gefressen“. Und Sie haben nicht einmal die Möglichkeit, die Ursache der Ausnahme zu überprüfen. Daher ist es am besten, Ausnahmen im Prozesspool abzufangen.
Strings in Java – Innenansicht
Quelle:
Medium Der Autor dieses Artikels hat beschlossen, einen detaillierten Blick auf die Erstellung, Funktionalität und Merkmale von Strings in Java zu werfen.
Schaffung
Ein String kann in Java auf zwei verschiedene Arten erstellt werden: implizit als String-Literal und explizit mit dem Schlüsselwort
new . String-Literale sind in doppelte Anführungszeichen eingeschlossene Zeichen.
String literal = "Michael Jordan";
String object = new String("Michael Jordan");
Obwohl beide Deklarationen ein String-Objekt erstellen, gibt es einen Unterschied darin, wie sich diese beiden Objekte im Heap-Speicher befinden.
Interne Vertretung
Bisher wurden Zeichenfolgen in der Form char[] gespeichert , was bedeutete, dass jedes Zeichen ein separates Element im Zeichenarray war. Da sie im
UTF-16- Zeichenkodierungsformat dargestellt wurden , bedeutete dies, dass jedes Zeichen zwei Bytes Speicher belegte. Dies ist nicht ganz richtig, da Nutzungsstatistiken zeigen, dass die meisten String-Objekte nur aus
Latin-1 -Zeichen bestehen . Latin-1-Zeichen können mit einem einzigen Byte Speicher dargestellt werden, wodurch die Speichernutzung erheblich reduziert werden kann – um bis zu 50 %. Als Teil der JDK 9-Version basierend auf
JEP 254 wurde eine neue interne String-Funktion namens Compact Strings implementiert. In dieser Version wurde
char[] in byte[] geändert und ein Encoder-Flag-Feld hinzugefügt, um die verwendete Codierung darzustellen (Latin-1 oder UTF-16). Danach erfolgt die Codierung basierend auf dem Inhalt der Zeichenfolge. Wenn der Wert nur Latin-1-Zeichen enthält, wird die Latin-1-Kodierung (die
StringLatin1- Klasse ) oder die UTF-16-Kodierung (die
StringUTF16 -Klasse ) verwendet.
Speicherzuweisung
Wie bereits erwähnt, gibt es einen Unterschied in der Art und Weise, wie Speicher für diese Objekte auf dem Heap zugewiesen wird. Die Verwendung des expliziten Schlüsselworts new ist ziemlich einfach, da die JVM Speicher für die Variable auf dem Heap erstellt und zuweist. Daher folgt bei der Verwendung eines String-Literals ein Prozess, der Internierung genannt wird. String-Interning ist der Prozess, bei dem Strings in einen Pool gelegt werden. Es verwendet eine Methode zum Speichern nur einer Kopie jedes einzelnen Zeichenfolgenwerts, der unveränderlich sein muss. Einzelne Werte werden im String Intern-Pool gespeichert. Dieser Pool ist ein
Hashtable- Speicher , der einen Verweis auf jedes mithilfe von Literalen erstellte Zeichenfolgenobjekt und seinen Hash speichert. Obwohl sich der Zeichenfolgewert auf dem Heap befindet, kann seine Referenz im internen Pool gefunden werden. Dies kann anhand des folgenden Experiments leicht überprüft werden. Hier haben wir zwei Variablen mit demselben Wert:
String firstName1 = "Michael";
String firstName2 = "Michael";
System.out.println(firstName1 == firstName2);
Wenn die JVM während der Codeausführung auf
firstName1 trifft , sucht sie nach dem String-Wert im internen String-Pool
Michael . Wenn es nicht gefunden werden kann, wird ein neuer Eintrag für das Objekt im internen Pool erstellt. Wenn die Ausführung
„firstName2“ erreicht , wird der Prozess erneut wiederholt und dieses Mal kann der Wert im Pool basierend auf der Variablen
„firstName1“ gefunden werden . Auf diese Weise wird derselbe Link zurückgegeben, anstatt einen neuen Eintrag zu duplizieren und zu erstellen. Daher ist die Gleichheitsbedingung erfüllt. Wenn andererseits eine Variable mit dem Wert
Michael mit dem Schlüsselwort new erstellt wird, findet keine Internierung statt und die Gleichheitsbedingung ist nicht erfüllt.
String firstName3 = new String("Michael");
System.out.println(firstName3 == firstName2);
Interning kann mit der Methode
firstName3 intern() verwendet werden , obwohl dies normalerweise nicht bevorzugt wird.
firstName3 = firstName3.intern();
System.out.println(firstName3 == firstName2);
Eine Internierung kann auch auftreten, wenn zwei String-Literale mit dem
+ -Operator verkettet werden .
String fullName = "Michael Jordan";
System.out.println(fullName == "Michael " + "Jordan");
Hier sehen wir, dass der Compiler zur Kompilierungszeit beide Literale hinzufügt und den
+ -Operator aus dem Ausdruck entfernt, um eine einzelne Zeichenfolge zu bilden, wie unten gezeigt. Zur Laufzeit werden sowohl
fullName als auch das „added literal“ interniert und die Gleichheitsbedingung erfüllt.
System.out.println(fullName == "Michael Jordan");
Gleichwertigkeit
Aus den obigen Experimenten können Sie ersehen, dass standardmäßig nur String-Literale interniert werden. Allerdings wird eine Java-Anwendung sicherlich nicht nur String-Literale haben, da sie Strings aus verschiedenen Quellen empfangen kann. Daher wird die Verwendung des Gleichheitsoperators nicht empfohlen und kann zu unerwünschten Ergebnissen führen. Gleichheitstests sollten nur mit der Methode
equals durchgeführt werden . Es führt eine Gleichheit basierend auf dem Wert der Zeichenfolge durch und nicht auf der Speicheradresse, an der sie gespeichert ist.
System.out.println(firstName1.equals(firstName2));
System.out.println(firstName3.equals(firstName2));
Es gibt auch eine leicht modifizierte Version der Methode equal namens
equalIgnoreCase . Dies kann nützlich sein, wenn die Groß-/Kleinschreibung nicht beachtet werden soll.
String firstName4 = "miCHAEL";
System.out.println(firstName4.equalsIgnoreCase(firstName1));
Unveränderlichkeit
Zeichenfolgen sind unveränderlich, was bedeutet, dass ihr interner Zustand nach ihrer Erstellung nicht mehr geändert werden kann. Sie können den Wert einer Variablen ändern, nicht jedoch den Wert der Zeichenfolge selbst. Jede Methode der
String- Klasse , die sich mit der Manipulation eines Objekts befasst (z. B.
concat ,
substring ), gibt eine neue Kopie des Werts zurück, anstatt den vorhandenen Wert zu aktualisieren.
String firstName = "Michael";
String lastName = "Jordan";
firstName.concat(lastName);
System.out.println(firstName);
System.out.println(lastName);
Wie Sie sehen, werden an keiner der Variablen Änderungen vorgenommen: weder
firstName noch
lastName .
String- Klassenmethoden ändern den internen Status nicht, sie erstellen eine neue Kopie des Ergebnisses und geben das Ergebnis wie unten gezeigt zurück.
firstName = firstName.concat(lastName);
System.out.println(firstName);
GO TO FULL VERSION