Quando usar final para parâmetros de método e variáveis ​​locais?


171

Encontrei algumas referências ( por exemplo ) que sugerem usar finalo máximo possível e estou me perguntando o quanto isso é importante. Isso ocorre principalmente no contexto de parâmetros de método e variáveis ​​locais, não de métodos ou classes finais. Para constantes, faz sentido óbvio.

Por um lado, o compilador pode fazer algumas otimizações e tornar mais clara a intenção do programador. Por outro lado, adiciona verbosidade e as otimizações podem ser triviais.

É algo que eu deveria fazer um esforço para lembrar?


Aqui está uma postagem relacionada a ser
examinada


1
Estou votando apenas porque não sabia que era possível usar final como um modificador de parâmetros antes de ler isso. Obrigado!
Kip

Respostas:


178

Obcecado por:

  • Campos finais - A marcação de campos como final os força a serem definidos no final da construção, tornando imutável essa referência de campo. Isso permite a publicação segura de campos e pode evitar a necessidade de sincronização em leituras posteriores. (Observe que, para uma referência a objeto, apenas a referência de campo é imutável - as coisas a que a referência a objeto se refere ainda podem mudar e isso afeta a imutabilidade.)
  • Campos estáticos finais - Embora eu use enumerações agora para muitos dos casos em que costumava usar campos finais estáticos.

Considere, mas use criteriosamente:

  • Classes finais - O design da estrutura / API é o único caso em que eu o considero.
  • Métodos finais - basicamente o mesmo que as aulas finais. Se você estiver usando padrões de métodos de modelo, como itens malucos e marcadores finais, provavelmente estará confiando demais na herança e não na delegação.

Ignore, a menos que sinta anal:

  • Parâmetros de método e variáveis ​​locais - RARAMENTE faço isso em grande parte porque sou preguiçoso e acho que isso atrapalha o código. Admito plenamente que a marcação de parâmetros e variáveis ​​locais que não vou modificar é "mais correta". Eu gostaria que fosse o padrão. Mas não é e acho o código mais difícil de entender com as finais por todo o lado. Se eu estiver no código de outra pessoa, não vou retirá-lo, mas se estiver escrevendo um novo código, não o colocarei. Uma exceção é o caso em que você precisa marcar algo final para poder acessar de dentro de uma classe interna anônima.

  • Edit: note que um caso de uso em que as variáveis ​​locais finais são realmente muito úteis, como mencionado por @ adam-gent, é quando o valor é atribuído à var nos if/ elsebranches.


8
Joshua Bloch argumenta que todas as classes devem ser definidas como finais, a menos que sejam projetadas para herança. Eu concordo com ele; Acrescento final a todas as classes que implementam uma interface (para poder criar testes de unidade). Marque também como final todos os métodos de classe / protegido, que não serão substituídos.
Rmaruszewski 01/10/08

61
Com todo o respeito devido a Josh Bloch (e isso é uma quantia considerável), eu discordo do caso geral. No caso de criar uma API, com certeza bloqueie as coisas. Comprar dentro de seu próprio código, erguer paredes que você mais tarde precisará derrubar é uma perda de tempo.
558 Alex Miller1 de

9
Definitivamente, não é uma "perda de tempo", especialmente porque não custa tempo ... Em um aplicativo, normalmente faço quase todas as classes finalpor padrão. Você pode não perceber os benefícios, a menos que use um Java IDE verdadeiramente moderno (por exemplo, IDEA).
Rogério

9
A IDEA possui (fora da caixa) centenas de inspeções de código, e algumas delas podem detectar código não utilizado / desnecessário em finalclasses / métodos. Por exemplo, se um método final declarar lançar uma exceção verificada, mas nunca a lançar, o IDEA informará isso e você poderá remover a exceção da throwscláusula. Às vezes, você também pode encontrar métodos inteiros que não são usados, detectáveis ​​quando não podem ser substituídos.
Rogério

8
@rmaruszewski A marcação de classes como final evita que a maioria das estruturas de zombaria possa zombar delas, tornando o código mais difícil de testar. Eu só faria uma aula final se fosse extremamente importante que ela não fosse estendida.
Mike Rylander

44

É algo que devo me esforçar para me lembrar de fazer?

