88 Stimmen

Java-Zeichenfolgen: "String s = new String("albern");"

Ich bin ein C++-Typ, der Java lernt. Ich lese Effective Java und etwas hat mich verwirrt. Es sagt, man solle niemals Code wie diesen schreiben:

String s = new String("silly");

Da es unnötige String-Objekte erstellt. Stattdessen sollte es so geschrieben werden:

String s = "Nicht mehr albern";

Ok bis hierher ... Aber, gegeben diese Klasse:

public final class CaseInsensitiveString {
    private String s;
    public CaseInsensitiveString(String s) {
        if (s == null) {
            throw new NullPointerException();
        }
        this.s = s;
    }
    :
    :
}

CaseInsensitiveString cis = new CaseInsensitiveString("Polnisch");
String s = "polnisch";
  1. Warum ist die erste Aussage in Ordnung? Sollte es nicht sein

    CaseInsensitiveString cis = "Polnisch";

  2. Wie mache ich CaseInsensitiveString so, dass es sich wie String verhält, damit die obige Aussage in Ordnung ist (mit und ohne Erweiterung von String)? Was macht String zu etwas, das es erlaubt, einfach einen Literal zu übergeben? Nach meinem Verständnis gibt es kein Konzept des "Kopierkonstruktors" in Java?

2 Stimmen

String str1 = "foo"; String str2 = "foo"; Beide str1 und str2 gehören zum selben String-Objekt, "foo", da Java Strings im StringPool verwaltet, sodass eine neue Variable auf denselben String verweist und nicht einen anderen erstellt, sondern den bereits im StringPool vorhandenen zuweist. Aber wenn wir dies tun: String str1 = new String("foo"); String str2 = new String("foo"); Hier gehören sowohl str1 als auch str2 zu verschiedenen Objekten, da new String() zwangsläufig ein neues String-Objekt erstellt.

8voto

Surender Thakran Punkte 3698

Der beste Weg, um Ihre Frage zu beantworten, wäre Sie mit dem "Zeichenkettenkonstantenpool" vertraut zu machen. In Java sind Zeichenkettenobjekte unveränderlich (d.h. ihre Werte können nicht geändert werden, sobald sie initialisiert sind). Beim Bearbeiten eines Zeichenkettenobjekts erstellen Sie also ein neues bearbeitetes Zeichenkettenobjekt, während das alte Objekt einfach im sogenannten "Zeichenkettenkonstantenpool" schwebt. Das Erstellen eines neuen Zeichenkettenobjekts durch

String s = "Hallo";

erstellt nur ein Zeichenkettenobjekt im Pool und die Referenz s verweist darauf, aber durch die Verwendung von

String s = new String("Hallo");

erstellen Sie zwei Zeichenkettenobjekte: eins im Pool und eins im Heap. Die Referenz wird auf das Objekt im Heap verweisen.

7voto

mson Punkte 7684

Java-Strings sind interessant. Es sieht so aus, als hätten die Antworten einige interessante Punkte abgedeckt. Hier sind meine zwei Cent.

Strings sind unveränderlich (du kannst sie nie ändern)

String x = "x";
x = "Y"; 
  • Die erste Zeile wird eine Variable x erstellen, die den Zeichenfolgenwert "x" enthält. Die JVM wird in ihrem Pool von Zeichenfolgenwerten nachsehen und sehen, ob "x" existiert, wenn es das tut, wird die Variable x darauf zeigen, wenn es nicht existiert, wird es erstellt und dann die Zuweisung durchgeführt
  • Die zweite Zeile wird die Referenz zu "x" entfernen und sehen, ob "Y" im Pool von Zeichenfolgenwerten existiert. Wenn es existiert, wird es zugewiesen, wenn es nicht existiert, wird es zuerst erstellt und dann zugewiesen. Da die Zeichenfolgenwerte verwendet oder nicht verwendet werden, wird der Speicherplatz im Pool von Zeichenfolgenwerten zurückgefordert.

Zeichenfolgenvergleiche hängen davon ab, was du vergleichst

String a1 = new String("A");

String a2 = new String("A");
  • a1 ist nicht gleich a2
  • a1 und a2 sind Objektreferenzen
  • Wenn eine Zeichenfolge explizit deklariert wird, werden neue Instanzen erstellt und ihre Referenzen werden nicht dieselben sein.

Ich glaube, du bist auf dem falschen Weg, wenn du versuchst, die caseinsensitive-Klasse zu verwenden. Lass die Zeichenfolgen in Ruhe. Was du wirklich interessiert, ist, wie du die Werte anzeigst oder vergleichst. Verwende eine andere Klasse, um die Zeichenfolge zu formatieren oder zu vergleichen.

