Stringhe Java: "String s = new String (" silly ");"


85

Sono un ragazzo C ++ che impara Java. Sto leggendo Java efficace e qualcosa mi ha confuso. Dice di non scrivere mai codice come questo:

String s = new String("silly");

Perché crea Stringoggetti non necessari . Ma invece dovrebbe essere scritto così:

String s = "No longer silly";

Ok, per ora va bene ... Tuttavia, data questa classe:

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

CaseInsensitiveString cis = new CaseInsensitiveString("Polish");
String s = "polish";
  1. Perché la prima affermazione è corretta? Non dovrebbe essere

    CaseInsensitiveString cis = "Polish";

  2. Come faccio a CaseInsensitiveStringcomportarmi in questo Stringmodo in modo che l'affermazione sopra sia OK (con e senza estensione String)? Di cosa si tratta String che rende OK essere in grado di trasmetterlo letteralmente come quello? Dalla mia comprensione non esiste il concetto di "costruttore di copia" in Java?


2
String str1 = "foo"; String str2 = "foo"; Sia str1 che str2 appartengono allo stesso oggetto String, "foo", b'coz per Java gestisce le stringhe in StringPool, quindi una nuova variabile si riferisce alla stessa stringa, non ne crea un'altra piuttosto assegna la stessa avvertenza presente in StringPool. Ma quando lo facciamo: String str1 = new String ("foo"); String str2 = new String ("foo"); Qui sia str1 che str2 appartengono a oggetti diversi, b'coz new String () crea con forza un nuovo oggetto stringa.
Akash5288

Risposte:


110

Stringè una speciale classe incorporata della lingua. È solo per la Stringclasse in cui dovresti evitare di dire

String s = new String("Polish");

Perché il letterale "Polish"è già di tipo Stringe stai creando un oggetto extra non necessario. Per qualsiasi altra classe, dicendo

CaseInsensitiveString cis = new CaseInsensitiveString("Polish");

