Método Java `final`: ¿qué promete?

141

En una clase Java, se puede definir un método para que sea final, para marcar que este método no se puede anular:

public class Thingy {
    public Thingy() { ... }
    public int operationA() {...}
    /** this method does @return That and is final. */
    public final int getThat() { ...}
}

Eso está claro, y puede ser útil para proteger contra la anulación accidental, o tal vez el rendimiento, pero esa no es mi pregunta.

Mi pregunta es: desde el punto de vista de OOP, entendí que, al definir un método, finalel diseñador de la clase promete que este método siempre funcionará según lo descrito o implícito. Pero a menudo esto puede estar fuera de la influencia del autor de la clase, si lo que está haciendo el método es más complicado que simplemente entregar una propiedad .

La restricción sintáctica es clara para mí, pero ¿cuál es la implicación en el sentido OOP? ¿Se finalusa correctamente en este sentido por la mayoría de los autores de clase?

¿Qué tipo de "contrato" finalpromete un método?

Towi
fuente

Respuestas:

155

Como se mencionó, finalse usa con un método Java para marcar que el método no puede ser anulado (para el alcance del objeto) u oculto (para estático). Esto permite al desarrollador original crear funcionalidades que no pueden ser cambiadas por subclases, y esa es toda la garantía que proporciona.

Esto significa que si el método se basa en otros componentes personalizables, como los campos / métodos no públicos, la funcionalidad del método final aún puede ser personalizable. Sin embargo, esto es bueno ya que (con polimorfismo) permite una personalización parcial.

Hay varias razones para evitar que algo sea personalizable, que incluyen:

  • Rendimiento : algunos compiladores pueden analizar y optimizar la operación, especialmente la que no tiene efectos secundarios.

  • Obtenga datos encapsulados : observe los Objetos inmutables donde sus atributos se establecen en el momento de la construcción y nunca se deben cambiar. O un valor calculado derivado de esos atributos. Un buen ejemplo es la Stringclase Java .

  • Fiabilidad y Contrato - Objetos están compuestas de primitivas ( int, char, double, etc.) y / u otros objetos. No todas las operaciones aplicables a esos componentes deberían ser aplicables o incluso lógicas cuando se usan en el Objeto más grande. Se finalpueden utilizar métodos con el modificador para garantizar eso. La clase Counter es un buen ejemplo.


public class Counter {
    private int counter = 0;

    public final int count() {
        return counter++;
    }

    public final int reset() {
        return (counter = 0);
    }
}

Si el public final int count()método no es final, podemos hacer algo como esto:

Counter c = new Counter() {   
    public int count() {
        super.count();   
        return super.count();   
    } 
}

c.count(); // now count 2

O algo como esto:

Counter c = new Counter() {
    public int count() {
        int lastCount = 0;
        for (int i = super.count(); --i >= 0; ) {
            lastCount = super.count();
        }

        return lastCount;
    }
}

c.count(); // Now double count
NawaMan
fuente
27

¿Qué tipo de "contrato" promete un método final?

Míralo de otra manera, cualquier método no final hace la garantía implícita de que puedes anularlo con tu propia implementación y la clase seguirá funcionando como se esperaba. Cuando no pueda garantizar que su clase admite sobrescribir un método, debe hacerlo final.

josefx
fuente
¿Pero esta opinión no implica a mi pregunta original "este método final siempre se comportará como se prometió" que no debo llamar a ningún método no final desde dentro de un método final? ¿Porque si hago el método llamado puede haber sido anulado y, por lo tanto, no puedo garantizar el comportamiento de mi método final?
Towi
8

En primer lugar, puede marcar clases no abstractas final, así como campos y métodos. De esta manera, toda la clase no se puede subclasificar. Entonces, el comportamiento de la clase será arreglado.

Estoy de acuerdo en que los métodos de marcado finalno garantizan que su comportamiento sea el mismo en las subclases si estos métodos están llamando a métodos no finales. Si es necesario corregir el comportamiento, esto debe lograrse por convención y por un diseño cuidadoso. ¡Y no olvides mencionar esto en javadoc! (Documentación de java)

Por último, pero no menos importante, la finalpalabra clave tiene un papel muy importante en Java Memory Model (JMM). JMM garantiza que para lograr la visibilidad de los finalcampos no necesita una sincronización adecuada. P.ej:

class A implements Runnable {
  final String caption = "Some caption";                           

  void run() {
    // no need to synchronize here to see proper value of final field..
    System.out.println(caption);
  }
}  
Victor Sorokin
fuente
Sí, sé sobre las clases finales. Caso fácil. Buen punto sobre los campos finales con JMM, no se necesita sincronización ... hmm: esto solo se refiere al "puntero", ¿verdad? Todavía podría modificar el objeto al que hace referencia fuera de sincronización (ok, no dentro String, sino en clases definidas por el usuario). Pero lo que usted dijo sobre "final no garantiza el comportamiento" es exactamente mi punto. Estoy de acuerdo, el documento y el diseño son importantes.
Towi
@towi usted razón en que finalno va a asegurar la visibilidad de los cambios realizados en los objetos compuestos, tales como Map.
Victor Sorokin
0

No estoy seguro de que pueda hacer ninguna afirmación sobre el uso de "final" y cómo eso afecta el contrato de diseño general del software. Le garantizamos que ningún desarrollador puede anular este método y anular su contrato de esa manera. Pero, por otro lado, el método final puede basarse en variables de clase o instancia cuyos valores se establecen mediante subclases, y puede llamar a otros métodos de clase que se anulan. Por lo tanto, la final es, como mucho, una garantía muy débil.

Jim Ferrans
fuente
1
Sí, a eso me refería. Correcto. Me gusta el término "garantía débil" :-) Y (principalmente) me gustan los C ++ const. Como en char const * const = "Hello"o char const * const addName(char const * const name) const...
towi
0

No, no está fuera de la influencia del autor de la clase. No puede anularlo en su clase derivada, por lo tanto, hará lo que el autor de la clase base pretendía.

http://download.oracle.com/javase/tutorial/java/IandI/final.html

Vale la pena señalar que es la parte donde sugiere que los métodos llamados desde los constructores deberían ser final.

Brian Roach
fuente
1
Bueno, técnicamente hará lo que escribió el autor de la clase base .
Joey