Cadenas de Java: "String s = new String (" tonto ");"

85

Soy un chico de C ++ que está aprendiendo Java. Estoy leyendo Effective Java y algo me confundió. Dice nunca escribir código como este:

String s = new String("silly");

Porque crea Stringobjetos innecesarios . Pero, en cambio, debería escribirse así:

String s = "No longer silly";

Ok bien hasta ahora ... Sin embargo, dada esta clase:

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. ¿Por qué está bien la primera declaración? No debería ser

    CaseInsensitiveString cis = "Polish";

  2. ¿Cómo hago para CaseInsensitiveStringcomportarme Stringasí la declaración anterior está bien (con y sin extensión String)? ¿Qué tiene String que hace que esté bien poder pasarle un literal como ese? Según tengo entendido, ¿no existe el concepto de "constructor de copias" en Java?

JavaNewbie
fuente
2
String str1 = "foo"; String str2 = "foo"; Tanto str1 como str2 pertenecen al mismo objeto String, "foo", b'coz para Java administra las cadenas en StringPool, por lo que si una nueva variable se refiere a la misma cadena, no crea otra en lugar de asignar la misma alerady presente en StringPool. Pero cuando hacemos esto: String str1 = new String ("foo"); String str2 = new String ("foo"); Aquí tanto str1 como str2 pertenecen a diferentes Objetos, b'coz new String () crea forzosamente un nuevo Objeto String.
Akash5288

Respuestas:

110

Stringes una clase incorporada especial del idioma. Corresponde a la Stringclase única en la que se debe evitar decir

String s = new String("Polish");

Porque el literal "Polish"ya es de tipo Stringy está creando un objeto adicional innecesario. Para cualquier otra clase, diciendo

CaseInsensitiveString cis = new CaseInsensitiveString("Polish");

es lo correcto (y único, en este caso) que hacer.

Adam Rosenfield
fuente
8
un segundo punto es que el compilador puede imaginarse internig / propagating-stuff con cadenas literales que no son necesariamente posibles con una función tan extraña como "String (literal)"
Tetha
4
Como nunca debe llamar new String("foo"), puede preguntarse por qué new String(String)existe el constructor . La respuesta es que a veces tiene un buen uso: stackoverflow.com/a/390854/1442870
Enwired
Para su información, el comentario de Tetha anterior escribió mal la palabra "internación", como en internación de cuerdas .
Basil Bourque
56

Creo que el principal beneficio de usar la forma literal (es decir, "foo" en lugar de una nueva cadena ("foo")) es que todos los literales de cadena son "internados" por la máquina virtual. En otras palabras, se agrega a un grupo de modo que cualquier otro código que cree la misma cadena utilizará el String agrupado en lugar de crear una nueva instancia.

Para ilustrar, el siguiente código imprimirá verdadero para la primera línea, pero falso para la segunda:

System.out.println("foo" == "foo");
System.out.println(new String("bar") == new String("bar"));
Leigh
fuente
14
De manera similar, es por eso que FindBugs le dice que reemplace "new Integer (N)" con "Integer.valueOf (N)" - debido a esa internación.
Paul Tomblin
6
También debe agregar "foo" == new String ("foo"). Intern ()
James Schek
4
Una corrección: los literales de cadena están hechos para apuntar a la misma referencia por el compilador, no a la VM. La VM puede internar objetos String en tiempo de ejecución, por lo que la segunda línea puede devolver verdadero o falso.
Craig P. Motlin
1
@Motlin: No estoy seguro de que sea correcto. El javadoc para la clase String exige que "todas las cadenas literales y las expresiones constantes con valores de cadena estén internas". Por lo tanto, podemos confiar en que los literales se internen, lo que significa que "foo" == "foo" siempre debe devolver verdadero.
Leigh
3
@Leigh Sí, los literales son internados, pero por el compilador y no por la VM. Lo que Motlin quiere decir es que la máquina virtual también puede internar cadenas, por lo tanto, si new String ("barra") == new String ("barra") -> false es dependiente de la implementación.
Aaron Maenpaa
30

