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

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

Si excluimos el uso de clases anónimas, la legibilidad y la statement de intenciones, entonces parece casi inútil para mí.

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

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

  • Si estamos pasando un parámetro por referencia, entonces la referencia en sí es una variable local y si la referencia se cambia dentro del método, eso no tendría ningún efecto fuera del scope 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 c = new ArrayList(); nullify(c); assertNotNull(c); final Collection c1 = c; assertTrue(c1.equals(c)); change(c); assertTrue(c1.equals(c)); } private void change(Collection c) { c = new ArrayList(); } public void nullify(Collection t) { t = null; } 

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

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

si olvida la palabra clave ‘this’ en un setter, la variable que desea establecer no se establece. Sin embargo, si utilizó la palabra clave final en el parámetro, el error se detectaría en tiempo de comstackción.

Detener la reasignación de una variable

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

Utilice la palabra clave final cuando desee que el comstackdor evite que una variable se vuelva a asignar a un objeto diferente.

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

Ejemplo

Veamos el efecto en acción.

Considere este método simple, donde las dos variables ( arg y x ) pueden reasignarse 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 produce un error de comstackció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 cambio, marquemos la variable de parámetro como final . Esto también genera un error de comstackción.

 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 apunte al mismo objeto, marque la variable final .

Nunca reasignar argumentos

Como buena práctica de progtwigción (en cualquier idioma), 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, nunca se debe escribir la línea arg = . Como los humanos cometen errores y los progtwigdores son humanos, pidamos al comstackdor que nos ayude. Marque cada parámetro / variable de argumento como ‘final’ para que el comstackdor pueda encontrar y marcar tales reasignaciones.

En retrospectiva

Como se señaló en otras respuestas … Dado el objective de diseño original de Java de ayudar a los progtwigdores a evitar errores estúpidos como leer más allá del final de una matriz, Java debería haber sido diseñado para aplicar automáticamente todas las variables de parámetro / argumento como ‘final’. En otras palabras, los argumentos no deben ser variables . Pero en retrospectiva tiene una visión 20/20, y los diseñadores de Java tenían las manos ocupadas 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 } 

Sí, excluyendo las clases anónimas, la legibilidad y la statement de intención es casi inútil. Sin embargo, ¿son esas tres cosas inútiles?

Personalmente, tiendo a no utilizar el valor 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í mismo no cambiará ( incluso si el objeto al que se refiere cambia sus contenidos). Para aquellos que encuentran que eso se agrega a la legibilidad, creo que es algo completamente razonable de hacer.

Su punto sería más importante si alguien realmente estuviera afirmando que mantuvo los datos constantes de una manera que no es así, pero no recuerdo haber visto tales afirmaciones. ¿Sugiere que haya un grupo importante de desarrolladores que sugiera que la final tiene más efecto del 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?”

Permítanme explicarles un poco sobre el caso donde deben usar el final, que Jon ya mencionó:

