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:
Map t = new TreeMap<String, Integer>();
Map h = new HashMap<String, Integer>();
MyString one = new MyString("one");
MyString two = new MyString("two");
t.put(one, 1); h.put(one, 1);
t.put(two, 2); h.put(two, 2);
one.prepend("z");
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 otras java.langclases 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:
- Resumen
- Final
- Implementación vacía
El racional para el cual es:
Este estilo de diseño de API protege las superclases de las subclases. La desventaja es que las subclases tienen una flexibilidad limitada, en particular no pueden evitar la ejecución de código en la superclase, pero eso también significa que las subclases no pueden corromper el estado de la superclase olvidando llamar al método super.
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í.
WleaoString foo = new WleaoString("foo");
MichaelTString bar = foo; // This doesn't work.
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::stringy char*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:
String foo = "foo";
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.