Las cadenas se tratan un poco especialmente en Java, son inmutables, por lo que es seguro que se manejen mediante el recuento de referencias.

Si tú escribes

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

entonces syt en realidad se refieren al mismo objeto, y s == t devolverá verdadero, porque "==" para los objetos leídos "es el mismo objeto" (o puedo, de todos modos, "no estoy seguro de si esto es parte de la especificación del lenguaje real o simplemente un detalle de la implementación del compilador, por lo que quizás no sea seguro confiar en esto)

Si tú escribes

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

entonces s! = t (porque ha creado explícitamente una nueva cadena) aunque s.equals (t) devolverá verdadero (porque la cadena agrega este comportamiento a igual).

Lo que quieres escribir

CaseInsensitiveString cis = "Polish";

no puede funcionar porque está pensando que las citas son una especie de constructor de cortocircuito para su objeto, cuando de hecho esto solo funciona para java.lang.Strings antiguos y sencillos.

Steve B.
fuente
+1 por mencionar la inmutabilidad, que para mí es la verdadera razón en Java se escribe strA = strB, en lugar de strA = new String(strB). realmente no tiene mucho que ver con la práctica de cuerdas.
kritzikratzi
No se manejan mediante recuento de referencias. El JLS requiere la agrupación de cadenas.
Marqués de Lorne
20
String s1="foo";

literal irá al grupo y s1 hará referencia.

String s2="foo";

esta vez comprobará que el literal "foo" ya está disponible en StringPool o no, ya que ahora existe, por lo que s2 hará referencia al mismo literal.

String s3=new String("foo");

El literal "foo" se creará en StringPool primero y luego a través del constructor de string arg. Se creará el objeto String, es decir, "foo" en el montón debido a la creación del objeto a través del nuevo operador y luego s3 lo referirá.

String s4=new String("foo");

igual que s3

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

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

comparación (s3 y s4 se crean en diferentes lugares del montón)

Vikas
fuente
1
No utilice formato de comillas para texto que no esté citado, y utilice formato de código para código.
Marqués de Lorne
12

StringLos s son especiales en Java: son inmutables y las constantes de cadena se convierten automáticamente en Stringobjetos.

No hay forma de que su SomeStringClass cis = "value"ejemplo se aplique a ninguna otra clase.

Tampoco puede extender String, porque se declara como final, lo que significa que no se permiten subclases.

Alnitak
fuente
7

Las cadenas de Java son interesantes. Parece que las respuestas han cubierto algunos de los puntos interesantes. Aquí están mis dos centavos.

las cadenas son inmutables (nunca puedes cambiarlas)

String x = "x";
x = "Y"; 
  • La primera línea creará una variable x que contendrá el valor de cadena "x". La JVM buscará en su grupo de valores de cadena y verá si "x" existe, si lo hace, apuntará la variable x hacia ella, si no existe, la creará y luego hará la asignación
  • La segunda línea eliminará la referencia a "x" y verá si "Y" existe en el grupo de valores de cadena. Si existe, lo asignará, si no, lo creará primero y luego lo asignará. A medida que se usen o no los valores de cadena, se recuperará el espacio de memoria en el grupo de valores de cadena.

las comparaciones de cadenas dependen de lo que esté comparando

String a1 = new String("A");

String a2 = new String("A");
  • a1 no es igual a2
  • a1y a2son referencias a objetos
  • Cuando la cadena se declara explícitamente, se crean nuevas instancias y sus referencias no serán las mismas.

Creo que estás en el camino equivocado al intentar usar la clase que no distingue entre mayúsculas y minúsculas. Deja las cuerdas en paz. Lo que realmente le importa es cómo muestra o compara los valores. Utilice otra clase para formatear la cadena o para hacer comparaciones.

es decir

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

Dado que está formando la clase, puede hacer que las comparaciones hagan lo que desee: comparar los valores del texto.

mson
fuente
El compilador crea la agrupación de cadenas, no la JVM. Las variables no contienen objetos, se refieren a ellos. El espacio del grupo de cadenas para literales de cadena nunca se reclama.
Marqués de Lorne
6

No puedes. Las cosas entre comillas dobles en Java son especialmente reconocidas por el compilador como cadenas, y desafortunadamente no puede anular esto (o extenderlo java.lang.String, está declarado final).

Dan Vinton
fuente
final es una pista falsa aquí. Incluso si String no fuera final, extender String no lo ayudaría en este caso.
Darron
1
Creo que quiere decir que, afortunadamente, no puede anular esto. :)
Craig P. Motlin
@Motlin: ¡Ja! bien puede tener razón. Creo que leí en alguna parte que Java fue diseñado para evitar que cualquiera haga algo estúpido al excluir deliberadamente algo interesante como esto ...
Dan Vinton
6

