¿Por qué la clase String se declara final en Java?

141

Desde que supe que la clase java.lang.Stringse declara como final en Java, me preguntaba por qué. No encontré ninguna respuesta en ese entonces, pero esta publicación: ¿Cómo crear una réplica de la clase String en Java? Me recordó mi consulta.

Claro, String proporciona toda la funcionalidad que alguna vez necesité, y nunca pensé en ninguna operación que requiriera una extensión de la clase String, ¡pero aún así nunca sabrás lo que alguien podría necesitar!

Entonces, ¿alguien sabe cuál era la intención de los diseñadores cuando decidieron hacerlo definitivo?

Alex Ntousias
fuente
¡Gracias a todos por sus respuestas, especialmente TrueWill, Bruno Reis y Thilo! Desearía poder elegir más de una respuesta como la mejor, pero desafortunadamente ...!
Alex Ntousias
1
Considere también la proliferación de los proyectos "Oh, solo necesito algunos métodos de utilidad más en String" que aparecerían, todos los cuales no podían usar los Strings porque eran una clase diferente.
Thorbjørn Ravn Andersen
Gracias por esta respuesta es muy útil. Tenemos dos hechos ahora. Una cadena es una clase final y es inmutable porque no se puede cambiar pero se puede referir a otro objeto. pero que pasa con: - String a = new String ("test1"); entonces, s = "test2"; Si String es un objeto de clase Final, ¿cómo se puede modificar? ¿Cómo puedo usar el objeto final modificado? Por favor, déjame si he preguntado algo erróneamente.
Suresh Sharma
Puedes ver este buen artículo .
Aniket Thakur
44
Una cosa que afortunadamente hemos evitado en Java es que "todos tienen su propia subclase de String con muchos métodos adicionales, y ninguno de ellos es compatible entre sí".
Thorbjørn Ravn Andersen

Respuestas:

88

Es muy útil tener cadenas implementadas como objetos inmutables . Debería leer sobre la inmutabilidad para comprender más al respecto.

Una ventaja de los objetos inmutables es que

Puede compartir duplicados señalándolos a una sola instancia.

(desde aquí ).

Si String no fuera definitivo, podría crear una subclase y tener dos cadenas que se parezcan cuando se "vean como Strings", pero que en realidad son diferentes.

Bruno Reis
fuente
70
A menos que haya una conexión entre las clases finales y los objetos inmutables que no estoy viendo, no veo cómo su respuesta se relaciona con la pregunta.
sepp2k
9
Porque si no es final, puede pasar un StringChild a algún método como un parámetro de cadena, y podría ser mutable (debido a un cambio de estado de clase secundaria).
helios
44
¡Guauu! Votos a favor? ¿No entiendes cómo se relaciona la subclase con la inmutabilidad? Agradecería una explicación sobre cuál es el problema.
Bruno Reis
77
@Bruno, re: votos negativos: no te voté negativamente, pero podrías agregar una oración sobre cómo prevenir las subclases impone la inmutabilidad. En este momento, es una especie de media respuesta.
Thilo
12
@BrunoReis: encontré un buen artículo al que puedes vincular que tiene una entrevista con James Gosling (creador de Java) donde habla brevemente sobre este tema aquí . Aquí hay un fragmento interesante: "Una de las cosas que obligó a las cadenas a ser inmutables fue la seguridad. Tienes un método para abrir archivos. Le pasas una cadena. Y luego está haciendo todo tipo de verificaciones de autenticación antes de comenzar a hacer el sistema operativo llamar. Si logras hacer algo que efectivamente ha mutado el String, después del control de seguridad y antes de la llamada del sistema operativo, entonces boom, estás en ... "
Anurag
60

Este es un buen artículo que describe dos razones ya mencionadas en las respuestas anteriores:

  1. Seguridad : el sistema puede entregar fragmentos sensibles de información de solo lectura sin preocuparse de que se modifiquen
  2. Rendimiento : los datos inmutables son muy útiles para hacer que las cosas sean seguras para subprocesos.

Y este es probablemente el comentario más detallado en ese artículo. Tiene que ver con el conjunto de cadenas en Java y los problemas de seguridad. Se trata de cómo decidir qué entra en el conjunto de cadenas. Suponiendo que ambas cadenas son iguales si su secuencia de caracteres es la misma, entonces tenemos una condición de carrera sobre quién llega primero y junto con los problemas de seguridad. De lo contrario, el conjunto de cadenas contendrá cadenas redundantes, perdiendo así la ventaja de tenerlo en primer lugar. Solo léelo por ti mismo, ¿quieres?


La extensión de String causaría estragos en iguales e internos. JavaDoc dice igual a:

Compara esta cadena con el objeto especificado. El resultado es verdadero si y solo si el argumento no es nulo y es un objeto String que representa la misma secuencia de caracteres que este objeto.

Suponiendo que java.lang.Stringno sea final, a SafeStringpodría ser igual a a String, y viceversa; porque representarían la misma secuencia de personajes.

¿Qué sucedería si aplicaras interna un SafeString- SafeStringiría al grupo de cadenas de la JVM? El ClassLoadery todos los objetos a los que se hacen SafeStringreferencias se bloquearían en su lugar durante la vida útil de la JVM. Tendría una condición de carrera sobre quién podría ser el primero en internar en una secuencia de personajes: tal vez SafeStringganaría, tal vez un String, o tal vez un SafeStringcargado por un cargador de clases diferente (por lo tanto, una clase diferente).

Si ganaras la carrera en el grupo, este sería un verdadero singleton y las personas podrían acceder a todo tu entorno (caja de arena) a través de la reflexión y secretKey.intern().getClass().getClassLoader().

O la JVM podría bloquear este agujero asegurándose de que solo se agregaron objetos de String concretos (y no subclases) al grupo.

Si equals se implementó de manera que SafeString! = StringEntonces SafeString.intern! = String.intern, Y SafeStringtendría que agregarse al grupo. El grupo se convertiría en un grupo de en <Class, String>lugar de <String>y todo lo que necesitaría para ingresar al grupo sería un nuevo cargador de clases.

Anurag
fuente
2
Por supuesto, la razón del rendimiento es una falacia: si String hubiera sido una interfaz, habría podido proporcionar una implementación que funciona mejor en mi aplicación.
Stephan Eggermont
28

La razón absolutamente más importante por la que String es inmutable o final es que la utiliza el mecanismo de carga de clases y, por lo tanto, tiene aspectos de seguridad profundos y fundamentales.

Si String hubiera sido mutable o no final, una solicitud para cargar "java.io.Writer" podría haberse cambiado para cargar "mil.vogoon.DiskErasingWriter"

referencia: por qué String es inmutable en Java

Jackob
fuente
15

String es una clase muy básica en Java, muchas cosas dependen de que funcione de cierta manera, por ejemplo, que sea inmutable.

Hacer la clase finalevita las subclases que podrían romper estos supuestos.

Tenga en cuenta que, incluso ahora, si usa la reflexión, puede romper cadenas (cambiar su valor o código hash). La reflexión se puede detener con un administrador de seguridad. Si Stringno fuera así final, todos podrían hacerlo.

Otras clases que no se declaran le finalpermiten definir subclases algo rotas (podría tener una Listque agregue a la posición incorrecta, por ejemplo), pero al menos la JVM no depende de esas para sus operaciones principales.

Thilo
fuente
66
finalen una clase no garantiza la inmutabilidad. Simplemente garantiza que los invariantes de una clase (uno de los cuales puede ser inmutabilidad) no pueden ser modificados por una subclase.
Kevin Brock
1
@ Kevin: sí. Final en una clase garantiza que no hay subclases. No tiene nada que ver con la inmutabilidad.
Thilo
44
Hacer una clase final no es, por sí misma, inmutable. Pero hacer una clase inmutable final asegura que nadie haga una subclase que rompa la inmutabilidad. Quizás las personas que señalaron la inmutabilidad no tenían claro exactamente lo que querían decir, pero sus declaraciones son correctas cuando se entienden en contexto.
Jay
Algunas veces leí esta respuesta y pensé que era una respuesta okey, luego leí hashcode & equals de 'The Effective Java' y me di cuenta de que es una muy buena respuesta. Cualquiera necesita explicaciones, recomiendo ieam 8 e iteam 9 del mismo libro.
Abhishek Singh
6

Como dijo Bruno, se trata de la inmutabilidad. No se trata solo de cadenas, sino también de cualquier envoltorio, por ejemplo, doble, entero, carácter, etc. Hay muchas razones para esto:

  • Hilo de seguridad
  • Seguridad
  • Montón que es administrado por el propio Java (de manera diferente al montón ordinario que se recolecta basura de manera diferente)
  • Gestión de la memoria

Básicamente, para que usted, como programador, pueda estar seguro de que su cadena nunca se cambiará. También, si sabe cómo funciona, puede mejorar la gestión de la memoria. Intente crear dos cadenas idénticas una tras otra, por ejemplo "hola". Notarás, si depuras, que tienen ID idénticos, eso significa que son exactamente LOS MISMOS objetos. Esto se debe al hecho de que Java te permite hacerlo. Esto no sería posible si las cuerdas fueran muttables. Pueden tener lo mismo que yo, etc., porque nunca cambiarán. Entonces, si alguna vez decide crear 1,000,000 de cadenas "hola", lo que realmente haría es crear 1,000,000 de punteros para "hola". Además, la asignación de cualquier función en una cadena, o cualquier envoltorio por esa razón, daría como resultado la creación de otro objeto (nuevamente observe la ID del objeto: cambiará).

Además, final en Java no significa necesariamente que el objeto no pueda cambiar (es diferente a, por ejemplo, C ++). Significa que la dirección a la que apunta no puede cambiar, pero aún puede cambiar sus propiedades y / o atributos. Por lo tanto, comprender la diferencia entre la inmutabilidad y la final en algunos casos podría ser realmente importante.

HTH

Referencias

Artur
fuente
1
No creo que las cadenas vayan a un montón diferente o usen una administración de memoria diferente. Ciertamente son recolectables de basura.
Thilo
2
Además, la palabra clave final en una clase es completamente diferente de la palabra clave final para un campo.
Thilo
1
De acuerdo, en la JVM de Sun, las cadenas que están internadas () pueden ir a la generación permanente, que no es parte del montón. Pero eso definitivamente no sucede para todas las cadenas o todas las JVM.
Thilo
2
No todas las cadenas van a esa área, solo las cadenas que han sido internadas. La internación es automática para cadenas literales. (@Thilo, escribiendo mientras envías tu comentario).
Kevin Brock
Gracias por esta respuesta es muy útil. Tenemos dos hechos ahora. Una cadena es una clase final y es inmutable porque no se puede cambiar pero se puede referir a otro objeto. pero que pasa con: - String a = new String ("test1"); entonces, s = "test2"; Si String es un objeto de clase Final, ¿cómo se puede modificar? ¿Cómo puedo usar el objeto final modificado? Por favor, déjame si he preguntado algo erróneamente.
Suresh Sharma
2

Pudo haber sido para simplificar la implementación. Si diseña una clase que será heredada por los usuarios de la clase, entonces tiene un conjunto completamente nuevo de casos de uso para considerar en su diseño. ¿Qué sucede si hacen esto o aquello con el campo X protegido? Al hacerlo definitivo, pueden centrarse en hacer que la interfaz pública funcione correctamente y asegurarse de que sea sólida.

AaronLS
fuente
3
+1 para "diseñar para la herencia es difícil". Por cierto, eso está muy bien explicado en "Java eficaz" de Bloch.
sleske
2

Con muchos puntos buenos ya mencionados, me gustaría agregar otro, uno de los motivos por los que String es inmutable en Java es permitir que String almacene en caché su código hash , siendo String inmutable en Java almacena en caché su código hash, y no calculo cada vez que llamamos al método hashcode de String , lo que lo hace muy rápido como clave hashmap para usar en hashmap en Java.

En resumen, porque String es inmutable, nadie puede cambiar su contenido una vez creado, lo que garantiza que hashCode of String sea el mismo en la invocación múltiple.

Si ve que la Stringclase tiene se declara como

/** Cache the hash code for the string */
private int hash; // Default to 0

y la hashcode()función es la siguiente:

public int hashCode() {
    int h = hash;
    if (h == 0 && value.length > 0) {
        char val[] = value;

        for (int i = 0; i < value.length; i++) {
            h = 31 * h + val[i];
        }
        hash = h;
    }
    return h;
}

Si ya es computadora, simplemente devuelva el valor.

Aniket Thakur
fuente
2

Para asegurarnos de que no obtengamos una mejor implementación. Por supuesto, debería haber sido una interfaz.

[editar] Ah, obteniendo más votos despistados. La respuesta es perfectamente seria. Tuve que programar mi camino alrededor de la estúpida implementación de String varias veces, lo que condujo a una severa pérdida de rendimiento y productividad

Stephan Eggermont
fuente
2

Además de las razones obvias sugeridas en otras respuestas, una idea de hacer que la clase String sea final también podría estar relacionada con la sobrecarga del rendimiento de los métodos virtuales. Recuerde que String es una clase pesada, por lo que esta final significa que no hay sub-implementación con seguridad, significa que no hay sobrecarga de llamadas indirectas. Por supuesto, ahora tenemos cosas como invocación virtual y otras, que siempre hacen este tipo de optimización por usted.

Abhishek Singh
fuente
2

Además de las razones mencionadas en otras respuestas (seguridad, inmutabilidad, rendimiento), debe tenerse en cuenta que Stringtiene un soporte de idioma especial. Puede escribir Stringliterales y hay soporte para el +operador. Permitir que los programadores subclasifiquen String, fomentaría hacks como:

class MyComplex extends String { ... }

MyComplex a = new MyComplex("5+3i");
MyComplex b = new MyComplex("7+4i");
MyComplex c = new MyComplex(a + b);   // would work since a and b are strings,
                                      // and a string + a string is a string.
