¿Buenas razones para prohibir la herencia en Java?

178

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

Cretzel
fuente
1
Pedantry: el constructor predeterminado es el generado para usted por el compilador de Java si no escribe explícitamente uno en su código. Te refieres al constructor sin argumentos.
John Topley
¿No es ese el "constructor implícito por defecto"?
Cretzel
2
"No tiene que proporcionar ningún constructor para su clase, pero debe tener cuidado al hacerlo. El compilador proporciona automáticamente un constructor predeterminado sin argumentos para cualquier clase sin constructores". java.sun.com/docs/books/tutorial/java/javaOO/constructors.html - John Topley
John Topley

Respuestas:

184

Su mejor referencia aquí es el Artículo 19 del excelente libro de Joshua Bloch "Java eficaz", llamado "Diseñe y documente la herencia o prohíbala". (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 antepasado no fue diseñado para ser heredado.

Por lo tanto, las clases deben venir en dos tipos:

  1. Clases diseñadas para extenderse y con suficiente documentación para describir cómo debe hacerse

  2. Clases marcadas final

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, entonces un futuro codificador siempre puede eliminar el 'final'; puede pensarlo como una advertencia que dice "esta clase no fue diseñada teniendo en cuenta la herencia".

DJClayworth
fuente
66
En mi experiencia, esto no es excesivo si alguien además de mí usará el código. Nunca he entendido por qué Java tiene métodos reemplazables por defecto.
Eric Weilnau
9
Para comprender algo de Java eficaz, debe comprender la versión de diseño de Josh. Él dice [algo así como] siempre debe diseñar interfaces de clase como si fueran una API pública. Creo que la opinión de la mayoría es que este enfoque suele ser demasiado pesado e inflexible. (YAGNI, etc.)
Tom Hawtin - tackline
9
Buena respuesta. (No puedo resistirme a señalar que son seis caracteres, ya que también hay un espacio ... ;-)
joel.neely
36
Tenga en cuenta, que hacen clases de fuerza definitiva que el análisis sea más duro (más difícil de talón o simulado)
notnoop
10
Si la clase final está escribiendo en una interfaz, la burla no debería ser un problema.
Fred Haslam
26

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

Bill el lagarto
fuente
No entendí bien esta respuesta. Si no le importa, ¿podría explicar qué quiere decir con "anular las clases no puede cambiar el comportamiento que se cuenta con otros métodos"? En primer lugar, pregunto porque no entendí la oración, y en segundo lugar porque Netbeans recomienda que una de mis funciones que llamo desde el constructor sea final.
Nav
1
@Nav Si hace que un método sea final, entonces una clase superior no podrá tener su propia versión de ese método (o será un error de compilación). De esa manera, sabrá que el comportamiento que implemente en ese método no cambiará más adelante cuando alguien más extienda la clase.
Bill the Lizard
19

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.

John Topley
fuente
15

Hay 3 casos de uso en los que puede recurrir a los métodos finales.

  1. Para evitar que la clase derivada anule una funcionalidad de clase base particular.
  2. Esto es por razones de seguridad, donde la clase base está dando una funcionalidad básica importante del marco donde la clase derivada no debe cambiarla.
  3. Los métodos finales son más rápidos que los métodos de instancia, ya que no se utiliza el concepto de tabla virtual para los métodos finales y privados. Entonces, siempre que exista la 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: clase Wrapper Integer es una clase final. Si esa clase no es final, cualquiera puede extender Integer a su propia clase y cambiar el comportamiento básico de la clase entera. Para evitar esto, Java hizo todas las clases de contenedor como clases finales.

usuario1923551
fuente
No muy convencido con tu ejemplo. Si ese es el caso, ¿por qué no hacer que los métodos y las variables sean finales en lugar de hacer que toda la clase sea final? ¿Puedes editar tu respuesta con los detalles?
Rajkiran
44
Incluso si hace que todas las variables y métodos sean finales, otros pueden heredar su clase y agregar funcionalidad adicional y cambiar el comportamiento básico de su clase.
user1923551
3
> Si esa clase no es final, cualquiera puede extender Integer a su propia clase y cambiar el comportamiento básico de la clase entera. Digamos que esto se permitió y se llamó a esta nueva clase DerivedInteger, todavía no cambia la clase Integer original, y quien lo use lo DerivedIntegerhace bajo su propio riesgo, por lo que todavía no entiendo por qué es un problema.
Siddhartha
7

La herencia es como una motosierra: muy poderosa, pero horrible en las manos equivocadas. O diseñas una clase de la que se hereda (lo que puede limitar la flexibilidad y tomar mucho más tiempo) o deberías prohibirla.

Consulte los artículos 16 y 17 de Java 2nd Edition efectivos, o la publicación de mi blog "Impuesto de sucesiones" .

Jon Skeet
fuente
El enlace a la publicación del blog está roto. Además, ¿crees que podrías elaborar un poco más sobre esto?
@MichaelT: Se corrigió el enlace, gracias, sígalo para más detalles :) (No tengo tiempo para agregar a esto ahora, me temo)
Jon Skeet
@ JonSkeet, el enlace a la publicación del blog está roto.
Lucifer
@Kedarnath: Funciona para mí ... estuvo roto por un tiempo, pero ahora apunta a la página correcta, en codeblog.jonskeet.uk ...
Jon Skeet
4