La mejor manera de responder a su pregunta sería familiarizarse con el "Grupo constante de cadenas". En Java, los objetos de cadena son inmutables (es decir, sus valores no se pueden cambiar una vez que se inicializan), por lo que al editar un objeto de cadena, terminas creando un nuevo objeto de cadena editado, por lo que el objeto antiguo simplemente flota en un área de memoria especial llamada "cadena piscina constante ". creando un nuevo objeto de cadena por

String s = "Hello";

solo creará un objeto de cadena en el grupo y las referencias se referirán a él, pero usando

String s = new String("Hello");

crea dos objetos de cadena: uno en el grupo y el otro en el montón. la referencia se referirá al objeto en el montón.

Surender Thakran
fuente
4

- ¿Cómo hago para que CaseInsensitiveString se comporte como String para que la declaración anterior esté bien (con y sin extender String)? ¿Qué tiene String que hace que esté bien poder pasarle un literal como ese? Según tengo entendido, no existe el concepto de "constructor de copias" en Java, ¿verdad?

Ya se ha dicho suficiente desde el primer punto. "Polaco" es un literal de cadena y no se puede asignar a la clase CaseInsentiviveString.

Ahora sobre el segundo punto

Aunque no puede crear nuevos literales, puede seguir el primer elemento de ese libro para un enfoque "similar", de modo que las siguientes declaraciones sean verdaderas:

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

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

Aquí está el código.

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();
    }

// Prueba la clase usando la palabra clave "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>

Es decir, cree un grupo interno de objetos CaseInsensitiveString y devuelva la instancia correspondiente desde allí.

De esta manera, el operador "==" devuelve verdadero para dos referencias de objetos que representan el mismo valor .

Esto es útil cuando se utilizan objetos similares con mucha frecuencia y la creación de costes es cara.

La documentación de la clase de cadena indica que la clase usa un grupo interno

La clase no está completa, surgen algunos problemas interesantes cuando intentamos recorrer el contenido del objeto al implementar la interfaz CharSequence, pero este código es lo suficientemente bueno para mostrar cómo se podría aplicar ese elemento en el Libro.

Es importante notar que al usar el objeto internalPool , las referencias no se liberan y, por lo tanto, no se pueden recolectar como basura, y eso puede convertirse en un problema si se crean muchos objetos.

Funciona para la clase String porque se usa de forma intensiva y el grupo está constituido únicamente por objetos "internos".

También funciona bien para la clase booleana, porque solo hay dos valores posibles.

Y finalmente, esa es también la razón por la que valueOf (int) en la clase Integer está limitado a valores de -128 a 127 int.

OscarRyz
fuente
3

En su primer ejemplo, está creando una Cadena, "tonta" y luego pasándola como parámetro al constructor de copia de otra Cadena, lo que crea una segunda Cadena que es idéntica a la primera. Dado que las cadenas de Java son inmutables (algo que frecuentemente irrita a las personas que están acostumbradas a las cadenas de C), esto es una pérdida innecesaria de recursos. En su lugar, debería utilizar el segundo ejemplo porque omite varios pasos innecesarios.

