¿Cuándo se debe usar final para los parámetros del método y las variables locales?


171

He encontrado un par de referencias ( por ejemplo ) que sugieren usarfinal tanto como sea posible y me pregunto qué tan importante es eso. Esto es principalmente en el contexto de parámetros de métodos y variables locales, no en métodos finales o clases. Para las constantes, tiene sentido obvio.

Por un lado, el compilador puede hacer algunas optimizaciones y aclara la intención del programador. Por otro lado, agrega verbosidad y las optimizaciones pueden ser triviales.

¿Es algo que debería hacer un esfuerzo por recordar?


Aquí hay una publicación relacionada para revisar: stackoverflow.com/questions/137868/…
mattlant


1
Estoy votando solo porque no sabía que era posible usar final como un modificador en los parámetros antes de leer esto. ¡Gracias!
Kip

Respuestas:


178

Obsesionarse con:

  • Campos finales: marcar campos como finales los obliga a establecerse al final de la construcción, haciendo que la referencia de campo sea inmutable. Esto permite la publicación segura de campos y puede evitar la necesidad de sincronización en lecturas posteriores. (Tenga en cuenta que para una referencia de objeto, solo la referencia de campo es inmutable; las cosas a las que se refiere la referencia de objeto todavía pueden cambiar y eso afecta la inmutabilidad).
  • Campos estáticos finales: aunque ahora uso enumeraciones para muchos de los casos en los que solía usar campos estáticos finales.

Considere pero use juiciosamente:

  • Clases finales: el diseño de marco / API es el único caso en el que lo considero.
  • Métodos finales: básicamente lo mismo que las clases finales. Si está utilizando patrones de métodos de plantilla como loco y marcando cosas finales, probablemente esté confiando demasiado en la herencia y no lo suficiente en la delegación.

Ignorar a menos que se sienta anal:

  • Parámetros del método y variables locales: RARAMENTE hago esto en gran medida porque soy flojo y me parece que satura el código. Admitiré completamente que marcar parámetros y variables locales que no voy a modificar es "más correcto". Desearía que fuera el valor predeterminado. Pero no lo es y encuentro que el código es más difícil de entender con finales por todas partes. Si estoy en el código de otra persona, no voy a extraerlo, pero si escribo un código nuevo no lo pondré. Una excepción es el caso en el que tiene que marcar algo final para que pueda acceder desde dentro de una clase interna anónima.

  • Editar: tenga en cuenta que un caso de uso donde las variables locales finales son realmente muy útiles como lo menciona @ adam-gent es cuando el valor se asigna a la var en las if/ elseramas.


8
Joshua Bloch argumenta que todas las clases deben definirse como finales, a menos que estén diseñadas para la herencia. Estoy de acuerdo con él; Agrego final a cada clase que implemente una interfaz (para poder crear pruebas unitarias). También marque como final todos los métodos protegidos / de clase, que no se anularán.
rmaruszewski

61
Con el debido respeto a Josh Bloch (y eso es una cantidad considerable), no estoy de acuerdo con el caso general. En el caso de construir una API, asegúrese de bloquear las cosas. Bui dentro de su propio código, erigir muros que luego tendrá que derribar es una pérdida de tiempo.
Alex Miller

9
Definitivamente no es una "pérdida de tiempo", especialmente porque no cuesta tiempo en absoluto ... En una aplicación, normalmente hago casi todas las clases finalpor defecto. Sin embargo, es posible que no note los beneficios a menos que utilice un Java IDE verdaderamente moderno (es decir, IDEA).
Rogério

9
IDEA tiene (fuera de la caja) cientos de inspecciones de código, y algunas de ellas pueden detectar código no utilizado / innecesario en finalclases / métodos. Por ejemplo, si un método final declara lanzar una excepción marcada pero nunca la arroja, IDEA le dirá eso, y usted puede eliminar la excepción de la throwscláusula. A veces, también puede encontrar métodos completos que no se utilizan, lo que es detectable cuando no se pueden anular.
Rogério

8
@rmaruszewski Marcar las clases como finales evita que la mayoría de los frameworks burlones puedan burlarse de ellos, lo que hace que el código sea más difícil de probar. Solo haría una final de clase si fuera críticamente importante que no se extienda.
Mike Rylander

44

¿Es algo que debería hacer un esfuerzo para recordar hacer?

No, si está utilizando Eclipse, porque puede configurar una Acción de guardado para agregar automáticamente estos modificadores finales . Entonces obtienes los beneficios por menos esfuerzo.


3
Gran consejo con Save Action, no sabía sobre eso.
cordura

1
Principalmente estoy considerando el beneficio de que final hace que el código sea más seguro de los errores al asignar accidentalmente a la variable incorrecta, en lugar de cualquier optimización que pueda suceder o no.
Peter Hilton

44
¿Es esto realmente un problema para ti? ¿Con qué frecuencia has tenido un error como resultado de esto?
Alex Miller

1
+1 para consejos útiles en Eclipse. Creo que deberíamos usar final tanto como sea posible para evitar errores.
esmeralda

¿Puedes hacer esto también en IntelliJ?
Koray Tugay

15

Los beneficios en tiempo de desarrollo de "final" son al menos tan significativos como los beneficios en tiempo de ejecución. Le dice a los futuros editores del código algo sobre sus intenciones.

Marcar una clase como "final" indica que no ha hecho un esfuerzo durante el diseño o la implementación de la clase para manejar la extensión con gracia. Si los lectores pueden hacer cambios en la clase y desean eliminar el modificador "final", pueden hacerlo bajo su propio riesgo. Depende de ellos asegurarse de que la clase manejará bien la extensión.

Marcar una variable "final" (y asignarla en el constructor) es útil con la inyección de dependencia. Indica la naturaleza "colaboradora" de la variable.

Marcar un método "final" es útil en clases abstractas. Delinea claramente dónde están los puntos de extensión.


11

Uso finaltodo el tiempo para hacer que Java esté más basado en expresiones. Ver las condiciones de Java (if,else,switch ) no están basadas en expresiones, lo que siempre he odiado, especialmente si está acostumbrado a la programación funcional (es decir, ML, Scala o Lisp).

Por lo tanto, debe intentar usar siempre (en mi humilde opinión) las variables finales cuando use condiciones.

Dejame darte un ejemplo:

    final String name;
    switch(pluginType) {
        case CANDIDATE_EXPORT:
            name = "Candidate Stuff";
            break;
        case JOB_POSTING_IMPORT:
            name = "Blah";
            break;
        default:
            throw new IllegalStateException();
    }

Ahora si agrega otra caseinstrucción y no la configura, nameel compilador fallará. El compilador también fallará si no interrumpe en todos los casos (si establece la variable). Esto le permite hacer que Java sea muy similar a la de Lisplet expresiones y hace que su código no tenga una sangría masiva (debido a las variables de alcance léxico).

Y como señaló @Recurse (pero aparentemente -1 yo), puede hacer lo anterior sin hacer String name finalpara obtener el error del compilador (que nunca dije que no podría), pero podría hacer que el error del compilador desaparezca fácilmente configurando el nombre después del cambio declaración que descarta la expresión semántica o, lo que es peor, olvidando breakque no puede causar un error (a pesar de lo que dice @Recurse) sin usar final:

    String name;
    switch(pluginType) {
        case CANDIDATE_EXPORT:
            name = "Candidate Stuff";
            //break; whoops forgot break.. 
            //this will cause a compile error for final ;P @Recurse
        case JOB_POSTING_IMPORT:
            name = "Blah";
            break;
    }
    // code, code, code
    // Below is not possible with final
    name = "Whoops bug";

Debido al nombre de configuración del error (además de olvidar a breakqué otro error) ahora puedo hacer esto accidentalmente:

    String name;
    switch(pluginType) {
        case CANDIDATE_EXPORT:
            name = "Candidate Stuff";
            break;
        //should have handled all the cases for pluginType
    }
    // code, code, code
    // Below is not possible with final
    name = "Whoops bug";

