Un finalizador , en lenguajes de programación orientados a objetos que usan recolección de basura , es un método especial llamado por el tiempo de ejecución antes de que el recolector de basura elimine un objeto.
Un finalizador es un método de clase que el tiempo de ejecución llama automáticamente entre el momento en que el recolector de elementos no utilizados reconoce un objeto de esa clase como no utilizado y el momento en que se elimina el objeto (se libera la memoria que ocupa). Un finalizador para un objeto en particular siempre se ejecuta después de que el programa deja de usar el objeto y antes de que el recolector de basura libere la memoria del objeto. Es conveniente pensar que el finalizador se llama justo antes de que el objeto se elimine de la memoria, aunque esto no suele estar garantizado.
En la superficie, un finalizador es similar a un destructor de clases , pero en realidad, el efecto y el alcance de estos métodos son bastante diferentes. La diferencia se debe al hecho de que el momento en que se llama al finalizador, a diferencia del destructor, no está rígidamente definido: el finalizador siempre se llama antes de que el recolector de basura destruya el objeto, pero el momento de destrucción depende del modo de funcionamiento del recolector de basura, la cantidad de RAM disponible y la actividad de uso de memoria del programa. Por lo tanto, si hay poca memoria libre y la creación de nuevos objetos ocurre constantemente, la necesidad de recolección de basura ocurre con frecuencia y, en consecuencia, es muy probable que se llame al finalizador poco después de que el objeto ya no se use. Si hay mucha memoria y su consumo por parte del programa es pequeño, puede pasar mucho tiempo desde que finaliza el uso del objeto hasta la recolección de basura (y la llamada del finalizador). Además, si hay mucha memoria y se crean pocos o ningún objeto nuevo, es posible que no se llame al recolector de basura y, al final del programa, toda la memoria asignada a él simplemente se devolverá al sistema operativo. sistema; en este caso, es posible que no se llame al finalizador.
Si bien los destructores se usan muy a menudo para liberar recursos escasos del sistema (como el acceso a un archivo o hardware ) ocupados por un objeto, los finalizadores, debido a las características mencionadas anteriormente, generalmente no se recomiendan para usarse de esta manera. Por supuesto, el finalizador puede cerrar el archivo o decirle al sistema operativo que el dispositivo ya no está en uso, sin embargo, puede pasar una cantidad indefinida de tiempo desde el momento en que el objeto ya no se usa hasta el momento en que se llama al finalizador, y todo este tiempo los recursos ocupados por el objeto no serán utilizados, sino que permanecerán ocupados. [una]
Los finalizadores son impredecibles, a menudo peligrosos y, a menudo, innecesarios.
—Joshua Bloch. Java efectivo. Addison-Westley, 2001.Como resultado de lo anterior, el uso de finalizadores es muy limitado. Los lenguajes recolectados en basura usan el patrón de diseño "dispose" para desasignar recursos . El lenguaje de programación C# admite implícitamente el patrón "dispose" a través de la interfaz IDisposable y la palabra clave using, y Java 7 introdujo un mecanismo similar de "probar con recursos".
Uno de los raros casos en los que un finalizador es realmente necesario es cuando una clase implementa sus propios mecanismos de manejo de memoria que se basan en código de terceros no administrado por el sistema de recolección de basura, por ejemplo, cuando una clase Java usa código escrito en C para lograr máxima eficiencia o realizar operaciones de bajo nivel. Para que el código externo funcione, la memoria debe asignarse utilizando mecanismos C estándar (malloc) y liberarse con su propia ayuda (gratis). Puede (y generalmente debe) llamar a la función de asignación de memoria en el constructor de clases, y el lugar correcto para llamar a la función de desasignación de memoria externa es justo en el finalizador, ya que esta ubicación garantiza que la memoria para el código externo se asigne antes que el objeto. se usa (cuando se creó) y se libera solo cuando cesa su uso. Si el finalizador no se llama de inmediato o incluso no se llama en absoluto, no sucederá nada malo, ya que la memoria externa asignada aún se devolverá al sistema automáticamente después de que finalice el programa.
Otra buena manera de usar un finalizador es asegurarse de que un objeto se limpie antes de eliminarlo. Si un objeto captura recursos valiosos del sistema que no sean la memoria durante la creación o en el curso de su operación (abre archivos o canales de comunicación, se conecta a dispositivos de E / S), entonces, obviamente, en el momento en que el recolector de basura elimina el objeto, todos estos recursos ya deberían estar liberados (entonces hay un objeto para borrar). Los errores de limpieza (cuando un objeto en algunas situaciones no se limpia o, peor aún, no se limpia por completo) son muy insidiosos, son difíciles de detectar, ya que aparecen al ejecutar una parte del código completamente diferente donde estaba el error. hecha. Como ya se mencionó, no es aconsejable limpiar en el finalizador, ya que no se sabe cuándo se llamará y si se llamará en absoluto. Pero en el finalizador, es muy apropiado y conveniente verificar si el objeto está completamente borrado y emitir, de una forma u otra, un mensaje de error si algún recurso permanece capturado. No importa que el finalizador sea llamado tarde y no siempre; de todos modos, si hay un error de limpieza de objetos, tarde o temprano el finalizador lo "atrapará".
Los finalizadores se pueden crear de diferentes maneras. En algunos idiomas, el finalizador forma parte de la biblioteca estándar. Por lo general, en tales casos, es un método virtual de la clase raíz estándar, de la cual todas las demás clases en el sistema son descendientes (en Java, este es el método finalize () de la clase Object). Los finalizadores también se pueden declarar usando una sintaxis especial. En C# , la sintaxis para declarar un finalizador se toma prestada de los destructores de C++ : el finalizador de la clase Class se convierte en el método con la firma ~Class(). El lenguaje Nemerle , que se basa en C#, abandonó esta sintaxis porque se consideró propensa a errores.