Sin embargo, el literal String no es CaseInsensitiveString, por lo que no puede hacer lo que quiera en su último ejemplo. Además, no hay forma de sobrecargar a un operador de conversión como puede hacerlo en C ++, por lo que literalmente no hay forma de hacer lo que quiere. En su lugar, debe pasarlo como parámetro al constructor de su clase. Por supuesto, probablemente solo usaría String.toLowerCase () y terminaría con eso.

Además, su CaseInsensitiveString debería implementar la interfaz CharSequence, así como probablemente las interfaces Serializable y Comparable. Por supuesto, si implementa Comparable, también debe anular equals () y hashCode ().

James
fuente
3

El hecho de que tenga la palabra Stringen su clase no significa que obtenga todas las características especiales de la Stringclase incorporada .

javaguy
fuente
3

CaseInsensitiveStringno es un Stringaunque contiene un String. Un Stringliteral, por ejemplo, "ejemplo", solo se puede asignar a un String.

fastcodejava
fuente
2

CaseInsensitiveString y String son objetos diferentes. No puedes hacer:

CaseInsensitiveString cis = "Polish";

porque "Polaco" es una Cadena, no una CaseInsensitiveString. Si String extendió CaseInsensitiveString String, estaría bien, pero obviamente no es así.

Y no te preocupes por la construcción aquí, no estarás haciendo objetos innecesarios. Si observa el código del constructor, todo lo que está haciendo es almacenar una referencia a la cadena que pasó. No se está creando nada adicional.

En el caso de String s = new String ("foobar"), está haciendo algo diferente. Primero está creando la cadena literal "foobar", luego crea una copia construyendo una nueva cadena a partir de ella. No es necesario crear esa copia.

Herms
fuente
Incluso si extendiera String, esto no funcionaría. Necesitaría String para extender CaseInsensitiveString.
Darron
en cualquier caso, es imposible, ya sea porque la cadena está incorporada o porque es final declarada
luke
2

cuando dicen escribir

String s = "Silly";

en vez de

String s = new String("Silly");

lo dicen en serio cuando crean un objeto String porque ambas declaraciones anteriores crean un objeto String pero la nueva versión String () crea dos objetos String: uno en el montón y el otro en el grupo constante de cadenas. Por lo tanto, usar más memoria.

Pero cuando escribes

CaseInsensitiveString cis = new CaseInsensitiveString("Polish");

no está creando una cadena, sino que está creando un objeto de la clase CaseInsensitiveString. Por lo tanto, debe utilizar el nuevo operador.


fuente
1

Si lo entendí correctamente, su pregunta significa por qué no podemos crear un objeto asignándole directamente un valor, no lo limitemos a una clase Wrapper of String en java.

Para responder a eso, solo diría que los lenguajes de programación puramente orientados a objetos tienen algunas construcciones y dice que todos los literales cuando se escriben solos pueden transformarse directamente en un objeto del tipo dado.

Eso significa precisamente que si el intérprete ve 3, se convertirá en un objeto Integer porque integer es el tipo definido para tales literales.

Si el intérprete ve algo entre comillas simples como 'a', creará directamente un objeto de tipo carácter, no es necesario que lo especifique ya que el idioma define el objeto predeterminado de tipo carácter para él.

De manera similar, si el intérprete ve algo en "", se considerará como un objeto de su tipo predeterminado, es decir, una cadena. Este es un código nativo que funciona en segundo plano.

Gracias al curso de conferencias en video del MIT 6.00 donde obtuve la pista para esta respuesta.

Dharam
fuente
0

En Java, la sintaxis "texto" crea una instancia de la clase java.lang.String. La asignación:

String foo = "text";

es una tarea simple, sin necesidad de constructor de copias.

MyString bar = "text";

Es ilegal hagas lo que hagas porque la clase MyString no es java.lang.String ni una superclase de java.lang.String.