Hmmm ... puedo pensar en dos cosas:

Es posible que tenga una clase que se ocupe de ciertos problemas de seguridad. Al subclasificarlo y alimentar su sistema con la versión subclasificada, un atacante puede eludir las restricciones de seguridad. Por ejemplo, su aplicación puede admitir complementos y si un complemento puede simplemente subclasificar sus clases relevantes de seguridad, puede usar este truco para de alguna manera pasar de contrabando una versión subclasificada. Sin embargo, esto es algo con lo que Sun tiene que lidiar con respecto a los applets y similares, 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 guardar referencias de manera segura

 String blah = someOtherString;

en lugar de copiar la cadena primero. Sin embargo, si puede subclasificar String, puede agregarle métodos que permitan modificar el valor de la cadena, ahora ya no puede confiar en que la cadena se mantendrá igual si solo copia la cadena como se indicó anteriormente, en su lugar, debe duplicar cuerda.

Mecki
fuente
2

Además, si está escribiendo una clase comercial de código cerrado, es posible que no desee que las personas puedan cambiar la funcionalidad en el futuro, especialmente si necesita brindarle soporte y las personas han anulado su método y se quejan de que llamarlo da resultados inesperados

DavidG
fuente
2

Si marca las clases y los métodos como finales, puede notar 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 ampliarse adecuadamente si es necesario, los métodos finales se pueden vincular directamente o compilar en línea en la clase.

sean todos juntos
fuente
1
Creo que esto es un mito, ya que las máquinas virtuales de la generación actual deberían poder optimizar y desoptimizar según sea necesario. Recuerdo haber visto algunos bencharmking esto y clasificarlo como un mito.
Miguel Ping
1
La diferencia en la generación de código no es un mito, pero quizás lo sean las ganancias de rendimiento. Como dije es una pequeña ganancia, un sitio mencionó una ganancia de rendimiento del 3.5%, algo más alto, que en la mayoría de los casos no vale la pena ir a todos los nazis en su código.
seantodostodos
1
No es un mito De hecho, el compilador solo puede incorporar V métodos finales. No estoy seguro de si algún JIT "en línea" crea arte en tiempo de ejecución, pero dudo que pueda, ya que puede estar cargando una clase derivada más tarde.
Uri
1
Microbenchmark == grano de sal. Dicho esto, después del calentamiento del ciclo 10B, un promedio de más de 10B llamadas no muestra esencialmente ninguna diferencia bajo Eclipse en mi computadora portátil. Dos métodos que devuelven String, el final fue 6.9643us, el no final fue 6.9641us. Ese delta es ruido de fondo, en mi humilde opinión.
joel.neely
1

Para evitar que las personas hagan cosas que puedan confundir a sí mismas 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.

Anson Smith
fuente
2
Hay algo que no entiendo sobre ese argumento: si alguien cambió esos cálculos / constantes que no deberían cambiar, ¿no hay fallas debido a que es 100% culpa suya? En otras palabras, ¿cómo beneficia algo el cambio?
mate b
Sí, técnicamente es "su" culpa, pero imagine que otra persona que usa la biblioteca que no se ha dado cuenta de los cambios, podría generar confusión. Siempre pensé que esa era la razón por la cual muchas de las clases de Java Base se marcaron como finales, para evitar que las personas alteren la funcionalidad básica.
Anson Smith
@matt b: parece estar asumiendo que el desarrollador que realizó el cambio lo hizo a sabiendas o que les importaría que fuera su culpa. Si alguien además de mí usará mi código, lo marcaré como final a menos que esté destinado a ser cambiado.
Eric Weilnau
Este es en realidad un uso diferente de 'final' del que se le preguntó.
DJClayworth
Esta respuesta parece confundir la herencia y la mutabilidad de los campos individuales con la capacidad de escribir una subclase. Puede marcar campos y métodos individuales como finales (evitando su redefinición) sin prohibir la herencia.
joel.neely
0

Desea hacer un método final para que anular clases no cambie su comportamiento. Cuando desee poder cambiar el comportamiento, haga público el método. Cuando anula un método público, se puede cambiar.

Alexander Robinson
fuente