Não, se você estiver usando o Eclipse, porque você pode configurar uma Ação de salvamento para adicionar automaticamente esses modificadores finais para você. Então você obtém os benefícios por menos esforço.


3
Ótima dica com a ação Salvar, não sabia disso.
sanity

1
Estou considerando principalmente o benefício que final torna o código mais seguro contra erros, atribuindo acidentalmente à variável errada, em vez de quaisquer otimizações que podem ou não ocorrer.
Peter Hilton

4
Isso é realmente um problema para você? Quantas vezes você realmente teve um bug resultante disso?
Alex Miller

1
+1 para obter dicas úteis no Eclipse. Eu acho que devemos usar final, tanto quanto possível, para evitar bugs.
Emeraldhieu 22/05

Você também pode fazer isso no IntelliJ?
precisa saber é o seguinte

15

Os benefícios em tempo de desenvolvimento de "final" são pelo menos tão significativos quanto os benefícios em tempo de execução. Ele informa aos futuros editores do código algo sobre suas intenções.

Marcar uma classe como "final" indica que você não fez um esforço durante o design ou a implementação da classe para lidar com a extensão normalmente. Se os leitores puderem alterar a classe e quiserem remover o modificador "final", poderão fazê-lo por sua conta e risco. Cabe a eles garantir que a classe lide bem com a extensão.

Marcar uma variável "final" (e atribuí-la no construtor) é útil com injeção de dependência. Indica a natureza "colaborador" da variável.

Marcar um método como "final" é útil nas classes abstratas. Delineia claramente onde estão os pontos de extensão.


11

Eu uso finalo tempo todo para tornar o Java mais baseado em expressões. Veja que as condições de Java ( if,else,switch) não são baseadas em expressões que eu sempre odiei, especialmente se você está acostumado a programação funcional (por exemplo, ML, Scala ou Lisp).

Portanto, você deve sempre tentar (IMHO) usar variáveis ​​finais ao usar condições.

Deixe-me lhe dar um exemplo:

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

Agora, se adicionar outra caseinstrução e não definir, nameo compilador falhará. O compilador também falhará se você não interromper todos os casos (que você definiu a variável). Isso permite que você torne o Java muito semelhante às letexpressões do Lisp e faz com que seu código não seja massivamente recuado (devido a variáveis ​​de escopo lexicais).

E como o @Recurse observou (mas aparentemente -1 me), você pode fazer o anterior sem String name finalobter o erro do compilador (o que eu nunca disse que não poderia), mas você poderia facilmente fazer com que o erro do compilador desaparecesse definindo o nome após a alternância declaração que joga fora a expressão semântica ou pior, para a breakqual você não pode causar um erro (apesar do que @Recurse diz) sem 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";

Por causa do nome da configuração do bug (além de esquecer para breakqual outro bug), agora posso fazer isso acidentalmente:

    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";

A variável final força uma avaliação única de qual nome deve ser. Semelhante à maneira como uma função que tem um valor de retorno sempre deve retornar um valor (ignorando exceções), o bloco de comutação de nomes terá que resolver o nome e, portanto, vinculado a esse bloco de comutação, o que facilita a refatoração de blocos de código (por exemplo, refator do Eclipe: método de extração) .

O acima em OCaml:

type plugin = CandidateExport | JobPostingImport

let p = CandidateExport

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

O match ... with ...avalia como uma função ie expressão. Observe como se parece com a nossa declaração de opção.

Aqui está um exemplo no esquema (raquete ou frango):

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

1
Exceto que o compilador java já fornecerá um erro "nome potencialmente não inicializado" e se recusará a compilar com ou sem o final.
Recursar

4
Sim, mas sem a final você pode redefini-la a qualquer momento. Na verdade, eu vi isso acontecer quando um desenvolvedor transformou um else if (...)em um if(...)e, assim, redefiniu a variável. Eu mostrei a ele que nunca teria acontecido com uma variável final. Basicamente, finalobriga a atribuir a variável uma vez e apenas uma vez ... então: P
Adam Gent

9

Encontrei parâmetros e locals do método de marcação como finalúteis para auxiliar na refatoração quando o método em questão é uma bagunça incompreensível com várias páginas. Polvilhe finalgenerosamente, veja o que "não pode atribuir à variável final" erros que o compilador (ou seu IDE) lança e você pode descobrir por que a variável chamada "dados" acaba nula, apesar de vários comentários (desatualizados) jurarem que podem isso acontece.

Em seguida, você pode corrigir alguns dos erros substituindo as variáveis ​​reutilizadas por novas variáveis ​​declaradas mais próximas do ponto de uso. Então você descobre que pode envolver partes inteiras do método em chaves de escopo e, de repente, você fica a um pressionamento de tecla IDE do "Extract Method" e seu monstro ficou mais compreensível.

Se o seu método ainda não é um destroço inatingível, acho que pode haver valor em tornar as coisas finais para desencorajar as pessoas a transformá-lo nos destroços; mas se for um método curto (consulte: não é sustentável), você corre o risco de adicionar muita verbosidade. Em particular, as assinaturas de funções Java são difíceis o suficiente para caber em 80 caracteres, sem adicionar mais seis por argumento!


1
Último ponto muito válido, embora eu tenha desistido do limite de 80 caracteres há muito tempo, já que a resolução da tela mudou um pouco nos últimos 10 anos. Posso encaixar facilmente uma linha de 300 caracteres na tela sem rolar. No entanto, a legibilidade é obviamente melhor sem o finalparâmetro antes de cada parâmetro.
Brimborium

8

Bem, tudo depende do seu estilo ... se você gosta de ver a final quando não modifica a variável, use-a. Se você NÃO GOSTA de vê-lo ... deixe-o de fora.

Pessoalmente, gosto da menor verbosidade possível, por isso evito usar palavras-chave extras que não são realmente necessárias.

Eu prefiro linguagens dinâmicas, então provavelmente não é nenhuma surpresa que eu goste de evitar verbosidade.

Então, eu diria que apenas escolha a direção na qual você está se inclinando e continue com ela (seja qual for o caso, tente ser consistente).


Como observação lateral, trabalhei em projetos que usam e não usam esse padrão, e não vi diferença na quantidade de bugs ou erros ... Não acho que seja um padrão que imensamente melhore sua contagem de bugs ou algo assim, mas, novamente, é estilo, e se você gosta de expressar a intenção de não modificá-lo, vá em frente e use-o.


6

É útil em parâmetros para evitar alterar o valor do parâmetro por acidente e introduzir um bug sutil. Eu uso para ignorar esta recomendação, mas depois de passar cerca de 4 horas. em um método horrível (com centenas de linhas de código e múltiplos fors, ifs aninhados e todo tipo de práticas ruins), eu recomendaria que você o fizesse.

 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 ) { ...

 }

É claro que em um mundo perfeito isso não aconteceria, mas ... bem ... às vezes você precisa dar suporte ao código de outras pessoas. :(


2
Este método tem problemas mais sérios do que a falta final. É bastante incomum, embora não impossível, que haja uma boa razão para que um método seja tão confuso que esses tipos de erros possam ocorrer. Um pouco de reflexão sobre nomes de variáveis ​​ajudaria bastante em acidentes como esse.
Ykaganovich 01/10/08

2
Se você tiver "centenas de linhas de código" em um único método, convém dividi-lo em vários métodos menores.
Steve Kuo

5

Se você estiver escrevendo um aplicativo em que alguém precisará ler o código após, digamos, 1 ano, então sim, use a variável final on que não deve ser modificada o tempo todo. Ao fazer isso, seu código será mais "auto-documentado" e você também reduzirá a chance de outros desenvolvedores fazerem coisas tolas, como usar uma constante local como uma variável temporária local.

Se você estiver escrevendo algum código descartável, não se preocupe em identificar todas as constantes e torná-las finais.


4

Vou usar final o máximo que puder. Isso sinalizará se você alterar o campo acidentalmente. Também defino os parâmetros do método como final. Ao fazer isso, peguei vários erros do código que assumi quando eles tentaram 'definir' um parâmetro esquecendo que o Java passa por valor.


2

Não está claro na questão se isso é óbvio, mas tornar o parâmetro do método final afeta apenas o corpo do método. Ele não transmitir qualquer informação interessante sobre as intenções do método para o invocador. O objeto que está sendo passado ainda pode ser alterado dentro do método (finais não são consts) e o escopo da variável está dentro do método.

Para responder sua pergunta precisa, eu não me incomodaria em tornar uma instância ou variável local (incluindo parâmetros do método) final, a menos que o código exigisse (por exemplo, a variável seja referenciada a partir de uma classe interna), ou para esclarecer alguma lógica realmente complicada.

Por exemplo, variáveis, eu as tornaria finais se forem constantes logicamente.


2

Há muitos usos para a variável final. Aqui estão apenas alguns

Constantes Finais

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

Isso pode ser usado para outras partes dos seus códigos ou acessado por outras classes, dessa forma, se você alterar o valor, não precisará alterá-los um por um.

Variáveis ​​finais

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

  }

}