Darron
fuente
0

Primero, no puede crear una clase que se extienda desde String, porque String es una clase final. Y Java administra Strings de manera diferente a otras clases, por lo que solo con String puede hacerlo

String s = "Polish";

Pero con tu clase tienes que invocar al constructor. Entonces, ese código está bien.

Lucas Gabriel Sánchez
fuente
0

Solo agregaría que Java tiene constructores de copia ...

Bueno, ese es un constructor ordinario con un objeto del mismo tipo que el argumento.

PhiLho
fuente
2
Es un patrón de diseño, no una construcción de lenguaje. Con Java hay muy pocos usos donde un constructor de copias sería interesante ya que todo es siempre "por referencia" y cada objeto solo tiene una copia. De hecho, hacer copias realmente causaría MUCHOS problemas.
Bill K
0

En la mayoría de las versiones del JDK, las dos versiones serán iguales:

String s = new String ("tonto");

String s = "Ya no es tonto";

Debido a que las cadenas son inmutables, el compilador mantiene una lista de constantes de cadenas y, si intenta crear una nueva, primero verificará si la cadena ya está definida. Si es así, se devuelve una referencia a la cadena inmutable existente.

Para aclarar, cuando dice "String s =", está definiendo una nueva variable que ocupa espacio en la pila, entonces si dice "Ya no es tonto" o nuevo String ("tonto") sucede exactamente lo mismo: un nuevo La cadena constante se compila en su aplicación y la referencia apunta a eso.

No veo la distinción aquí. Sin embargo, para su propia clase, que no es inmutable, este comportamiento es irrelevante y debe llamar a su constructor.

ACTUALIZACIÓN: ¡Me equivoqué! Basado en un voto negativo y un comentario adjunto, probé esto y me di cuenta de que mi comprensión es incorrecta: la nueva cadena ("tonto") de hecho crea una nueva cadena en lugar de reutilizar la existente. No tengo claro por qué sería esto (¿cuál es el beneficio?) ¡Pero el código habla más que las palabras!

Ewan Makepeace
fuente
0

String es una de las clases especiales en las que puede crearlos sin la nueva parte Sring

es lo mismo que

int x = y;

o

char c;

Tolga E
fuente
0

Es una ley básica que las cadenas en Java son inmutables y distinguen entre mayúsculas y minúsculas.

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

Tanto str1 como str2 pertenecen al mismo objeto String, "foo", b'coz Java administra las cadenas en StringPool, por lo que si una nueva variable se refiere a la misma cadena, no crea otra en lugar de asignar la misma alerady presente en StringPool .

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

Aquí tanto str1 como str2 pertenecen a diferentes Objetos, b'coz new String () crea forzosamente un nuevo String Object.

Akash5288
fuente
-1

Java crea un objeto String para cada literal de cadena que usa en su código. Se ""usa cualquier tiempo , es lo mismo que llamar new String().

Las cadenas son datos complejos que simplemente "actúan" como datos primitivos. Los literales de cadena son en realidad objetos aunque pretendamos que son literales primitivos, como, 6, 6.0, 'c',etc. Entonces, el "literal" de "text"cadena devuelve un nuevo objeto de cadena con valor char[] value = {'t','e','x','t}. Por lo tanto, llamando

new String("text"); 

en realidad es similar a llamar

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

Es de esperar que desde aquí pueda ver por qué su libro de texto considera esto redundante.

Como referencia, aquí está la implementación de String: http://www.docjar.com/html/api/java/lang/String.java.html

Es una lectura divertida y puede inspirar algo de comprensión. También es excelente para que los principiantes lean e intenten comprender, ya que el código demuestra un código muy profesional y compatible con las convenciones.

Otra buena referencia es el tutorial de Java sobre cadenas: http://docs.oracle.com/javase/tutorial/java/data/strings.html

Patrick Michaelsen
fuente
Cada vez que se utiliza "" es una referencia a la misma cadena, en el grupo constante.
Marqués de Lorne