Unveränderliche Strings in der Java-Programmierung

JavaJavaBeginner
Jetzt üben

💡 Dieser Artikel wurde von AI-Assistenten übersetzt. Um die englische Version anzuzeigen, können Sie hier klicken

Einleitung

In diesem Lab lernen Sie die Vorteile der Verwendung von unveränderlichen Strings (immutable Strings) in der Java-Programmierung kennen. Sie werden verstehen, warum Strings in Java standardmäßig unveränderlich sind und wie diese Eigenschaft die Gesamtleistung und Sicherheit Ihrer Anwendungen verbessern kann. Am Ende dieses Labs werden Sie in der Lage sein, unveränderliche Strings in Ihren Java-Programmen zu erstellen und effektiv zu nutzen.

Erstellen einer Java-Projektdatenbank

In diesem Schritt erstellen Sie eine neue Java-Datei namens ImmutableStringDemo.java in Ihrem ~/project-Verzeichnis. Diese Datei wird den gesamten Java-Code für dieses Lab enthalten.

Stellen Sie zunächst sicher, dass Sie sich im Verzeichnis ~/project befinden. Sie können Ihr aktuelles Verzeichnis mit dem Befehl pwd überprüfen:

pwd

Sie sollten eine Ausgabe ähnlich dieser sehen:

/home/labex/project

Erstellen Sie nun die Datei ImmutableStringDemo.java mit dem Befehl touch:

touch ImmutableStringDemo.java

Nachdem Sie die Datei erstellt haben, können Sie sie im WebIDE-Editor öffnen, um mit dem Schreiben Ihres Java-Codes zu beginnen.

String-Unveränderlichkeit und der String-Pool verstehen

In Java sind String-Objekte unveränderlich (immutable), was bedeutet, dass ihr Wert nach ihrer Erstellung nicht mehr geändert werden kann. Diese Designentscheidung bietet mehrere Vorteile, darunter Sicherheit, Thread-Sicherheit und Leistungsoptimierungen durch den String-Pool.

Der String-Pool ist ein spezieller Speicherbereich innerhalb des Heaps, der String-Literale speichert. Wenn Sie ein String-Literal erstellen, prüft Java zunächst, ob ein identischer String bereits im Pool vorhanden ist. Wenn ja, wird die Referenz auf den vorhandenen String zurückgegeben, was Speicher spart. Andernfalls wird ein neuer String im Pool erstellt und seine Referenz zurückgegeben.

Fügen wir die grundlegende Struktur einer Java-Klasse hinzu und demonstrieren die String-Erstellung. Öffnen Sie ImmutableStringDemo.java im WebIDE und fügen Sie den folgenden Code hinzu:

// ~/project/ImmutableStringDemo.java
public class ImmutableStringDemo {

    public static void main(String[] args) {
        // Erstellen eines String-Literals
        String name = "LabexUser";
        System.out.println("Initial name: " + name);

        // Erstellen von Strings, die möglicherweise den String-Pool verwenden
        String str1 = "Hello";
        String str2 = "Hello"; // Dies wird wahrscheinlich auf dasselbe Objekt wie str1 im String-Pool verweisen

        System.out.println("str1: " + str1);
        System.out.println("str2: " + str2);
        System.out.println("str1 == str2: " + (str1 == str2)); // Prüft, ob sie auf dasselbe Objekt verweisen

        // Erstellen eines Strings mit dem Schlüsselwort 'new'
        // Dies erstellt immer ein neues Objekt im Heap, auch wenn das Literal im Pool vorhanden ist
        String str3 = new String("Hello");
        System.out.println("str3: " + str3);
        System.out.println("str1 == str3: " + (str1 == str3)); // Prüft, ob sie auf dasselbe Objekt verweisen
    }
}

Speichern Sie die Datei nach dem Hinzufügen des Codes.

Kompilieren und führen Sie nun das Java-Programm aus, um die Ausgabe zu sehen. Führen Sie im Terminal die folgenden Befehle aus:

javac ImmutableStringDemo.java
java ImmutableStringDemo

Sie sollten eine Ausgabe ähnlich dieser sehen:

Initial name: LabexUser
str1: Hello
str2: Hello
str1 == str2: true
str3: Hello
str1 == str3: false

Diese Ausgabe zeigt, dass str1 und str2 auf dasselbe Objekt im String-Pool verweisen, während str3 (erstellt mit new String()) ein separates Objekt im Heap ist, obwohl sein Inhalt identisch ist.

Unveränderlichkeit für Sicherheit und Thread-Sicherheit erzwingen

Unveränderlichkeit (Immutability) ist in Java entscheidend für Sicherheit und Thread-Sicherheit. Wenn ein String unveränderlich ist, kann sein Wert nach der Erstellung von keinem Teil des Programms geändert werden. Dies verhindert, dass bösartiger Code sensible Daten, die in Strings gespeichert sind, wie Passwörter oder URLs, verändert.

