pass - ¿Por qué debería usar la palabra clave "final" en un parámetro de método en Java?




java pass object by reference (8)

Detener la reasignación de una variable

Si bien estas respuestas son intelectualmente interesantes, no he leído la respuesta corta y simple:

Use la palabra clave final cuando desee que el compilador impida que una variable se reasigne a un objeto diferente.

Si la variable es una variable estática, variable miembro, variable local o variable de argumento / parámetro, el efecto es totalmente el mismo.

Ejemplo

Veamos el efecto en acción.

Considere este método simple, donde las dos variables ( arg y x ) pueden ser reasignadas a diferentes objetos.

// Example use of this method: 
//   this.doSomething( "tiger" );
void doSomething( String arg ) {
  String x = arg;   // Both variables now point to the same String object.
  x = "elephant";   // This variable now points to a different String object.
  arg = "giraffe";  // Ditto. Now neither variable points to the original passed String.
}

Marque la variable local como final . Esto resulta en un error de compilación.

void doSomething( String arg ) {
  final String x = arg;  // Mark variable as 'final'.
  x = "elephant";  // Compiler error: The final local variable x cannot be assigned. 
  arg = "giraffe";  
}

En su lugar, vamos a marcar la variable de parámetro como final . Esto también resulta en un error del compilador.

void doSomething( final String arg ) {  // Mark argument as 'final'.
  String x = arg;   
  x = "elephant"; 
  arg = "giraffe";  // Compiler error: The passed argument variable arg cannot be re-assigned to another object.
}

Moraleja de la historia:

Si desea asegurarse de que una variable siempre apunta al mismo objeto, marque la variable final .

Nunca reasignar argumentos

Como buena práctica de programación (en cualquier lenguaje), nunca debe volver a asignar una variable de parámetro / argumento a un objeto que no sea el objeto pasado por el método de llamada. En los ejemplos anteriores, uno nunca debe escribir la línea arg = . Como los humanos cometen errores y los programadores son humanos, pidamos al compilador que nos ayude. Marque cada variable de parámetro / argumento como 'final' para que el compilador pueda encontrar y marcar cualquiera de estas reasignaciones.

En retrospectiva

Como se señaló en otras respuestas ... Dado el objetivo de diseño original de Java de ayudar a los programadores a evitar errores tontos, como leer más allá del final de una matriz, Java debería haber sido diseñado para imponer automáticamente todas las variables de parámetro / argumento como "final". En otras palabras, los argumentos no deben ser variables . Pero la visión retrospectiva es una visión 20/20, y los diseñadores de Java estaban muy ocupados en ese momento.

Otro caso agregado para la integridad.

public class MyClass {
    private int x;
    //getters and setters
}

void doSomething( final MyClass arg ) {  // Mark argument as 'final'.

   arg =  new MyClass();  // Compiler error: The passed argument variable arg  cannot be re-assigned to another object.

   arg.setX(20); // allowed
  // We can re-assign properties of argument which is marked as final
 }

No puedo entender dónde está realmente útil la palabra clave final cuando se utiliza en los parámetros del método.

Si excluimos el uso de clases anónimas, la legibilidad y la declaración de intenciones, entonces me parece casi inútil.

Hacer que algunos datos permanezcan constantes no es tan fuerte como parece.

  • Si el parámetro es primitivo, no tendrá ningún efecto, ya que el parámetro se pasa al método como un valor y cambiarlo no tendrá ningún efecto fuera del alcance.

  • Si estamos pasando un parámetro por referencia, entonces la referencia en sí misma es una variable local y si la referencia se cambia desde dentro del método, eso no tendría ningún efecto desde fuera del alcance del método.

Considere el ejemplo de prueba simple a continuación. Esta prueba pasa aunque el método cambió el valor de la referencia que se le dio, no tiene ningún efecto.

public void testNullify() {
    Collection<Integer> c  = new ArrayList<Integer>();      
    nullify(c);
    assertNotNull(c);       
    final Collection<Integer> c1 = c;
    assertTrue(c1.equals(c));
    change(c);
    assertTrue(c1.equals(c));
}