Si crea una clase interna anónima en su método y usa una variable local (como un parámetro de método) dentro de esa clase, entonces el comstackdor lo obliga a hacer que el parámetro sea definitivo:

 public Iterator createIntegerIterator(final int from, final int to) { return new Iterator(){ int index = from; public Integer next() { return index++; } public boolean hasNext() { return index <= to; } // remove method omitted }; } 

Aquí los parámetros from y to necesitan ser definitivos para que puedan ser utilizados dentro de la clase anónima.

El motivo de este requisito es este: las variables locales viven en la stack, por lo tanto, existen solo mientras se ejecuta el método. Sin embargo, la instancia de clase anónima se devuelve del método, por lo que puede vivir mucho más tiempo. No puede conservar la stack, porque es necesaria para llamadas de método posteriores.

Entonces, lo que hace Java es colocar copias de esas variables locales como variables de instancia ocultas en la clase anónima (puede verlas si examina el código de bytes). Pero si no fueran finales, uno podría esperar que la clase anónima y el método vieran los cambios que el otro hace a la variable. Para mantener la ilusión de que solo hay una variable en lugar de dos copias, tiene que ser definitiva.

Uso final todo el tiempo en los parámetros.

¿Agrega tanto? Realmente no.

¿Lo apagaría? No.

La razón: encontré 3 errores donde la gente había escrito código descuidado y no pudo establecer una variable miembro en los descriptores de acceso. Todos los errores resultaron difíciles de encontrar.

Me gustaría ver que esto sea el predeterminado en una versión futura de Java. El paso por el valor / cosa de referencia hace tropezar a una gran cantidad de progtwigdores 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 statement de método no es un problema.

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

Personalmente, no utilizo parámetros de método finales, porque agrega mucho desorden a las listas de parámetros. Prefiero hacer cumplir que los parámetros del método no se cambian a través de algo como Checkstyle.

Para las variables locales, uso final siempre que sea posible, incluso dejo que Eclipse lo haga automáticamente en mi configuración para proyectos personales.

Sin duda me gustaría algo más fuerte como C / C ++ const.

Como Java pasa copias de argumentos, creo que su relevancia es bastante limitada. Supongo que esto viene de la era de C ++ en la que puedes prohibir que el contenido de referencia cambie al hacer una const char const * . Siento que este tipo de cosas te hace creer que el desarrollador es inherentemente estúpido y tiene que estar protegido contra todos los personajes que escribe. En toda humildad debo decir que escribo muy pocos errores, a pesar de que prometo la final menos que no quiera que alguien anule mis métodos y esas cosas … Tal vez solo soy un desarrollador de la vieja escuela …: – O

Respuesta corta: final ayuda un poco pero … usa progtwigción defensiva en el lado del cliente.

De hecho, el problema con el final es que solo exige que la referencia no se modifique, permitiendo alegremente que los miembros del objeto referenciados sean mutados, sin el conocimiento de la persona que llama. Por lo tanto, la mejor práctica en este sentido es la progtwigción defensiva del 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 inescrupulosas.

Una razón adicional para agregar un valor final a las declaraciones de parámetros es que ayuda a identificar las variables que necesitan ser renombradas como parte de una refactorización de “Método de extracción”. He descubierto que agregar una versión final a cada parámetro antes de comenzar un método de refacturación grande me dice rápidamente si hay algún problema que deba abordar antes de continuar.

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

Nunca utilizo el final en una lista de parámetros, solo agrega desorden como lo han dicho los encuestados anteriores. También en Eclipse puede establecer la asignación de parámetros para generar un error, por lo que usar el 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 generando un error, capté 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é hay de malo en hacer esto?

Seguimiento por lo que la publicación de Michel. Yo mismo me hice otro ejemplo para explicarlo. Espero que pueda ayudar.

 public static void main(String[] args){ MyParam myParam = thisIsWhy(new MyObj()); myParam.setArgNewName(); System.out.println(myParam.showObjName()); } public static MyParam thisIsWhy(final MyObj obj){ MyParam myParam = new MyParam() { @Override public void setArgNewName() { obj.name = "afterSet"; } @Override public String showObjName(){ return obj.name; } }; return myParam; } public static class MyObj{ String name = "beforeSet"; public MyObj() { } } public abstract static class MyParam{ public abstract void setArgNewName(); public abstract String showObjName(); } 

Del código anterior, en el método thisIsWhy () , en realidad no asignamos el [argumento MyObj obj] a una referencia real en MyParam. En cambio, simplemente usamos el [argumento MyObj obj] en el método dentro de MyParam.

Pero después de que terminemos el método thisIsWhy () , ¿ debería existir el argumento (objeto) MyObj?

Parece que debería, porque podemos ver que en main todavía llamamos al método showObjName () y necesita llegar a obj . MyParam aún usará / alcanzará el argumento de método, incluso si el método ya fue devuelto.

Cómo Java realmente logra esto es generar una copia también es una referencia oculta del argumento MyObj obj dentro del objeto MyParam (pero no es un campo formal en MyParam para que no podamos verlo)

Como llamamos “showObjName”, usará esa referencia para obtener el valor correspondiente.

Pero si no ponemos el argumento final, lo que lleva a una situación, podemos reasignar una nueva memoria (objeto) al argumento MyObj obj .

¡Técnicamente no hay choque en absoluto! Si se nos permite hacer eso, a continuación se muestra la situación:

  1. Ahora tenemos un punto [MyObj obj] oculto a una [Memoria A en el montón] ahora vivo en el objeto MyParam.
  2. También tenemos otro [MyObj obj] que es el argumento apunta a una [Memoria B en el montón] ahora vivo en este método IWWhy.

Sin enfrentamiento, pero “¡CONFUSO!” Porque todos usan el mismo “nombre de referencia” que es “obj” .

Para evitar esto, configúrelo como “final” para evitar que el progtwigdor haga el código “propenso a errores”.