è la cosa corretta (e l'unica, in questo caso) da fare.


8
un secondo punto è che il compilatore può immaginare cose di internig / propagating con stringhe letterali che non sono necessariamente possibili con una funzione così strana come "String (literal)"
Tetha

4
Poiché non dovresti mai chiamare new String("foo"), potresti chiederti perché new String(String)esiste il costruttore . La risposta è che non c'è a volte un buon uso per esso: stackoverflow.com/a/390854/1442870
Enwired

Cordiali saluti, il commento di Tetha sopra ha scritto male la parola "interning", come in string interning .
Basil Bourque

56

Credo che il vantaggio principale dell'utilizzo della forma letterale (cioè "foo" piuttosto che new String ("foo")) sia che tutti i letterali String sono "internati" dalla VM. In altre parole, viene aggiunto a un pool in modo tale che qualsiasi altro codice che crea la stessa stringa utilizzerà la stringa in pool anziché creare una nuova istanza.

Per illustrare, il codice seguente stamperà true per la prima riga, ma false per la seconda:

System.out.println("foo" == "foo");
System.out.println(new String("bar") == new String("bar"));

14
Allo stesso modo, ecco perché FindBugs ti dice di sostituire "new Integer (N)" con "Integer.valueOf (N)" - a causa di questo interning.
Paul Tomblin

6
Dovresti anche aggiungere "foo" == new String ("foo").
Intern

4
Una correzione: le stringhe letterali vengono fatte per puntare allo stesso riferimento dal compilatore, non dalla VM. La VM può internare oggetti String in fase di esecuzione, quindi la seconda riga potrebbe restituire true o false!
Craig P.Motlin

1
@ Motlin: non sono sicuro che sia corretto. Il javadoc per la classe String impone che "tutte le stringhe letterali e le espressioni costanti con valori di stringa siano internate". Quindi possiamo fare affidamento sul fatto che i letterali siano internati, il che significa che "foo" == "foo" dovrebbe sempre restituire true.
Leigh

3
@Leigh Sì, i letterali sono internati, ma dal compilatore e non dalla VM. Quello che Motlin sta ottenendo è che la VM può anche internare stringhe, quindi, indipendentemente dal fatto che new String ("bar") == new String ("bar") -> false sia dipendente dall'implementazione.
Aaron Maenpaa

30

Le stringhe sono trattate un po 'in modo speciale in java, sono immutabili, quindi è sicuro che vengano gestite dal conteggio dei riferimenti.

Se scrivi

String s = "Polish";
String t = "Polish";

allora set si riferiscono effettivamente allo stesso oggetto e s == t restituirà true, poiché "==" per oggetti letti "è lo stesso oggetto" (o posso, comunque, non sono sicuro che questo faccia parte di le specifiche del linguaggio o semplicemente un dettaglio dell'implementazione del compilatore, quindi forse non è sicuro fare affidamento su questo).

Se scrivi

String s = new String("Polish");
String t = new String("Polish");

quindi s! = t (perché hai creato esplicitamente una nuova stringa) sebbene s.equals (t) restituirà true (perché stringa aggiunge questo comportamento a uguale).

La cosa che vuoi scrivere,

CaseInsensitiveString cis = "Polish";

non può funzionare perché stai pensando che le virgolette siano una sorta di costruttore di cortocircuito per il tuo oggetto, quando in realtà funziona solo per le semplici vecchie java.lang.Strings.


+1 per aver menzionato l'immutabilità, che per me è il vero motivo in java si scrive strA = strB, invece di strA = new String(strB). non ha davvero molto a che fare con lo string interning.
kritzikratzi

Non vengono gestiti dal conteggio dei riferimenti. Il pool di stringhe è richiesto da JLS.
Marchese di Lorne

20
String s1="foo";

letterale andrà in pool e s1 farà riferimento.

String s2="foo";

questa volta controllerà che il letterale "foo" sia già disponibile in StringPool o meno poiché ora esiste quindi s2 farà riferimento allo stesso letterale.

String s3=new String("foo");

Il valore letterale "foo" verrà creato prima in StringPool, quindi tramite il costruttore della stringa arg verrà creato l'oggetto String, ovvero "foo" nell'heap a causa della creazione dell'oggetto tramite il nuovo operatore, quindi s3 lo farà riferimento.

String s4=new String("foo");

uguale a s3

così System.out.println(s1==s2);// **true** due to literal comparison.

e System.out.println(s3==s4);// **false** due to object

confronto (s3 e s4 vengono creati in punti diversi nell'heap)


1
Non utilizzare la formattazione delle virgolette per il testo che non è citato e non utilizzare la formattazione del codice per il codice.
Marchese di Lorne

12

StringLe s sono speciali in Java: sono immutabili e le costanti stringa vengono automaticamente trasformate in Stringoggetti.

Non c'è modo che il tuo SomeStringClass cis = "value"esempio si applichi a nessun altro corso.

Né puoi estendere String, perché è dichiarato come final, il che significa che non è consentita alcuna sottoclasse.


7

Le stringhe Java sono interessanti. Sembra che le risposte abbiano coperto alcuni dei punti interessanti. Ecco i miei due centesimi.

le stringhe sono immutabili (non puoi mai cambiarle)

String x = "x";
x = "Y"; 
  • La prima riga creerà una variabile x che conterrà il valore di stringa "x". La JVM cercherà nel suo pool di valori di stringa e vedrà se "x" esiste, se lo fa, punterà la variabile x ad esso, se non esiste, la creerà e quindi eseguirà l'assegnazione
  • La seconda riga rimuoverà il riferimento a "x" e vedrà se "Y" esiste nel pool di valori di stringa. Se esiste, lo assegnerà, se non lo è, lo creerà prima e poi l'assegnazione. Poiché i valori stringa vengono utilizzati o meno, lo spazio di memoria nel pool di valori stringa verrà recuperato.

i confronti tra stringhe dipendono da ciò che stai confrontando

String a1 = new String("A");

String a2 = new String("A");
  • a1 non è uguale a2
  • a1e a2sono riferimenti a oggetti
  • Quando la stringa viene dichiarata in modo esplicito, vengono create nuove istanze ei loro riferimenti non saranno gli stessi.

Penso che tu sia sulla strada sbagliata cercando di utilizzare la classe caseinsensitive. Lascia stare le corde. Quello che ti interessa davvero è come visualizzi o confronti i valori. Usa un'altra classe per formattare la stringa o per fare confronti.

cioè

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

Dal momento che stai creando la classe, puoi fare in modo che i confronti facciano quello che vuoi: confronta i valori di testo.


Il compilatore crea il pool di stringhe, non la JVM. Le variabili non contengono oggetti, si riferiscono a loro. Lo spazio del pool di stringhe per i valori letterali stringa non viene mai recuperato.
Marchese di Lorne

6

Non puoi. Le cose tra virgolette in Java sono particolarmente riconosciute dal compilatore come stringhe e sfortunatamente non puoi sovrascriverle (o estenderle java.lang.String- è dichiarato final).


la finale è una falsa pista qui. Anche se String non fosse definitivo, in questo caso l'estensione di String non lo aiuterebbe.
Darron

1
Penso che tu voglia dire fortunatamente che non puoi ignorare questo. :)
Craig P. Motlin

@Motlin: Ha! potresti avere ragione. Penso di aver letto da qualche parte che Java è stato progettato per impedire a chiunque di fare qualcosa di stupido escludendo deliberatamente qualcosa di interessante come questo ...
Dan Vinton

6

Il modo migliore per rispondere alla tua domanda sarebbe familiarizzare con il "pool di costanti di stringhe". In java gli oggetti stringa sono immutabili (cioè i loro valori non possono essere modificati una volta inizializzati), quindi quando si modifica un oggetto stringa si finisce per creare un nuovo oggetto stringa modificato mentre il vecchio oggetto galleggia semplicemente in una memoria speciale chiamata "stringa pool costante ". creando un nuovo oggetto stringa tramite

String s = "Hello";

creerà solo un oggetto stringa nel pool e il riferimento s farà riferimento ad esso, ma utilizzando

String s = new String("Hello");

crei due oggetti stringa: uno nel pool e l'altro nell'heap. il riferimento farà riferimento all'oggetto nell'heap.


4

- Come faccio a far sì che CaseInsensitiveString si comporti come String in modo che l'istruzione sopra sia ok (con e senza estensione String)? Di cosa si tratta String che rende giusto essere in grado di passarlo letteralmente come quello? Dalla mia comprensione non esiste il concetto di "costruttore di copia" in Java, giusto?

È stato detto abbastanza dal primo punto. "Polish" è una stringa letterale e non può essere assegnata alla classe CaseInsentiviveString.

Ora sul secondo punto

Sebbene non sia possibile creare nuovi valori letterali, è possibile seguire il primo elemento di quel libro per un approccio "simile", quindi le seguenti affermazioni sono vere:

    // Lets test the insensitiveness
    CaseInsensitiveString cis5 = CaseInsensitiveString.valueOf("sOmEtHiNg");
    CaseInsensitiveString cis6 = CaseInsensitiveString.valueOf("SoMeThInG");

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

Ecco il codice.

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

public final class CaseInsensitiveString  {


    private static final Map<String,CaseInsensitiveString> innerPool 
                                = new HashMap<String,CaseInsensitiveString>();

    private final String s;


    // Effective Java Item 1: Consider providing static factory methods instead of constructors
    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 );   
    }

    // Class constructor: This creates a new instance each time it is invoked.
    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();
    }