Darüber hinaus sind unveränderliche Objekte von Natur aus Thread-sicher. Mehrere Threads können gleichzeitig auf einen unveränderlichen String zugreifen, ohne dass eine externe Synchronisation erforderlich ist, da kein Risiko besteht, dass ein Thread den String modifiziert, während ein anderer ihn liest.

Lassen Sie uns dies demonstrieren, indem wir das Schlüsselwort final zu einem String hinzufügen und ein einfaches Thread-sicheres Beispiel erstellen.

Ändern Sie Ihre Datei ImmutableStringDemo.java wie folgt. Wir deklarieren einen final String auf Klassenebene, um ihn für unsere ThreadSafeTask-Klasse zugänglich zu machen.

// ~/project/ImmutableStringDemo.java
import java.util.HashMap;

public class ImmutableStringDemo {

    // Deklarieren eines finalen Strings für Sicherheit und Thread-Sicherheit
    private static final String SECURE_PASSWORD = "MySecurePassword123";

    // Innere Klasse zur Demonstration der Thread-Sicherheit
    static class ThreadSafeTask implements Runnable {
        private final String password; // Das Passwort wird als unveränderlicher String übergeben

        public ThreadSafeTask(String password) {
            this.password = password;
        }

        @Override
        public void run() {
            System.out.println("Thread " + Thread.currentThread().getName() + " accessing password: " + password);
            // Simulieren einer Arbeit
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }

