JavaRush /Java-Blog /Random-DE /Kaffeepause Nr. 146. 5 Fehler, die 99 % der Java-Entwickl...

Kaffeepause Nr. 146. 5 Fehler, die 99 % der Java-Entwickler machen. Strings in Java – Innenansicht

Veröffentlicht in der Gruppe Random-DE

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. Kaffeepause Nr. 146.  5 Fehler, die 99 % der Java-Entwickler machen.  Strings in Java – Innenansicht – 1Als 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); //true
System.out.println(Objects.equals(longValue,123)); //false
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));//2022-12-31 08:00:00
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());// the size is 2
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(()->{
            //do something
            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. Kaffeepause Nr. 146.  5 Fehler, die 99 % der Java-Entwickler machen.  Strings in Java – Innenansicht – 2

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);             //true
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);           //false
Interning kann mit der Methode firstName3 intern() verwendet werden , obwohl dies normalerweise nicht bevorzugt wird.
firstName3 = firstName3.intern();                      //Interning
System.out.println(firstName3 == firstName2);          //true
Eine Internierung kann auch auftreten, wenn zwei String-Literale mit dem + -Operator verkettet werden .
String fullName = "Michael Jordan";
System.out.println(fullName == "Michael " + "Jordan");     //true
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.
//After Compilation
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));       //true
System.out.println(firstName3.equals(firstName2));       //true
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));  //true

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);                       //Michael
System.out.println(lastName);                        //Jordan
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);                      //MichaelJordan
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION