En Java, ¿debo usar "final" para parámetros y locales incluso cuando no sea necesario?

105

Java permite marcar variables (campos / locales / parámetros) como final, para evitar la reasignación en ellas. Me resulta muy útil con los campos, ya que me ayuda a ver rápidamente si algunos atributos, o toda una clase, están destinados a ser inmutables.

Por otro lado, me parece mucho menos útil con los parámetros locales y, y generalmente evito marcarlos como finalsi nunca fueran reasignados (con la excepción obvia cuando necesitan ser utilizados en una clase interna) . Últimamente, sin embargo, me encontré con un código que usaba final siempre que podía, lo que supongo que técnicamente proporciona más información.

Ya no confío en mi estilo de programación, me pregunto cuáles son otras ventajas y desventajas de aplicar en finalcualquier lugar, cuál es el estilo más común de la industria y por qué.

Roble
fuente
Las preguntas de estilo de codificación de @Amir parecen pertenecer aquí mejor que en SO, y no pude encontrar ninguna política en las Preguntas frecuentes o en el meta de este sitio con respecto a esto. ¿Puedes por favor dirigirme?
Roble
1
@Oak dice: "Problema de programación específico, algoritmos de software, codificación , preguntar en Stack Overflow"
Amir Rezaei
77
@Amir No estoy de acuerdo, no veo cómo se trata de un problema de codificación. En cualquier caso, este debate no pertenece aquí, así que he abierto un metatema sobre este tema .
Roble
3
@amir esto es totalmente un tema para este sitio, ya que es una pregunta subjetiva sobre la programación - por favor vea el / faq
Jeff Atwood
1
Re, variables locales: los compiladores Java más antiguos prestaron atención a si declararon local finalo no, y se optimizaron en consecuencia. Los compiladores modernos son lo suficientemente inteligentes como para descubrirlo por sí mismos. Al menos en las variables locales, finales estrictamente para el beneficio de los lectores humanos. Si sus rutinas no son demasiado complicadas, entonces la mayoría de los lectores humanos también deberían poder resolverlo por sí mismos.
Solomon Slow

Respuestas:

67

Yo uso finallo mismo que tú. Para mí, parece superfluo en variables locales y parámetros de métodos, y no transmite información adicional útil.

Una cosa importante es esforzarme por mantener mis métodos cortos y limpios , cada uno haciendo una sola tarea. Por lo tanto, mis variables y parámetros locales tienen un alcance muy limitado y se usan solo para un único propósito. Esto minimiza las posibilidades de reasignarlos inadvertidamente.

Además, como seguramente sabe, finalno garantiza que no pueda cambiar el valor / estado de una variable (no primitiva). Solo que no puede reasignar la referencia a ese objeto una vez inicializado. En otras palabras, funciona a la perfección solo con variables de tipos primitivos o inmutables. Considerar

final String s = "forever";
final int i = 1;
final Map<String, Integer> m = new HashMap<String, Integer>();

s = "never"; // compilation error!
i++; // compilation error!
m.put(s, i); // fine

Esto significa que en muchos casos aún no hace que sea más fácil entender lo que sucede dentro del código, y un malentendido de hecho puede causar errores sutiles que son difíciles de detectar.

Péter Török
fuente
1
Con respecto a la edición: conozco la semántica de final, gracias :) pero buen punto sobre los métodos cortos y limpios: supongo que si el método es lo suficientemente corto como para que sea obvio que no se reasigna una variable, hay incluso menos motivo para considerar la finalpalabra clave.
Roble
¿No sería genial si pudiéramos tener parámetros finales y variables locales, y aún así una sintaxis corta y limpia? programmers.stackexchange.com/questions/199783/…
oberlies
77
"final no garantiza que no pueda cambiar el valor / estado de una variable (no primitiva). Solo que no puede reasignar la referencia a ese objeto una vez inicializado". No primitivo = referencia (los únicos tipos en Java son tipos primitivos y tipos de referencia). El valor de una variable de tipo de referencia es una referencia. Por lo tanto, no puede reasignar la referencia = no puede cambiar el valor.
user102008
2
+1 para la referencia / trampa de estado. Sin embargo, tenga en cuenta que marcar variables finales puede ser necesario para crear cierres en los nuevos aspectos funcionales de Java8
Newtopian
Su comparación está sesgada como señaló @ user102008. La asignación de variables no es lo mismo que su actualización de valor
ericn el
64

Su estilo de programación Java y sus pensamientos están bien, no necesita dudar de sí mismo allí.

Por otro lado, me parece mucho menos útil con los parámetros locales y, y generalmente evito marcarlos como finales, incluso si nunca serán reasignados (con la obvia excepción cuando deben usarse en una clase interna). )

Esto es exactamente por qué debe usar la finalpalabra clave. Usted declara que USTED sabe que nunca será reasignado, pero nadie más lo sabe. Usar finalinmediatamente desambigua su código un poquito más.