Nesta classe, você cria uma variável final com escopo que adiciona um prefixo ao parâmetro environmentKey. Nesse caso, a variável final é final apenas dentro do escopo de execução, que é diferente a cada execução do método. Cada vez que o método é inserido, a final é reconstruída. Assim que é construído, não pode ser alterado durante o escopo da execução do método. Isso permite que você corrija uma variável em um método pela duração do método. ver abaixo:

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 Finais

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   
}

Isso é especialmente útil quando você tem realmente longas linhas de códigos e gera erros de compilador para que você não encontre erros de lógica / negócios quando alguém acidentalmente altera variáveis ​​que não devem ser alteradas.

Coleções finais

Caso diferente, quando falamos de coleções, você precisa defini-las como não modificáveis.

 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);
    }

caso contrário, se você não definir como modificável:

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

Classes finais e métodos finais não podem ser estendidos ou substituídos, respectivamente.

EDIT: ENDEREÇO ​​AO PROBLEMA DA CLASSE FINAL RELATIVO AO ENCAPSULAMENTO:

Existem duas maneiras de fazer uma aula final. O primeiro é usar a palavra-chave final na declaração de classe:

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

A segunda maneira de finalizar uma classe é declarar todos os seus construtores como privados:

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

Marcá-lo como final evita o problema se descobrir que é realmente um final, para demonstrar o olhar para essa classe de teste. parece público à primeira vista.

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

Infelizmente, como o único construtor da classe é privado, é impossível estender essa classe. No caso da classe Teste, não há razão para que a classe seja final. A classe Test é um bom exemplo de como as classes finais implícitas podem causar problemas.

Portanto, você deve marcá-lo como final quando implicitamente tornar uma classe final, tornando seu construtor privado.


1

Um pouco de uma troca, como você mencionou, mas eu prefiro o uso explícito de algo sobre o uso implícito. Isso ajudará a remover alguma ambiguidade para futuros mantenedores de código - mesmo que seja apenas você.


1

Se você possui classes internas (anônimas) e o método precisa acessar a variável do método que contém, você precisa ter essa variável como final.

Fora isso, o que você disse está certo.


Agora o java 8 oferece flexibilidade com variáveis ​​finais efetivas.
Ravindra babu

0

Use a finalpalavra-chave para uma variável se você estiver fazendo essa variável comoimmutable

Ao declarar a variável como final, ajuda os desenvolvedores a descartar possíveis problemas de modificação de variáveis ​​em ambiente altamente multithread.

Com o lançamento do java 8, temos mais um conceito chamado " effectively final variable". Uma variável não final pode ser usada como variável final.

variáveis ​​locais referenciadas a partir de uma expressão lambda devem ser finais ou efetivamente finais

Uma variável é considerada final efetiva se não for modificada após a inicialização no bloco local. Isso significa que agora você pode usar a variável local sem a palavra-chave final dentro de uma classe anônima ou expressão lambda, desde que elas sejam efetivamente finais.

Até o Java 7, você não pode usar uma variável local não final dentro de uma classe anônima, mas no Java 8 você pode

Dê uma olhada neste artigo


-1

Primeiro, a palavra-chave final é usada para tornar uma variável constante. Constante significa que não muda. Por exemplo:

final int CM_PER_INCH = 2.54;

Você declararia a variável final porque um centímetro por polegada não muda.

Se você tentar substituir um valor final, a variável será o que foi declarado primeiro. Por exemplo:

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

Há um erro de compilação que é algo como:

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

Se sua variável não puder ser declarada final ou se você não quiser declará-la final, tente o seguinte:

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

Isso imprimirá:

Hello World!
A String
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.