    public static void main(String[] args) {
        // ... (vorheriger Code für name, str1, str2, str3 bleibt hier) ...
        String name = "LabexUser";
        System.out.println("Initial name: " + name);

        String str1 = "Hello";
        String str2 = "Hello";
        System.out.println("str1: " + str1);
        System.out.println("str2: " + str2);
        System.out.println("str1 == str2: " + (str1 == str2));

        String str3 = new String("Hello");
        System.out.println("str3: " + str3);
        System.out.println("str1 == str3: " + (str1 == str3));

        System.out.println("\n--- Demonstrating Security and Thread Safety ---");

        // Verwendung des finalen Strings
        System.out.println("Secure Password: " + SECURE_PASSWORD);

        // Demonstration der Thread-Sicherheit mit dem unveränderlichen Passwort
        for (int i = 0; i < 3; i++) {
            Thread thread = new Thread(new ThreadSafeTask(SECURE_PASSWORD), "Thread-" + (i + 1));
            thread.start();
        }

        // Warten, bis die Threads beendet sind (optional, für sauberere Ausgabe)
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

Hinweis: Das Schlüsselwort final stellt sicher, dass die Variable SECURE_PASSWORD nur einmal zugewiesen werden kann. Da String selbst unveränderlich ist, macht dies den Inhalt von SECURE_PASSWORD unveränderlich. Die Klasse ThreadSafeTask greift sicher von mehreren Threads auf diesen unveränderlichen String zu.

Speichern Sie die Datei. Kompilieren und führen Sie das Programm dann erneut aus:

javac ImmutableStringDemo.java
java ImmutableStringDemo

Sie sollten eine Ausgabe ähnlich dieser sehen, wobei die Meldungen zum Thread-Zugriff in nicht-deterministischer Reihenfolge erscheinen:

Initial name: LabexUser
str1: Hello
str2: Hello
str1 == str2: true
str3: Hello
str1 == str3: false

--- Demonstrating Security and Thread Safety ---
Secure Password: MySecurePassword123
Thread Thread-1 accessing password: MySecurePassword123
Thread Thread-2 accessing password: MySecurePassword123
Thread Thread-3 accessing password: MySecurePassword123

Unveränderlichkeit für Hashcode-Caching in Collections nutzen

Die Unveränderlichkeit von Strings macht sie ideal für die Verwendung als Schlüssel in Hash-basierten Collections wie HashMap und HashSet. Da sich der Wert eines Strings niemals ändert, kann sein hashCode() einmal berechnet und zwischengespeichert werden. Nachfolgende Aufrufe von hashCode() geben den zwischengespeicherten Wert zurück, was zu erheblichen Leistungsverbesserungen führt, insbesondere wenn Strings häufig als Schlüssel verwendet werden.

Wären Strings veränderlich, könnte sich ihr Hashcode ändern, nachdem sie in eine HashMap eingefügt wurden, was es unmöglich macht, das Objekt mit dem ursprünglichen Hashcode abzurufen.

Fügen wir dem ImmutableStringDemo.java Code hinzu, um die String-Verwendung in einer HashMap zu demonstrieren.

Fügen Sie den folgenden Code in die main-Methode Ihrer ImmutableStringDemo.java-Datei ein, nach der Demonstration der Thread-Sicherheit:

// ~/project/ImmutableStringDemo.java
import java.util.HashMap; // Stellen Sie sicher, dass dieser Import am Anfang Ihrer Datei steht

public class ImmutableStringDemo {

    private static final String SECURE_PASSWORD = "MySecurePassword123";

    static class ThreadSafeTask implements Runnable {
        private final String password;

        public ThreadSafeTask(String password) {
            this.password = password;
        }

        @Override
        public void run() {
            System.out.println("Thread " + Thread.currentThread().getName() + " accessing password: " + password);
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }

    public static void main(String[] args) {
        // ... (vorheriger Code für name, str1, str2, str3 und Thread-Sicherheit bleibt hier) ...
        String name = "LabexUser";
        System.out.println("Initial name: " + name);

        String str1 = "Hello";
        String str2 = "Hello";
        System.out.println("str1: " + str1);
        System.out.println("str2: " + str2);
        System.out.println("str1 == str2: " + (str1 == str2));

        String str3 = new String("Hello");
        System.out.println("str3: " + str3);
        System.out.println("str1 == str3: " + (str1 == str3));

        System.out.println("\n--- Demonstrating Security and Thread Safety ---");
        System.out.println("Secure Password: " + SECURE_PASSWORD);

        for (int i = 0; i < 3; i++) {
            Thread thread = new Thread(new ThreadSafeTask(SECURE_PASSWORD), "Thread-" + (i + 1));
            thread.start();
        }
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }

        System.out.println("\n--- Demonstrating Hashcode Caching ---");

        HashMap<String, Integer> studentScores = new HashMap<>();

        String studentName1 = "Alice";
        String studentName2 = "Bob";
        String studentName3 = "Alice"; // Dies wird auf dasselbe "Alice" im String-Pool verweisen

        studentScores.put(studentName1, 95);
        studentScores.put(studentName2, 88);
        studentScores.put(studentName3, 92); // Dies aktualisiert den Wert für "Alice"

        System.out.println("Student Scores: " + studentScores);
        System.out.println("Alice's score: " + studentScores.get("Alice"));
        System.out.println("Bob's score: " + studentScores.get("Bob"));

        // Demonstrieren, dass auch wenn wir versuchen, einen String zu "ändern", ein neuer erstellt wird
        String originalString = "Java";
        System.out.println("Original String: " + originalString + ", HashCode: " + originalString.hashCode());

        String modifiedString = originalString.concat(" Programming"); // Erstellt einen neuen String
        System.out.println("Modified String: " + modifiedString + ", HashCode: " + modifiedString.hashCode());
        System.out.println("Original String (after concat): " + originalString + ", HashCode: " + originalString.hashCode());
    }
}

Speichern Sie die Datei. Kompilieren und führen Sie das Programm dann erneut aus:

javac ImmutableStringDemo.java
java ImmutableStringDemo

Sie sollten eine Ausgabe ähnlich dieser sehen, einschließlich der HashMap- und Hashcode-Demonstrationen:

Initial name: LabexUser
str1: Hello
str2: Hello
str1 == str2: true
str3: Hello
str1 == str3: false

--- Demonstrating Security and Thread Safety ---
Secure Password: MySecurePassword123
Thread Thread-1 accessing password: MySecurePassword123
Thread Thread-2 accessing password: MySecurePassword123
Thread Thread-3 accessing password: MySecurePassword123

--- Demonstrating Hashcode Caching ---
Student Scores: {Bob=88, Alice=92}
Alice's score: 92
Bob's score: 88
Original String: Java, HashCode: 2301506
Modified String: Java Programming, HashCode: -1479700901
Original String (after concat): Java, HashCode: 2301506

Beachten Sie, wie der Hashcode von originalString gleich bleibt, auch nachdem concat aufgerufen wurde, da concat ein neues String-Objekt zurückgibt und das Original unverändert lässt.

Zusammenfassung

In diesem Lab haben Sie das grundlegende Konzept unveränderlicher Strings in der Java-Programmierung kennengelernt. Sie haben untersucht, warum Strings standardmäßig unveränderlich sind und wie diese Designentscheidung durch den String-Pool zur Speicheroptimierung beiträgt. Sie haben auch die entscheidenden Vorteile der Unveränderlichkeit verstanden, darunter erhöhte Sicherheit für sensible Daten, inhärente Thread-Sicherheit für gleichzeitige Operationen und verbesserte Leistung in Hash-basierten Collections aufgrund des Hashcode-Cachings. Durch die Durchführung der praktischen Übungen haben Sie praktische Erfahrungen im effektiven Erstellen und Nutzen unveränderlicher Strings in Ihren Java-Anwendungen gesammelt.