Jonathan Khoo
fuente
8
Si su método es claro y solo hace una cosa, el lector también lo sabrá. La última palabra solo hace que el código sea ilegible. Y si es ilegible, es mucho más ambiguo
eddieferetro
55
@eddieferetro No estoy de acuerdo. La palabra clave finalindica intención, lo que hace que el código sea más legible. Además, una vez que deba lidiar con el código del mundo real, notará que rara vez es prístino y claro, y agregar generosamente finals en todas partes puede ayudarlo a detectar errores y comprender mejor el código heredado.
Andres F.
44
¿La "intención" es que la variable nunca cambiará y que su código se basa en ese hecho ? O es la intención "simplemente sucede que esta variable nunca cambia, así que la estoy marcando como final". El último es un ruido inútil y posiblemente dañino. Y dado que pareces abogar por marcar a todos los lugareños, estás haciendo lo posterior. Gran -1.
user949300
2
finalestablece la intención, que generalmente es algo bueno en un fragmento de código, pero tiene el precio de agregar desorden visual. Como la mayoría de las cosas en la programación, hay una compensación aquí: ¿está expresando el hecho de que su variable solo se usará una vez que valga la verbosidad adicional?
christopheml
El desorden visual se reducirá en gran medida al utilizar el resaltado de sintaxis. Marco a mis locales que se asignan solo una vez, y que quiero que se me asignen solo una vez, con final. Eso puede ser la mayoría de los lugareños, pero claramente no todos . Para mí no hay "que nunca cambie". Hay 2 tipos de locales claramente separados en mi mundo. Aquellos que se supone que nunca volverán a cambiar (final) y aquellos que deben cambiar como resultado de lo que dicta la tarea en cuestión (de los cuales hay muy poco, lo que hace que las funciones sean bastante fáciles de razonar).
blubberdiblub
31

Una ventaja de usar final/ constsiempre que sea posible es que reduce la carga mental para el lector de su código.

Puede estar seguro de que el valor / referencia nunca se modifica más adelante. Por lo tanto, no necesita prestar atención a las modificaciones para comprender el cálculo.

He cambiado de opinión al respecto después de aprender lenguajes de programación puramente funcionales. Chico, qué alivio, si puedes confiar en una "variable" para mantener siempre su valor inicial.

LennyProgrammers
fuente
16
Bueno, en Java finalno garantiza que no pueda cambiar el valor / estado de una variable (no primitiva). Solo que no puede reasignar la referencia a ese objeto una vez inicializado.
Péter Török
99
Lo sé, por eso distingo entre valor y referencia. El concepto es el más útil en el contexto de estructuras de datos inmutables y / o funcionalidad pura.
LennyProgrammers
77
@ PéterTörök Es inmediatamente útil con tipos primitivos, y algo útil con referencias a objetos mutables (¡al menos sabes que siempre estás tratando con el mismo objeto!). Es inmensamente útil cuando se trata de código diseñado para ser inmutable desde cero.
Andres F.
18

Considero finalque los parámetros del método y las variables locales son ruido de código. Las declaraciones de métodos Java pueden ser bastante largas (especialmente con los genéricos); no es necesario hacerlas por más tiempo.

Si las pruebas unitarias se escriben correctamente, la asignación de parámetros que es "perjudicial" va a ser recogido, por lo que nunca debería realmente ser un problema. La claridad visual es más importante que evitar un posible error que no se detecta porque las pruebas de su unidad no tienen una cobertura suficiente.

Herramientas como FindBugs y CheckStyle que se pueden configurar para romper la compilación si se realiza una asignación a parámetros o variables locales, si se preocupa profundamente por tales cosas.

Por supuesto, si necesita hacerlos finales, por ejemplo, porque está usando el valor en una clase anónima, entonces no hay problema: esa es la solución más simple y limpia.

Además del efecto obvio de agregar palabras clave adicionales a sus parámetros y, por lo tanto, en mi humilde opinión, camuflarlas, agregar los parámetros finales al método a menudo puede hacer que el código en el cuerpo del método sea menos legible, lo que empeora el código: para ser "bueno", el código debe ser lo más legible y simple posible. Para un ejemplo artificial, digamos que tengo un método que debe funcionar sin distinción entre mayúsculas y minúsculas.

Sin final:

public void doSomething(String input) {
    input = input.toLowerCase();
    // do a few things with input
}

Sencillo. Limpiar. Todos saben lo que está pasando.

Ahora con 'final', opción 1:

public void doSomething(final String input) {
    final String lowercaseInput = input.toLowerCase();
    // do a few things with lowercaseInput
}

