¿Buenas razones para prohibir la herencia en Java?

¿Cuáles son buenas razones para prohibir la herencia en Java, por ejemplo, mediante el uso de clases finales o clases usando un único constructor privado sin parámetros? ¿Cuáles son buenas razones para hacer que un método sea definitivo?

Su mejor referencia aquí es el artículo 19 del excelente libro de Joshua Bloch “Effective Java”, llamado “Diseño y documento para la herencia o, de lo contrario, prohíba”. (Es el ítem 17 en la segunda edición y el ítem 15 en la primera edición). Realmente deberías leerlo, pero lo resumiré.

La interacción de las clases heredadas con sus padres puede ser sorprendente e impredecible si el ancestro no fue diseñado para ser heredado.

Por lo tanto, las clases deben ser de dos tipos:

  1. Clases diseñadas para ser extendidas , y con suficiente documentación para describir cómo se debe hacer

  2. Clases marcadas finales

Si está escribiendo código puramente interno, esto puede ser un poco exagerado. Sin embargo, el esfuerzo adicional que implica agregar cinco caracteres a un archivo de clase es muy pequeño. Si está escribiendo solo para consumo interno, un codificador futuro siempre puede eliminar el ‘final’; puede pensarlo como una advertencia que dice “esta clase no fue diseñada con herencia en mente”.

Es posible que desee hacer que un método sea definitivo para que las clases superiores no puedan cambiar el comportamiento que se cuenta en otros métodos. Los métodos llamados en los constructores a menudo se declaran definitivos para que no tenga sorpresas desagradables al crear objetos.

Una razón para hacer una clase final sería si quisieras forzar la composición sobre la herencia. Esto es generalmente deseable para evitar un acoplamiento estrecho entre clases.

es posible que desee crear objetos inmutables ( http://en.wikipedia.org/wiki/Immutable_object ), es posible que desee crear un singleton ( http://en.wikipedia.org/wiki/Singleton_pattern ), o puede querer para evitar que alguien anule el método por razones de eficiencia, seguridad o seguridad.

Hay 3 casos de uso donde puede ir para los métodos finales.

  1. Para evitar que la clase derivada invalide una funcionalidad particular de la clase base.
  2. Esto es por razones de seguridad, donde la clase base proporciona una funcionalidad central importante del marco donde no se supone que la clase derivada la modifique.
  3. Los métodos finales son más rápidos que los métodos de instancia, ya que no se usa el concepto de tabla virtual para los métodos finales y privados. Entonces, donde sea que haya una posibilidad, intente utilizar los métodos finales.

Propósito para hacer una clase final:

Para que ningún cuerpo pueda extender esas clases y cambiar su comportamiento.

Por ejemplo: la clase Wrapper Integer es una clase final. Si esa clase no es definitiva, cualquiera puede extender entero en su propia clase y cambiar el comportamiento básico de la clase entera. Para evitar esto, Java hizo todas las clases contenedoras como clases finales.

La herencia es como una motosierra: muy poderosa, pero horrible en las manos equivocadas. O bien diseñas una clase para heredarla (lo que puede limitar la flexibilidad y tardas mucho más) o debes prohibirla.

Consulte los artículos 16 y 17 de Effective Java 2nd edition, o la publicación de mi blog “Inheritance Tax” .

Hmmm … puedo pensar en dos cosas:

Es posible que tenga una clase que se ocupa de ciertos problemas de seguridad. Al crear una subclase y alimentar a su sistema con la versión subclase, un atacante puede eludir las restricciones de seguridad. Por ejemplo, su aplicación podría admitir complementos y si un complemento puede simplemente subclasificar sus clases de seguridad relevantes, puede usar este truco para pasar de contrabando una versión subclasificada de ella a su lugar. Sin embargo, esto es más bien algo con lo que Sun tiene que lidiar con respecto a los applets y cosas por el estilo, tal vez no sea un caso tan realista.

Una mucho más realista es evitar que un objeto se vuelva mutable. Por ejemplo, dado que las cadenas son inmutables, su código puede mantener referencias a él de manera segura

  String blah = someOtherString; 

en lugar de copiar la cadena primero. Sin embargo, si puede subclase String, puede agregarle métodos que permitan modificar el valor de la cadena, ahora ningún código puede confiar más en que la cadena se mantendrá igual si solo copia la cadena como se indica anteriormente, en su lugar debe duplicar el cuerda.

Para evitar que las personas hagan cosas que puedan confundirse a ellos mismos y a los demás. Imagine una biblioteca de física donde tiene algunas constantes o cálculos definidos. Sin usar la palabra clave final, alguien podría venir y redefinir cálculos básicos o constantes que NUNCA deberían cambiar.

Además, si está escribiendo una clase de fuente cerrada comercial, es posible que no desee que las personas puedan cambiar la funcionalidad más adelante, especialmente si necesita dar soporte y las personas han anulado su método y se quejan de que llamar le da resultados inesperados.

Si marca las clases y los métodos como finales, puede observar una pequeña ganancia de rendimiento, ya que el tiempo de ejecución no tiene que buscar el método de clase correcto para invocar un objeto determinado. Los métodos no finales se marcan como virtuales para que puedan extenderse adecuadamente si es necesario, los métodos finales se pueden vincular directamente o comstackr en línea en la clase.

Desea hacer que un método sea definitivo para que anular las clases no modifique su comportamiento. Cuando desee poder cambiar el comportamiento, haga público el método. Cuando anula un método público, puede cambiarse.