d.h.

TextUtility.compare(Zeichenfolge 1, Zeichenfolge 2) 
TextUtility.compareIgnoreCase(Zeichenfolge 1, Zeichenfolge 2)
TextUtility.camelHump(Zeichenfolge 1)

Da du die Klasse selbst erstellst, kannst du die Vergleiche so machen, wie du es möchtest – vergleiche die Textwerte.

0 Stimmen

Der Compiler erstellt den String-Pool, nicht die JVM. Variablen enthalten keine Objekte, sie verweisen auf sie. Der Speicherplatz für String-Literals im String-Pool wird nie zurückgewonnen.

6voto

Dan Vinton Punkte 25571

Sie können nicht. Dinge in doppelten Anführungszeichen in Java werden vom Compiler speziell als Strings erkannt und leider können Sie dies nicht überschreiben (oder java.lang.String erweitern - es ist als final deklariert).

0 Stimmen

Final ist hier eine falsche Fährte. Auch wenn String nicht final wäre, würde es ihm in diesem Fall nicht helfen, String zu erweitern.

1 Stimmen

Ich denke, du meinst zum Glück kann man das nicht überschreiben. :)

0 Stimmen

@Motlin: Ha! Du hast vielleicht recht. Ich glaube, ich habe irgendwo gelesen, dass Java darauf ausgelegt war, zu verhindern, dass jemand jemals etwas Dummes tut, indem absichtlich alles Interessante wie dieses ausgeschlossen wird...

4voto

OscarRyz Punkte 189898

- Wie mache ich CaseInsensitiveString so, dass es sich wie String verhält, sodass die obige Anweisung in Ordnung ist (mit und ohne Erweiterung von String)? Was ist es an String, das es in Ordnung macht, ihm einfach so ein Literal zu übergeben? Meines Wissens nach gibt es in Java kein "Kopierkonstruktor"-Konzept, oder?

Vom ersten Punkt wurde genug besprochen. "Polish" ist ein Zeichenkettenliteral und kann der Klasse CaseInsentiviveString nicht zugewiesen werden.

Nun zum zweiten Punkt

Auch wenn Sie keine neuen Literale erstellen können, können Sie den ersten Punkt dieses Buchs für einen "ähnlichen" Ansatz verwenden, damit die folgenden Aussagen wahr sind:

    // Testen wir die Unempfindlichkeit
    CaseInsensitiveString cis5 = CaseInsensitiveString.valueOf("sOmEtHiNg");
    CaseInsensitiveString cis6 = CaseInsensitiveString.valueOf("SoMeThInG");

    assert cis5 == cis6;
    assert cis5.equals(cis6);

Hier ist der Code.

C:\oreyes\samples\java\insensitive>type CaseInsensitiveString.java
import java.util.Map;
import java.util.HashMap;

public final class CaseInsensitiveString  {

    private static final Map innerPool 
                                = new HashMap();

    private final String s;

    // Effektiver Java-Element 1: Erwägen Sie die Bereitstellung von statischen Fabrikmethode anstelle von Konstruktoren
    public static CaseInsensitiveString valueOf( String s ) {

        if ( s == null ) {
            return null;
        }
        String value = s.toLowerCase();

        if ( !CaseInsensitiveString.innerPool.containsKey( value ) ) {
             CaseInsensitiveString.innerPool.put( value , new CaseInsensitiveString( value ) );
         }

         return CaseInsensitiveString.innerPool.get( value );   
    }

    // Klassenkonstruktor: Dies erzeugt bei jedem Aufruf eine neue Instanz.
    public CaseInsensitiveString(String s){
        if (s == null) {
            throw new NullPointerException();
         }         
         this.s = s.toLowerCase();
    }

    public boolean equals( Object other ) {
         if ( other instanceof CaseInsensitiveString ) {
              CaseInsensitiveString otherInstance = ( CaseInsensitiveString ) other;
             return this.s.equals( otherInstance.s );
         }

         return false;
    }

    public int hashCode(){
         return this.s.hashCode();
    }

// Testen Sie die Klasse mit dem "assert" Schlüsselwort