Si bien la configuración de los parámetros finalimpide que el codificador agregue código más abajo de pensar que está trabajando con el valor original, existe el mismo riesgo de que el código más abajo pueda usar en inputlugar de lowercaseInput, lo que no debería y de lo que no puede protegerse, porque puede ' t sacarlo del alcance (o incluso asignar nulla inputsi eso ayudaría de todos modos).

Con 'final', opción 2:

public void doSomething(final String input) {
    // do a few things with input.toLowerCase()
}

Ahora acabamos de crear aún más ruido de código e introdujimos un golpe de rendimiento al tener que invocar toLowerCase()n veces.

Con 'final', opción 3:

public void doSomething(final String input) {
    doSomethingPrivate(input.toLowerCase());
}

/** @throws IllegalArgumentException if input not all lower case */
private void doSomethingPrivate(final String input) {
    if (!input.equals(input.toLowerCase())) {
        throw new IllegalArgumentException("input not lowercase");
    }
    // do a few things with input
}

Habla sobre el ruido de código. Este es un choque de trenes. Tenemos un nuevo método, un bloque de excepción requerido, porque otro código puede invocarlo incorrectamente. Más pruebas unitarias para cubrir la excepción. Todo para evitar una línea simple, y en mi humilde opinión preferible e inofensiva.

También está el problema de que los métodos no deberían ser tan largos que no se puedan asimilar visualmente y saber de un vistazo que se ha realizado una asignación a un parámetro.

Creo que es una buena práctica / estilo que si asigna un parámetro lo hace al principio del método, preferiblemente en primera línea o inmediatamente después de la verificación de entrada básica, reemplazándolo efectivamente por todo el método, lo que tiene un efecto consistente dentro del método método. Los lectores saben que cualquier tarea debe ser obvia (cerca de la declaración de firma) y en un lugar coherente, lo que mitiga en gran medida el problema que está tratando de evitar la adición final. En realidad, rara vez asigno parámetros, pero si lo hago, siempre lo hago en la parte superior de un método.


Tenga en cuenta también que en finalrealidad no lo protege como puede parecer a primera vista:

public void foo(final Date date) {
    date.setTime(0); 
    // code that uses date
}

final no lo protege completamente a menos que el tipo de parámetro sea primitivo o inmutable.

bohemio
fuente
En el último caso, finalproporciona la garantía parcial de que se trata de la misma Dateinstancia. Algunas garantías son mejores que nada (en todo caso, ¡estás defendiendo clases inmutables desde cero!). En cualquier caso, en la práctica, gran parte de lo que diga debería afectar a los idiomas existentes inmutables por defecto, pero no lo hace, lo que significa que no es un problema.
Andres F.
+1 para señalar el riesgo "ese código más abajo puede usar input". Aún así, preferiría que mis parámetros fueran final, con la posibilidad de hacerlos no finales para casos como el anterior. Simplemente no vale la pena enviar spam a la firma con una palabra clave.
maaartinus
Encuentro el punto que uno puede usar por error en inputlugar de lowercaseInputdiscutible. Si bien es técnicamente correcto, es un error fácil de detectar una vez que le causa un error, ya que puede ver claramente las diferentes semánticas de las 2 variables nombradas por separado en su punto de uso (siempre que les dé buenos nombres). Para mí, es más peligroso combinar 2 usos semánticamente diferentes de valores separados en la misma variable y, por lo tanto, el mismo nombre. Puede hacer que el seguimiento de errores sea más difícil, ya que tendrá que leer y comprender todo el código anterior que de alguna manera está relacionado con la variable 1.
blubberdiblub
7

Dejo que eclipse se coloque finalantes de cada variable local, ya que considero que hace que el programa sea más fácil de leer. No lo hago con parámetros, ya que quiero mantener la lista de parámetros lo más corta posible, idealmente debería caber en una línea.

maaartinus
fuente
2
Además, para los parámetros, puede hacer que el compilador emita una advertencia o error si se asigna un parámetro.
Oberlies
3
Todo lo contrario, me resulta más difícil de leer, ya que solo llena el código.
Steve Kuo
3
@ Steve Kuo: Simplemente me permite encontrar rápidamente todas las variables. sin grandes ganancias, pero junto con la prevención de asignaciones accidentales, vale la pena los 6 caracteres. Estaría mucho más feliz si hubiera algo así como varpara marcar variables no finales. YMMV.
maaartinus
1
@maaartinus ¡De acuerdo var! Desafortunadamente, no es el predeterminado en Java y ahora es demasiado tarde para cambiar el idioma. Por lo tanto, estoy dispuesto a soportar el pequeño inconveniente de escribir final:)
Andres F.
1
@maaartinus De acuerdo. Encuentro que aproximadamente el 80-90% de mis variables (locales) no necesitan modificarse después de la inicialización. Por lo tanto, 80-90% de ellos tienen la finalpalabra clave antepuesta, mientras que solo 10-20% necesitaría un var...
JimmyB