Soy un desarrollador de Java desde hace mucho tiempo y, finalmente, después de especializarme, tengo tiempo para estudiarlo decentemente para tomar el examen de certificación ... Una cosa que siempre me ha molestado es que String sea "final". Lo entiendo cuando leo sobre los problemas de seguridad y cosas relacionadas ... Pero, en serio, ¿alguien tiene un verdadero ejemplo de eso?
Por ejemplo, ¿qué pasaría si String no fuera definitivo? Como si no estuviera en Ruby. No he escuchado ninguna queja de la comunidad de Ruby ... Y estoy al tanto de StringUtils y las clases relacionadas que tiene que implementar usted mismo o buscar en la web para implementar ese comportamiento (4 líneas de código) Estás dispuesto a hacerlo.
java.io.File
no fuera asífinal
. Oh no lo es. Tío.Respuestas:
La razón principal es la velocidad: las
final
clases no se pueden extender, lo que permitió que el JIT hiciera todo tipo de optimizaciones al manejar cadenas; nunca es necesario verificar los métodos anulados.Otra razón es la seguridad de los subprocesos: los inmutables siempre son seguros para los subprocesos porque un subproceso debe construirlos por completo antes de que se puedan pasar a otra persona, y después de la construcción, ya no se pueden cambiar.
Además, los inventores del tiempo de ejecución de Java siempre quisieron errar por el lado de la seguridad. Ser capaz de extender String (algo que suelo hacer en Groovy porque es muy conveniente) puede abrir una lata entera de gusanos si no sabes lo que estás haciendo.
fuente
final
como una comprobación rápida de que un método no se anula al compilar código.final
palabra clave en la clase. La matriz de caracteres que contiene es final, por lo que es inmutable. Hacer que la clase en sí sea final cierra el ciclo como @abl menciona ya que no se puede introducir una subclase mutable.Hay otra razón por la cual la
String
clase de Java debe ser final: es importante para la seguridad en algunos escenarios. Si laString
clase no fuera final, el siguiente código sería vulnerable a ataques sutiles por parte de un llamador malicioso:El ataque: un llamador malicioso podría crear una subclase de String,
EvilString
dondeEvilString.startsWith()
siempre devuelve verdadero pero donde el valor deEvilString
es algo malvado (por ejemplo,javascript:alert('xss')
). Debido a la subclasificación, esto evadiría el control de seguridad. Este ataque se conoce como vulnerabilidad de tiempo de verificación a tiempo de uso (TOCTTOU): entre el momento en que se realiza el control (que la url comienza con http / https) y el momento en que se usa el valor (para construir el fragmento html), el valor efectivo puede cambiar. SiString
no fuera definitivo, los riesgos de TOCTTOU serían generalizados.Entonces, si
String
no es definitivo, se vuelve difícil escribir código seguro si no puede confiar en su interlocutor. Por supuesto, esa es exactamente la posición en la que se encuentran las bibliotecas de Java: pueden ser invocadas por applets no confiables, por lo que no se atreven a confiar en la persona que llama. Esto significa que sería irrazonablemente difícil escribir un código de biblioteca seguro, siString
no fuera definitivo.fuente
char[]
interior de la cadena, pero requiere algo de suerte en el tiempo.char[]
dentro de la cadena; por lo tanto, no pueden explotar la vulnerabilidad TOCTTOU.String
no fuera definitivo. Siempre que este modelo de amenaza sea relevante, será importante defenderse contra él. Siempre y cuando sea importante para los applets y otros códigos que no sean de confianza, podría decirse que es suficiente para justificar la finalizaciónString
, incluso si no es necesario para aplicaciones confiables. (En cualquier caso, las aplicaciones confiables ya pueden eludir todas las medidas de seguridad, independientemente de si es definitiva).De lo contrario, las aplicaciones Java multiproceso serían un desastre (incluso peor de lo que realmente es).
En mi humilde opinión, la principal ventaja de las cadenas finales (inmutables) es que son inherentemente seguras para subprocesos: no requieren sincronización (escribir ese código a veces es bastante trivial pero está más que menos lejos de eso). Si fueran mutables, garantizar que cosas como los kits de herramientas de Windows multiproceso serían muy difíciles, y esas cosas son estrictamente necesarias.
fuente
final
Las clases no tienen nada que ver con la mutabilidad de la clase.Otra ventaja de
String
ser final es que garantiza resultados predecibles, al igual que la inmutabilidad.String
, aunque es una clase, debe tratarse en algunos escenarios, como un tipo de valor (el compilador incluso lo admite a través deString blah = "test"
). Por lo tanto, si crea una cadena con un cierto valor, espera que tenga cierto comportamiento heredado de cualquier cadena, similar a las expectativas que tendría con los enteros.Piense en esto: ¿Qué pasa si subclasifica
java.lang.String
y sobrepasa los métodosequals
ohashCode
? De repente, la "prueba" de dos cadenas ya no podía igualarse.Imagina el caos si pudiera hacer esto:
alguna otra clase:
fuente
Antecedentes
Hay tres lugares donde el final puede aparecer en Java:
Hacer una clase final evita todas las subclases de la clase. Hacer que un método sea final evita que las subclases del método lo anulen. Hacer un campo final evita que se cambie más tarde.
Conceptos erróneos
Hay optimizaciones que ocurren alrededor de los métodos y campos finales.
Un método final facilita la optimización de HotSpot a través de la inserción. Sin embargo, HotSpot hace esto incluso si el método no es definitivo, ya que funciona bajo el supuesto de que no se ha anulado hasta que se demuestre lo contrario. Más sobre esto en SO
Una variable final se puede optimizar agresivamente, y se puede leer más sobre eso en la sección 17.5.3 de JLS .
Sin embargo, con esa comprensión, uno debe ser consciente de que ninguna de estas optimizaciones se trata de hacer una clase final. No hay ganancia de rendimiento al hacer una clase final.
El aspecto final de una clase tampoco tiene nada que ver con la inmutabilidad. Uno puede tener una clase inmutable (como BigInteger ) que no sea final, o una clase que sea mutable y final (como StringBuilder ). La decisión sobre si una clase debe ser final es una cuestión de diseño.
Diseño final
Las cadenas son uno de los tipos de datos más utilizados. Se encuentran como claves para los mapas, almacenan nombres de usuario y contraseñas, son lo que se lee desde un teclado o un campo en una página web. Las cuerdas están en todas partes .
Mapas
Lo primero que debe tener en cuenta de lo que sucedería si pudiera subclasificar String es darse cuenta de que alguien podría construir una clase de cadena mutable que, de lo contrario, parecería ser una cadena. Esto estropearía Maps en todas partes.
Considere este código hipotético:
Este es un problema con el uso de una clave mutable en general con un Mapa, pero lo que intento encontrar allí es que de repente hay varias cosas sobre la ruptura del Mapa. La entrada ya no está en el lugar correcto en el mapa. En un HashMap, el valor hash ha (debería haber) cambiado y, por lo tanto, ya no está en la entrada correcta. En TreeMap, el árbol ahora está roto porque uno de los nodos está en el lado equivocado.
Dado que el uso de una cadena para estas claves es muy común, este comportamiento debe evitarse haciendo que la cadena sea final.
Te puede interesar leer ¿Por qué String es inmutable en Java? para más información sobre la naturaleza inmutable de Strings.
Cuerdas nefastas
Hay varias opciones nefastas para las cadenas. Considere si hice una Cadena que siempre devuelve verdadero cuando se llamó a igual ... y la pasé a una verificación de contraseña. ¿O para que las asignaciones a MyString envíen una copia de la cadena a alguna dirección de correo electrónico?
Estas son posibilidades muy reales cuando tienes la capacidad de subclasificar String.
Optimizaciones de cadenas Java.lang
Mientras que antes mencioné que la final no hace que String sea más rápido. Sin embargo, la clase String (y otras clases en
java.lang
) hacen uso frecuente de la protección a nivel de paquete de campos y métodos para permitir que otrasjava.lang
clases puedan jugar con los elementos internos en lugar de pasar por la API pública para String todo el tiempo. Funciones como getChars sin comprobación de rango o lastIndexOf que utiliza StringBuffer, o el constructor que comparte la matriz subyacente (tenga en cuenta que eso es algo de Java 6 que se modificó debido a problemas de memoria).Si alguien hiciera una subclase de String, no podría compartir esas optimizaciones (a menos que también fuera parte de él
java.lang
, pero ese es un paquete sellado ).Es más difícil diseñar algo para la extensión
Diseñar algo para que sea extensible es difícil . Significa que debe exponer partes de sus componentes internos para que otra cosa pueda modificar.
Una cadena extensible no podría haber reparado su pérdida de memoria . Esas partes de él tendrían que haber estado expuestas a subclases y cambiar ese código significaría que las subclases se romperían.
Java se enorgullece de su compatibilidad con versiones anteriores y al abrir las clases principales a la extensión, uno pierde parte de esa capacidad para arreglar las cosas y al mismo tiempo mantiene la computabilidad con subclases de terceros.
Checkstyle tiene una regla que impone (que realmente me frustra al escribir código interno) llamada "DesignForExtension" que exige que cada clase sea:
El racional para el cual es:
Permitir que las clases de implementación se extiendan significa que las subclases posiblemente pueden corromper el estado de la clase en la que se basa y hacer que las diversas garantías que brinda la superclase no sean válidas. Para algo tan complejo como la cadena, es casi una certeza que el cambio de parte de ella va a romper algo.
Desarrollador hurbis
Es parte de ser un desarrollador. Considere la probabilidad de que cada desarrollador cree su propia subclase de String con su propia colección de utilidades . Pero ahora estas subclases no pueden asignarse libremente entre sí.
Este camino lleva a la locura. Lanzando a String por todas partes y verificando si String es una instancia de su clase String o si no, creando una nueva String basada en esa y ... simplemente, no. No lo hagas
Estoy seguro de que puede escribir una clase String bueno ... pero dejo de escribir múltiples implementaciones de cuerda a esos locos que escriben en C ++ y tienen que tratar con
std::string
ychar*
y algo de impulso y sString , y todo el resto .Java String magic
Hay varias cosas mágicas que Java hace con Strings. Esto hace que sea más fácil para un programador tratar, pero introduce algunas inconsistencias en el lenguaje. Permitir subclases en String requeriría un pensamiento muy significativo sobre cómo lidiar con estas cosas mágicas:
Literales de cadena (JLS 3.10.5 )
Tener un código que le permita a uno hacer:
Esto no debe confundirse con el boxeo de los tipos numéricos como Integer. No puedes hacer
1.toString()
, pero puedes hacer"foo".concat(bar)
.El
+
operador (JLS 15.18.1 )Ningún otro tipo de referencia en Java permite que se use un operador en él. La cadena es especial. El operador de concatenación de cadenas también funciona en el nivel del compilador, por lo que se
"foo" + "bar"
convierte"foobar"
cuando se compila en lugar de en tiempo de ejecución.Conversión de cadenas (JLS 5.1.11 )
Todos los objetos se pueden convertir en cadenas simplemente usándolos en un contexto de cadena.
Cadena internar ( JavaDoc )
La clase String tiene acceso al grupo de Strings que le permite tener representaciones canónicas del objeto que se rellena en el tipo de compilación con los literales de String.
Permitir una subclase de Cadena significaría que estos bits con la Cadena que facilitan la programación se volverán muy difíciles o imposibles de hacer cuando otros tipos de Cadena sean posibles.
fuente
Si String no fuera definitivo, cada cofre de herramientas del programador contendría su propio "String con solo unos pocos métodos de ayuda". Cada uno de estos sería incompatible con todos los demás cofres de herramientas.
Lo consideré una restricción tonta. Hoy estoy convencido de que fue una decisión muy acertada. Mantiene las cadenas para ser cadenas.
fuente
final
en Java no es lo mismo quefinal
en C #. En este contexto, es lo mismo que C # 'sreadonly
. Ver stackoverflow.com/questions/1327544/…public final class String
.