La variable final obliga a una sola evaluación de qué nombre debe ser. De forma similar a cómo una función que tiene un valor de retorno siempre debe devolver un valor (sin tener en cuenta las excepciones), el bloque de cambio de nombre tendrá que resolver el nombre y, por lo tanto, unido a ese bloque de cambio que facilita la refactorización de fragmentos de código (es decir, refactorización de Eclipe: método de extracción) .

Lo anterior en OCaml:

type plugin = CandidateExport | JobPostingImport

let p = CandidateExport

let name = match p with
    | CandidateExport -> "Candidate Stuff"
    | JobPostingImport -> "Blah" ;;

El match ... with ...evalúa como una función, es decir, expresión. Observe cómo se ve nuestra declaración de cambio.

Aquí hay un ejemplo en Scheme (Racket or Chicken):

(define name 
    (match b
      ['CandidateExport "Candidate Stuff"]
      ['JobPostingImport "Blah"]))

1
Excepto que el compilador de Java ya le dará un error de "nombre potencialmente no inicializado" y se negará a compilar con o sin el final.
Recursa el

44
Sí, pero sin la final, puede restablecerla en cualquier momento. De hecho, he visto que esto sucede cuando un desarrollador convierte un else if (...)a if(...)y, por lo tanto, restablece la variable. Le mostré que nunca habría sucedido con una variable final. Básicamente finalte obliga a asignar la variable una vez y solo una vez ... entonces: P
Adam Gent

9

He encontrado parámetros y métodos locales de marcado, ya que finales útil como ayuda de refactorización cuando el método en cuestión es un desastre incomprensible de varias páginas. Espolvorearfinal generosamente, vea qué errores "no se pueden asignar a la variable final" el compilador (o su IDE) arroja, y puede descubrir por qué la variable llamada "datos" termina siendo nula a pesar de que varios comentarios (desactualizados) juran que pueden No suceda

Luego puede corregir algunos de los errores reemplazando las variables reutilizadas con nuevas variables declaradas más cercanas al punto de uso. Luego descubres que puedes envolver partes enteras del método en llaves de alcance, y de repente estás presionando una tecla IDE fuera del "Método de extracción" y tu monstruo se vuelve más comprensible.

Si su método aún no es un desastre imposible de mantener, supongo que puede ser útil hacer que las cosas sean definitivas para disuadir a las personas de convertirlo en dicho desastre; pero si es un método corto (ver: no imposible de mantener) entonces corre el riesgo de agregar mucha verbosidad. En particular, las firmas de funciones de Java son lo suficientemente difíciles como para caber en 80 caracteres, ¡sin agregar seis más por argumento!


1
Último punto muy válido, aunque renuncié al límite de 80 caracteres hace mucho tiempo ya que la resolución de la pantalla ha cambiado un poco en los últimos 10 años. Puedo colocar fácilmente una línea de 300 caracteres en mi pantalla sin desplazarme. Sin embargo, la legibilidad es, por supuesto, mejor sin el finalantes de cada parámetro.
Brimborium

8

Bueno, todo depende de tu estilo ... si te GUSTA ver la final cuando no vas a modificar la variable, entonces úsala. Si NO TE GUSTA verlo ... entonces déjalo afuera.

Personalmente, me gusta la menor verbosidad posible, por lo que tiendo a evitar el uso de palabras clave adicionales que no son realmente necesarias.

Sin embargo, prefiero los lenguajes dinámicos, por lo que probablemente no sea una sorpresa que me guste evitar la verbosidad.

Por lo tanto, yo diría que solo elija la dirección hacia la que se inclina e ir con ella (en cualquier caso, trate de ser coherente).


Como nota al margen, he trabajado en proyectos que usan y no usan ese patrón, y no he visto ninguna diferencia en la cantidad de errores o errores ... No creo que sea un patrón que pueda mejore su conteo de errores o cualquier cosa, pero nuevamente es estilo, y si le gusta expresar la intención de que no lo modificará, continúe y utilícelo.


6

Es útil en los parámetros para evitar cambiar el valor del parámetro por accidente e introducir un error sutil. Solía ​​ignorar esta recomendación, pero después de pasar unas 4 horas. en un método horrible (con cientos de líneas de código y múltiples fors, ifs anidados y todo tipo de malas prácticas), le recomendaría que lo haga.

 public int processSomethingCritical( final int x, final int y ){
 // hundreds of lines here 
     // for loop here...
         int x2 = 0;
        x++; // bug aarrgg...
 // hundreds of lines there
 // if( x == 0 ) { ...

 }

Por supuesto, en un mundo perfecto esto no sucedería, pero ... bueno ... a veces tienes que admitir el código de otros. :(


2
Este método tiene problemas más serios que faltar final. Es bastante raro, aunque no imposible, que haya una buena razón para que un método esté tan desordenado que se produzcan este tipo de errores. Un pequeño pensamiento puesto en nombres de variables contribuiría en gran medida a accidentes como este.
ykaganovich

2
Si tiene "cientos de líneas de código" en un solo método, es posible que desee dividirlo en varios métodos más pequeños.
Steve Kuo

5

Si está escribiendo una aplicación en la que alguien tendrá que leer el código después de, por ejemplo, 1 año, entonces sí, use la variable final en que no debe modificarse todo el tiempo. Al hacer esto, su código será más "autodocumentado" y también reducirá la posibilidad de que otros desarrolladores hagan cosas tontas como usar una constante local como una variable temporal local.

Si está escribiendo un código desechable, entonces, no, no se moleste en identificar todas las constantes y hacerlas finales.


4

Usaré final tanto como pueda. Si lo hace, se marcará si cambia involuntariamente el campo. También establecí los parámetros del Método en final. Al hacerlo, detecté varios errores del código que asumí cuando intentan 'establecer' un parámetro olvidando los pases de Java por valor.


2

No queda claro a partir de la pregunta si esto es obvio, pero hacer que un parámetro del método sea final solo afecta al cuerpo del método. No NO transmitir cualquier información interesante acerca de las intenciones del método para el invocador. El objeto que se pasa aún puede ser mutado dentro del método (las finales no son constantes), y el alcance de la variable está dentro del método.

Para responder a su pregunta precisa, no me molestaría en hacer una instancia o variable local (incluidos los parámetros del método) final a menos que el código lo requiera (por ejemplo, la variable se hace referencia desde una clase interna), o para aclarar alguna lógica realmente complicada.

Por ejemplo, las variables, las haré finales si son lógicamente constantes.


2

Hay muchos usos para la variable final. Aquí hay algunos

Constantes finales

 public static class CircleToolsBetter {
     public final static double PI = 3.141;
        public double getCircleArea(final double radius) {
          return (Math.pow(radius, 2) * PI);
        }
    }

Esto puede usarse luego para otras partes de sus códigos, o acceder a otras clases, de esa manera si alguna vez cambiara el valor, no tendría que cambiarlos uno por uno.

Variables finales

public static String someMethod(final String environmentKey) {
    final String key = "env." + environmentKey;
    System.out.println("Key is: " + key);
    return (System.getProperty(key));

  }

}

En esta clase, construye una variable final con ámbito que agrega un prefijo al parámetro environmentKey. En este caso, la variable final es final solo dentro del alcance de ejecución, que es diferente en cada ejecución del método. Cada vez que se ingresa el método, se reconstruye el final. Tan pronto como se construye, no se puede cambiar durante el alcance de la ejecución del método. Esto le permite corregir una variable en un método durante la duración del método. vea abajo:

public class FinalVariables {


  public final static void main(final String[] args) {
    System.out.println("Note how the key variable is changed.");
    someMethod("JAVA_HOME");
    someMethod("ANT_HOME");
  }
}

Constantes finales

public double equation2Better(final double inputValue) {
    final double K = 1.414;
    final double X = 45.0;

double result = (((Math.pow(inputValue, 3.0d) * K) + X) * M);
double powInputValue = 0;         
if (result > 360) {
  powInputValue = X * Math.sin(result); 
} else {
  inputValue = K * Math.sin(result);   // <= Compiler error   
}

Estos son especialmente útiles cuando tiene líneas de códigos realmente largas, y generará un error del compilador para que no se encuentre con un error lógico / comercial cuando alguien cambia accidentalmente variables que no deberían cambiarse.

Colecciones finales

Caso diferente cuando hablamos de colecciones, debe establecerlas como no modificables.

 public final static Set VALID_COLORS; 
    static {
      Set temp = new HashSet( );
      temp.add(Color.red);
      temp.add(Color.orange);
      temp.add(Color.yellow);
      temp.add(Color.green);
      temp.add(Color.blue);
      temp.add(Color.decode("#4B0082")); // indigo
      temp.add(Color.decode("#8A2BE2")); // violet
      VALID_COLORS = Collections.unmodifiableSet(temp);
    }

de lo contrario, si no lo configura como no modificable:

Set colors = Rainbow.VALID_COLORS;
colors.add(Color.black); // <= logic error but allowed by compiler

Las clases finales y los métodos finales no pueden ampliarse ni sobrescribirse, respectivamente.

EDITAR: PARA ABORDAR EL PROBLEMA DE CLASE FINAL SOBRE LA ENCAPSULACIÓN:

Hay dos formas de hacer una clase final. El primero es usar la palabra clave final en la declaración de clase:

public final class SomeClass {
  //  . . . Class contents
}

La segunda forma de hacer que una clase sea final es declarar a todos sus constructores como privados:

public class SomeClass {
  public final static SOME_INSTANCE = new SomeClass(5);
  private SomeClass(final int value) {
  }

Marcarlo como final te ahorra el problema si descubres que es una final real, para demostrar que miras esta clase de Prueba. parece público a primera vista.

public class Test{
  private Test(Class beanClass, Class stopClass, int flags)
    throws Exception{
    //  . . . snip . . . 
  }
}

Desafortunadamente, dado que el único constructor de la clase es privado, es imposible extender esta clase. En el caso de la clase Test, no hay razón para que la clase sea final. La clase Test es un buen ejemplo de cómo las clases finales implícitas pueden causar problemas.

Por lo tanto, debe marcarlo como final cuando implícitamente haga una clase final haciendo que su constructor sea privado.


1

Como mencionas, es una compensación, pero prefiero el uso explícito de algo sobre el uso implícito. Esto ayudará a eliminar cierta ambigüedad para los futuros mantenedores de código, incluso si es solo usted.


1

Si tiene clases internas (anónimas) y el método necesita acceder a la variable del método contenedor, debe tener esa variable como final.

Aparte de eso, lo que has dicho es correcto.


Ahora Java 8 ofrece flexibilidad con variables finales efectivas.
Ravindra babu

0

Use la finalpalabra clave para una variable si está haciendo esa variable comoimmutable

Al declarar la variable como final, ayuda a los desarrolladores a descartar posibles problemas de modificación de variables en un entorno altamente multiproceso.

Con el lanzamiento de Java 8, tenemos un concepto más llamado " effectively final variable". Una variable no final puede desplazarse como variable final.

Las variables locales a las que se hace referencia desde una expresión lambda deben ser finales o efectivamente finales

Una variable se considera efectiva final si no se modifica después de la inicialización en el bloque local. Esto significa que ahora puede usar la variable local sin la palabra clave final dentro de una clase anónima o expresión lambda, siempre que sean efectivamente finales.

Hasta Java 7, no puede usar una variable local no final dentro de una clase anónima, pero desde Java 8 puede

Echa un vistazo a este artículo


-1

En primer lugar, la palabra clave final se usa para hacer una variable constante. Constante significa que no cambia. Por ejemplo:

final int CM_PER_INCH = 2.54;

Declararía la variable final porque un centímetro por pulgada no cambia.

Si intenta anular un valor final, la variable es lo que se declaró primero. Por ejemplo:

final String helloworld = "Hello World";
helloworld = "A String"; //helloworld still equals "Hello World"

Hay un error de compilación que es algo así como:

local variable is accessed from inner class, must be declared final

Si su variable no se puede declarar final o si no desea declararla final, intente esto:

final String[] helloworld = new String[1];
helloworld[0] = "Hello World!";
System.out.println(helloworld[0]);
helloworld[0] = "A String";
System.out.println(helloworld[0]);

Esto imprimirá:

Hello World!
A String
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.