private void change(Collection<Integer> c) {
    c = new ArrayList<Integer>();
}

public void nullify(Collection<?> t) {
    t = null;
}

A veces es bueno ser explícito (para facilitar la lectura) que la variable no cambia. Aquí hay un ejemplo simple donde usar final puede ahorrar algunos posibles dolores de cabeza

public void setTest(String test) {
    test = test;
}

si olvida la palabra clave 'this' en un setter, la variable que desea configurar no se establece. Sin embargo, si utilizó la palabra clave final en el parámetro, el error se detectaría en el momento de la compilación.


El uso de final en un parámetro de método no tiene nada que ver con lo que sucede con el argumento en el lado de la persona que llama. Solo tiene la intención de marcarlo como que no cambia dentro de ese método. Cuando trato de adoptar un estilo de programación más funcional, veo el valor en eso.


Nunca uso final en una lista de parámetros, solo agrega el desorden como los encuestados anteriores han dicho. También en Eclipse puede configurar la asignación de parámetros para generar un error, por lo que usar final en una lista de parámetros me parece bastante redundante. Curiosamente, cuando habilité la configuración de Eclipse para la asignación de parámetros que generó un error, detecté este código (así es como recuerdo el flujo, no el código real):

private String getString(String A, int i, String B, String C)
{
    if (i > 0)
        A += B;

    if (i > 100)
        A += C;

    return A;
}

Jugando al abogado del diablo, ¿qué es exactamente lo que está mal al hacer esto?


Respuesta corta: la versión final ayuda un poquito pero, en su lugar, use programación defensiva en el lado del cliente.

De hecho, el problema con final es que solo impone que la referencia no se modifique, lo que permite con alegría que los miembros del objeto al que se hace referencia sean mutados, sin que el autor de la llamada lo sepa. Por lo tanto, la mejor práctica a este respecto es la programación defensiva en el lado de la persona que llama, creando instancias profundamente inmutables o copias profundas de objetos que están en peligro de ser asaltados por API sin escrúpulos.


Sí, excluyendo clases anónimas, legibilidad y declaración de intenciones es casi inútil. ¿Son esas tres cosas sin valor sin embargo?

Personalmente, tiendo a no usar final para variables y parámetros locales a menos que use la variable en una clase interna anónima, pero ciertamente puedo ver el punto de aquellos que quieren dejar en claro que el valor del parámetro en sí no cambiará ( incluso si el objeto al que se refiere cambia su contenido). Para aquellos que descubren que eso se suma a la legibilidad, creo que es algo completamente razonable.

Su punto sería más importante si alguien realmente afirmara que mantuvo los datos constantes de una manera que no lo hace, pero no recuerdo haber visto tales afirmaciones. ¿Estás sugiriendo que hay un grupo significativo de desarrolladores que sugieren que final tiene más efecto que lo que realmente tiene?

EDITAR: Realmente debería haber resumido todo esto con una referencia de Monty Python; la pregunta parece algo similar a preguntar "¿Qué han hecho los romanos por nosotros?"


Una razón adicional para agregar declaraciones finales a los parámetros es que ayuda a identificar las variables que necesitan ser renombradas como parte de una refactorización "Extraer método". Descubrí que agregar un parámetro final a cada parámetro antes de comenzar una refactorización de método grande rápidamente me dice si hay algún problema que deba resolver antes de continuar.

Sin embargo, generalmente los elimino como superfluos al final de la refactorización.


Uso final todo el tiempo en parámetros.

¿Agrega eso mucho? Realmente no.

¿Lo apagaría? No.

La razón: encontré 3 errores en los que las personas escribieron código descuidado y no lograron establecer una variable miembro en los accesores. Todos los errores resultaron difíciles de encontrar.

Me gustaría ver que esto sea lo predeterminado en una futura versión de Java. La cosa de pasar por valor / referencia dispara a una gran cantidad de programadores junior.

Una cosa más ... mis métodos tienden a tener un bajo número de parámetros, por lo que el texto adicional en una declaración de método no es un problema.





pass-by-value