aioobe
fuente
1

Bueno, tengo un pensamiento diferente, no estoy seguro de si estoy en lo correcto o no, pero en Java String es el único objeto que puede ser tratado como un tipo de datos primitivo, quiero decir que podemos crear un objeto String como String name = "java " . Ahora, como otros tipos de datos primitivos que se copian por valor, no se copian por referencia , se espera que String tenga el mismo comportamiento, por lo que String es final. Eso es lo que pensé. Por favor ignore si es completamente ilógico.

webdev
fuente
1
En mi opinión, String nunca se comporta como un tipo primitivo. Un literal de cadena como "java" es en realidad un objeto de la clase String (puede usar el operador de punto en él, inmediatamente después de la cita de cierre). Entonces, asignar un literal a una variable de cadena es solo asignar referencias de objeto como de costumbre. Lo que es diferente es que la clase String tiene soporte de nivel de lenguaje integrado en el compilador ... convirtiendo las cosas entre comillas dobles en objetos String y el operador + como se mencionó anteriormente.
Georgie
1

La finalidad de las cadenas también las defiende como estándar. En C ++ puede crear subclases de cadena, por lo que cada tienda de programación podría tener su propia versión de cadena. Esto llevaría a la falta de un estándar fuerte.

ncmathsadist
fuente
1

Digamos que tienes una Employeeclase que tiene un método greet. Cuando greetse llama al método, simplemente se imprime Hello everyone!. Ese es el comportamiento esperado del greetmétodo

public class Employee {

    void greet() {
        System.out.println("Hello everyone!");
    }
}

Ahora, deje que la GrumpyEmployeesubclase Employeey anule el greetmétodo como se muestra a continuación.

public class GrumpyEmployee extends Employee {

    @Override
    void greet() {
        System.out.println("Get lost!");
    }
}

Ahora, en el siguiente código, eche un vistazo al sayHellométodo. Toma Employeeinstancia como parámetro y llama al método greet esperando que diga Hello everyone!Pero lo que obtenemos es Get lost!. Este cambio de comportamiento se debe aEmployee grumpyEmployee = new GrumpyEmployee();

public class TestFinal {
    static Employee grumpyEmployee = new GrumpyEmployee();

    public static void main(String[] args) {
        TestFinal testFinal = new TestFinal();
        testFinal.sayHello(grumpyEmployee);
    }

    private void sayHello(Employee employee) {
        employee.greet(); //Here you would expect a warm greeting, but what you get is "Get lost!"
    }
}

Esta situación se puede evitar si Employeese realizó la clase final. Ahora depende de su imaginación la cantidad de caos que un programador descarado podría causar si la StringClase no se declarara como final.

Andy
fuente
0

¿JVM sabe lo que es inmutable? La respuesta es No, el grupo constante contiene todos los campos inmutables, pero todos los campos / objetos inmutables no se almacenan solo en el grupo constante. Solo lo implementamos de manera que logre la inmutabilidad y sus características. CustomString podría implementarse sin que sea final utilizando MarkerInterface, lo que proporcionaría un comportamiento especial de Java para su agrupación, ¡la característica aún está esperando!

hola.nitish
fuente
0

La mayoría de las respuestas están relacionadas con la inmutabilidad: por qué un objeto de tipo String no se puede actualizar en su lugar. Aquí hay mucha buena discusión, y la comunidad Java haría bien en adoptar la inmutabilidad como principal. (No aguanto la respiración)

Sin embargo, la pregunta del OP es por qué es final, por qué no se puede extender. Algunos aquí asumieron esto, pero estaría de acuerdo con el OP que hay una brecha real aquí. Otro lenguaje permite a los desarrolladores crear nuevos tipos nominales para un tipo. Por ejemplo, en Haskell puedo crear los siguientes tipos nuevos que son idénticos en tiempo de ejecución como texto, pero proporcionan seguridad de enlace en tiempo de compilación.

newtype AccountCode = AccountCode Text
newtype FundCode = FundCode Text

Por lo tanto, propondría la siguiente sugerencia como una mejora del lenguaje Java:

newtype AccountCode of String;
newtype FundCode of String;

AccountCode acctCode = "099876";
FundCode fundCode = "099876";

acctCode.equals(fundCode);  // evaluates to false;
acctCode.toString().equals(fundCode.toString());  // evaluates to true;

acctCode=fundCode;  // compile error
getAccount(fundCode);  // compile error

(O tal vez podríamos comenzar a alejarnos de Java)

dsmith
fuente
-1

Si crea una cadena una vez, lo considerará, es un objeto si desea modificar eso, no es posible, creará un nuevo objeto.

Suresh
fuente
Por favor aclare su respuesta.
Marko Popovic