    public static void main( String [] args ) {

        // Zwei verschiedene Objekte erstellen, da bei neuen String("Polish") == new String("Polish") falsch ist
        CaseInsensitiveString cis1 = new CaseInsensitiveString("Polish");
        CaseInsensitiveString cis2 = new CaseInsensitiveString("Polish");

        // Verweise cis1 und cis2 deuten auf unterschiedliche Objekte.
        // also folgendes ist wahr
        assert cis1 !=  cis2;      // Ja, sie sind unterschiedlich
        assert cis1.equals(cis2);  // Ja, sie sind gleich dank der equals Methode

        // Jetzt probieren wir das valueOf-Idiom aus
        CaseInsensitiveString cis3 = CaseInsensitiveString.valueOf("Polish");
        CaseInsensitiveString cis4 = CaseInsensitiveString.valueOf("Polish");

        // Verweise cis3 und cis4 deuten auf dasselbe Objekt.
        // also folgendes ist wahr
        assert cis3 == cis4;      // Ja, sie zeigen auf dasselbe Objekt
        assert cis3.equals(cis4); // und immer noch gleich.

        // Testen wir die Unempfindlichkeit
        CaseInsensitiveString cis5 = CaseInsensitiveString.valueOf("sOmEtHiNg");
        CaseInsensitiveString cis6 = CaseInsensitiveString.valueOf("SoMeThInG");

        assert cis5 == cis6;
        assert cis5.equals(cis6);

        // Außerdem
        CaseInsensitiveString cis7 = CaseInsensitiveString.valueOf("SomethinG");
        CaseInsensitiveString cis8 = CaseInsensitiveString.valueOf("someThing");

        assert cis8 == cis5 && cis7 == cis6;
        assert cis7.equals(cis5) && cis6.equals(cis8);
    }

}

C:\oreyes\samples\java\insensitive>javac CaseInsensitiveString.java

C:\oreyes\samples\java\insensitive>java -ea CaseInsensitiveString

C:\oreyes\samples\java\insensitive>

Dadurch wird ein interner Pool von CaseInsensitiveString-Objekten erstellt und die entsprechende Instanz von dort zurückgegeben.

Auf diese Weise gibt der "==" Operator true zurück, wenn zwei Objektreferenzen den gleichen Wert darstellen.

Dies ist nützlich, wenn ähnliche Objekte sehr häufig verwendet werden und die Erstellungskosten hoch sind.

Die String-Klasse-Dokumentation besagt, dass die Klasse einen [internen Pool](http://java.sun.com/javase/6/docs/api/java/lang/String.html#intern()) verwendet

Die Klasse ist nicht vollständig, einige interessante Probleme entstehen, wenn wir versuchen, den Inhalt des Objekts beim Implementieren des CharSequence-Interface zu durchlaufen, aber dieser Code zeigt gut, wie das Element in dem Buch angewendet werden könnte.

Es ist wichtig zu beachten, dass durch die Verwendung des internalPool Objekts die Referenzen nicht freigegeben werden und somit nicht sammelbar sind, was zu einem Problem werden kann, wenn viele Objekte erstellt werden.

Es funktioniert für die String-Klasse, weil sie intensiv verwendet wird und der Pool nur aus "interned" Objekten besteht.

Es funktioniert auch gut für die Boolean-Klasse, da es nur zwei mögliche Werte gibt.

Und schließlich ist das auch der Grund, warum valueOf(int) in der Klasse Integer auf -128 bis 127 int-Werte beschränkt ist.

3voto

James Punkte 2030

In Ihrem ersten Beispiel erstellen Sie einen String, "silly", und übergeben ihn dann als Parameter an den Kopierkonstruktor eines anderen Strings, der einen zweiten String erstellt, der mit dem ersten identisch ist. Da Java-Strings unveränderlich sind (etwas, das viele Leute sticht, die sich an C-Strings gewöhnt haben), ist dies eine überflüssige Ressourcenverschwendung. Sie sollten stattdessen das zweite Beispiel verwenden, da es einige überflüssige Schritte überspringt.

Der Stringliteral ist jedoch kein CaseInsensitiveString, sodass Sie in Ihrem letzten Beispiel nicht das gewünschte Verhalten erreichen können. Außerdem gibt es keine Möglichkeit, wie in C++ einen Umwandlungsoperator zu überladen, sodass es buchstäblich keine Möglichkeit gibt, das zu tun, was Sie wollen. Sie müssen es stattdessen als Parameter an den Konstruktor Ihrer Klasse übergeben. Natürlich würde ich wahrscheinlich einfach String.toLowerCase() verwenden und fertig sein.

Außerdem sollte Ihr CaseInsensitiveString das CharSequence-Interface sowie wahrscheinlich das Serializable- und das Comparable-Interface implementieren. Wenn Sie Comparable implementieren, sollten Sie natürlich auch equals() und hashCode() überschreiben.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X