// Verifica la classe utilizzando la parola chiave "assert"

    public static void main( String [] args ) {

        // Creating two different objects as in new String("Polish") == new String("Polish") is false
        CaseInsensitiveString cis1 = new CaseInsensitiveString("Polish");
        CaseInsensitiveString cis2 = new CaseInsensitiveString("Polish");

        // references cis1 and cis2 points to differents objects.
        // so the following is true
        assert cis1 !=  cis2;      // Yes they're different
        assert cis1.equals(cis2);  // Yes they're equals thanks to the equals method

        // Now let's try the valueOf idiom
        CaseInsensitiveString cis3 = CaseInsensitiveString.valueOf("Polish");
        CaseInsensitiveString cis4 = CaseInsensitiveString.valueOf("Polish");

        // References cis3 and cis4 points to same  object.
        // so the following is true
        assert cis3 == cis4;      // Yes they point to the same object
        assert cis3.equals(cis4); // and still equals.

        // Lets test the insensitiveness
        CaseInsensitiveString cis5 = CaseInsensitiveString.valueOf("sOmEtHiNg");
        CaseInsensitiveString cis6 = CaseInsensitiveString.valueOf("SoMeThInG");

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

        // Futhermore
        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>

Ovvero, creare un pool interno di oggetti CaseInsensitiveString e restituire l'istanza corrispondente da lì.

In questo modo l'operatore "==" restituisce true per due riferimenti a oggetti che rappresentano lo stesso valore .

Ciò è utile quando oggetti simili vengono utilizzati molto frequentemente e il costo di creazione è costoso.

La documentazione della classe stringa afferma che la classe utilizza un pool interno

La classe non è completa, sorgono alcuni problemi interessanti quando proviamo a esplorare i contenuti dell'oggetto durante l'implementazione dell'interfaccia CharSequence, ma questo codice è abbastanza buono per mostrare come quell'elemento nel Libro potrebbe essere applicato.

È importante notare che utilizzando internalPool oggetto , i riferimenti non vengono rilasciati e quindi non possono essere raccolti in modo indesiderato, e questo può diventare un problema se vengono creati molti oggetti.

Funziona per la classe String perché viene utilizzata in modo intensivo e il pool è costituito solo da oggetti "internati".

Funziona bene anche per la classe booleana, perché ci sono solo due valori possibili.

E infine questo è anche il motivo per cui valueOf (int) nella classe Integer è limitato da -128 a 127 valori int.


3

Nel tuo primo esempio, stai creando una String, "stupida" e quindi passandola come parametro al costruttore di copie di un'altra String, che crea una seconda String identica alla prima. Poiché le stringhe Java sono immutabili (qualcosa che spesso punge le persone abituate alle stringhe C), questo è un inutile spreco di risorse. Dovresti invece usare il secondo esempio perché salta diversi passaggi inutili.

Tuttavia, il valore letterale String non è un CaseInsensitiveString, quindi non puoi fare ciò che desideri nel tuo ultimo esempio. Inoltre, non c'è modo di sovraccaricare un operatore di casting come puoi in C ++, quindi non c'è letteralmente modo di fare ciò che vuoi. Devi invece passarlo come parametro al costruttore della tua classe. Ovviamente, probabilmente userei semplicemente String.toLowerCase () e finirò con esso.

Inoltre, il tuo CaseInsensitiveString dovrebbe implementare l'interfaccia CharSequence così come probabilmente le interfacce Serializable e Comparable. Ovviamente, se implementi Comparable, dovresti sovrascrivere anche equals () e hashCode ().


3

Solo perché hai la parola Stringnella tua classe, non significa che ottieni tutte le caratteristiche speciali della Stringclasse incorporata .


3

CaseInsensitiveStringnon è un Stringsebbene contenga un file String. Un Stringletterale ad es. "Esempio" può essere assegnato solo a un file String.


2

CaseInsensitiveString e String sono oggetti diversi. Non puoi fare:

CaseInsensitiveString cis = "Polish";

perché "Polish" è una stringa, non una stringa CaseInsensitive. Se String estendesse CaseInsensitiveString String, allora staresti bene, ma ovviamente non lo fa.

E non preoccuparti per la costruzione qui, non creerai oggetti non necessari. Se guardi il codice del costruttore, tutto ciò che sta facendo è memorizzare un riferimento alla stringa che hai passato. Non viene creato nulla di extra.

Nel caso String s = new String ("foobar") sta facendo qualcosa di diverso. Prima stai creando la stringa letterale "foobar", quindi ne crei una copia costruendo una nuova stringa da essa. Non è necessario creare quella copia.


Anche se estendessi String questo non funzionerebbe. Avresti bisogno di String per estendere CaseInsensitiveString.
Darron

in entrambi i casi è impossibile, o perché la stringa è incorporata o perché è dichiarata finale
Luca

2

quando dicono di scrivere

String s = "Silly";

invece di

String s = new String("Silly");

lo intendono quando si crea un oggetto String perché entrambe le istruzioni precedenti creano un oggetto String ma la nuova versione String () crea due oggetti String: uno nell'heap e l'altro nel pool di costanti di stringa. Quindi usando più memoria.

Ma quando scrivi

CaseInsensitiveString cis = new CaseInsensitiveString("Polish");

non stai creando una String invece stai creando un oggetto di classe CaseInsensitiveString. Quindi è necessario utilizzare il nuovo operatore.


1

Se ho capito bene, la tua domanda significa perché non possiamo creare un oggetto assegnandogli direttamente un valore, non limitiamolo a una classe Wrapper of String in java.

Per rispondere a questo direi solo che i linguaggi di programmazione puramente orientati agli oggetti hanno alcuni costrutti e si dice che tutti i letterali quando scritti da soli possono essere trasformati direttamente in un oggetto del tipo dato.

Ciò significa precisamente che, se l'interprete vede 3, verrà convertito in un oggetto Integer perché integer è il tipo definito per tali letterali.

Se l'interprete vede qualcosa tra virgolette singole come "a", creerà direttamente un oggetto di tipo carattere, non è necessario specificarlo poiché la lingua definisce l'oggetto predefinito di tipo carattere per esso.

Allo stesso modo, se l'interprete vede qualcosa in "", sarà considerato come un oggetto del suo tipo predefinito, cioè stringa. Questo è un codice nativo che funziona in background.

Grazie al MIT video lecture course 6.00 dove ho avuto il suggerimento per questa risposta.


0

In Java la sintassi "text" crea un'istanza della classe java.lang.String. L'incarico:

String foo = "text";

è un compito semplice, senza bisogno di un costruttore di copia.

MyString bar = "text";

È illegale qualunque cosa tu faccia perché la classe MyString non è né java.lang.String né una superclasse di java.lang.String.


0

Primo, non puoi creare una classe che si estenda da String, perché String è una classe finale. E java gestisce le stringhe in modo diverso dalle altre classi, quindi solo con String puoi farlo

String s = "Polish";

Ma con la tua classe devi invocare il costruttore. Quindi, quel codice va bene.


0

Vorrei solo aggiungere che Java ha costruttori di copia ...

Bene, questo è un normale costruttore con un oggetto dello stesso tipo come argomento.


2
È un modello di progettazione, non un costrutto del linguaggio. Con Java ci sono pochissimi usi in cui un costruttore di copie sarebbe interessante poiché tutto è sempre "per riferimento" e ogni oggetto ha solo una copia. In effetti, fare delle copie causerebbe davvero MOLTI problemi.
Bill K

0

Nella maggior parte delle versioni di JDK le due versioni saranno le stesse:

String s = new String ("stupida");

String s = "Non più stupido";

Poiché le stringhe sono immutabili, il compilatore mantiene un elenco di costanti di stringa e se si tenta di crearne una nuova, prima controllerà per vedere se la stringa è già definita. Se lo è, viene restituito un riferimento alla stringa immutabile esistente.

Per chiarire - quando dici "String s =" stai definendo una nuova variabile che occupa spazio nello stack - allora se dici "Non più sciocco" o nuova Stringa ("sciocco") accade esattamente la stessa cosa - un nuovo la stringa costante viene compilata nell'applicazione e il riferimento punta a quella.

Non vedo la distinzione qui. Tuttavia per la tua classe, che non è immutabile, questo comportamento è irrilevante e devi chiamare il tuo costruttore.

AGGIORNAMENTO: mi sbagliavo! Sulla base di un voto negativo e di un commento allegato, l'ho testato e mi sono reso conto che la mia comprensione è sbagliata: new String ("Silly") crea effettivamente una nuova stringa piuttosto che riutilizzare quella esistente. Non sono chiaro perché questo sarebbe (qual è il vantaggio?) Ma il codice parla più forte delle parole!


0

String è una delle classi speciali in cui puoi crearli senza la nuova parte Sring

è lo stesso di

int x = y;

o

char c;


0

È una legge fondamentale che le stringhe in java siano immutabili e facciano distinzione tra maiuscole e minuscole.


0
 String str1 = "foo"; 
 String str2 = "foo"; 

Sia str1 che str2 appartengono allo stesso oggetto String, "foo", b'coz Java gestisce le stringhe in StringPool, quindi se una nuova variabile fa riferimento alla stessa String, non ne crea un'altra ma assegna la stessa alerady presente in StringPool .

 String str1 = new String("foo"); 
 String str2 = new String("foo");

Qui sia str1 che str2 appartengono a oggetti diversi, b'coz new String () crea con forza un nuovo oggetto stringa.


-1

Java crea un oggetto String per ogni stringa letterale utilizzata nel codice. Ogni volta che ""viene utilizzato, è come chiamare new String().

Le stringhe sono dati complessi che semplicemente "agiscono" come dati primitivi. I letterali stringa sono in realtà oggetti anche se si fa finta che siano letterali primitivi, come 6, 6.0, 'c',ecc. Quindi il "letterale" "text"String restituisce un nuovo oggetto String con valore char[] value = {'t','e','x','t}. Pertanto, chiamando

new String("text"); 

in realtà è come chiamare

new String(new String(new char[]{'t','e','x','t'}));

Si spera da qui che tu possa capire perché il tuo libro di testo lo considera ridondante.

Per riferimento, ecco l'implementazione di String: http://www.docjar.com/html/api/java/lang/String.java.html

È una lettura divertente e potrebbe ispirare qualche intuizione. È anche fantastico per i principianti leggere e cercare di capire, poiché il codice dimostra un codice molto professionale e conforme alle convenzioni.

Un altro buon riferimento è il tutorial Java su Strings: http://docs.oracle.com/javase/tutorial/java/data/strings.html


Ogni volta che "" viene utilizzato, è un riferimento alla stessa stringa, nel pool di costanti.
Marchese di Lorne
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.