Em Java, devo usar "final" para parâmetros e locais mesmo quando não preciso?


105

Java permite marcar variáveis ​​(campos / locais / parâmetros) como final, para evitar a atribuição novamente a elas. Acho isso muito útil nos campos, pois me ajuda a ver rapidamente se alguns atributos - ou uma classe inteira - devem ser imutáveis.

Por outro lado, acho isso muito menos útil com locais e parâmetros, e geralmente evito marcá-los como finalse nunca fossem redesignados (com a exceção óbvia quando eles precisam ser usados ​​em uma classe interna) . Ultimamente, no entanto, deparei-me com códigos que usavam final sempre que possível, e acho que tecnicamente fornece mais informações.

Não confiando mais no meu estilo de programação, pergunto-me quais são as outras vantagens e desvantagens de aplicar em finalqualquer lugar, qual é o estilo mais comum do setor e por quê.


As perguntas no estilo de codificação da @Amir parecem pertencer aqui melhor do que no SO, e não encontrei nenhuma política nas Perguntas frequentes ou na meta deste site sobre isso. Você pode me dirigir?
Oak

1
@Oak Ele diz: "problema de programação específica, algoritmos de software, codificação , perguntar sobre estouro de pilha"
Amir Rezaei

7
@Amir Eu discordo, não vejo como isso é um problema de codificação. De qualquer forma, esse debate não pertence aqui, então eu abri um meta-tópico sobre esse assunto .
Oak

3
@amir, este tópico é totalmente tópico para este site, pois é uma pergunta subjetiva sobre programação - consulte / faq
Jeff Atwood

1
Por exemplo, variáveis ​​locais: Os compiladores Java mais antigos prestaram atenção se você declarou um local finalou não e foi otimizado de acordo. Os compiladores modernos são inteligentes o suficiente para descobrir por si mesmos. Pelo menos em variáveis ​​locais, finalé estritamente para o benefício de leitores humanos. Se suas rotinas não são muito complicadas, a maioria dos leitores humanos deve ser capaz de descobrir por si também.
Solomon Slow

Respostas:


67

Eu uso finalda mesma maneira que você. Para mim, parece supérfluo em variáveis ​​locais e parâmetros de método, e não transmite informações extras úteis.

Uma coisa importante é que se esforçam para manter meus métodos curtos e limpos , cada um fazendo uma única tarefa. Assim, minhas variáveis ​​e parâmetros locais têm um escopo muito limitado e são usados ​​apenas para uma única finalidade. Isso minimiza as chances de redesigná-los inadvertidamente.

Além disso, como você certamente sabe, finalnão garante que você não possa alterar o valor / estado de uma variável (não-prioritária). Só que você não pode reatribuir a referência a esse objeto depois de inicializado. Em outras palavras, ele funciona perfeitamente apenas com variáveis ​​de tipos primitivos ou imutáveis. 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

Isso significa que, em muitos casos, ainda não facilita a compreensão do que acontece dentro do código, e entender isso de fato pode causar erros sutis, difíceis de detectar.


1
Em relação à edição - estou ciente da semântica de final, obrigado :), mas um bom argumento sobre métodos curtos e limpos - acho que, se o método for curto o suficiente para que seja óbvio que uma variável não está sendo redesignada, haverá ainda menos motivo para considerar a finalpalavra - chave.
carvalho

Não seria ótimo se pudéssemos ter parâmetros finais e variáveis ​​locais e ainda uma sintaxe curta e limpa? programmers.stackexchange.com/questions/199783/…
oberlies

7
"final não garante que você não possa alterar o valor / estado de uma variável (não-prioritária). Somente que você não pode reatribuir a referência a esse objeto depois de inicializado." Não primitivo = referência (os únicos tipos em Java são tipos primitivos e tipos de referência). O valor de uma variável do tipo de referência é uma referência. Portanto, você não pode reatribuir a referência = você não pode alterar o valor.
usar o seguinte comando

2
+1 para a armadilha de referência / estado. Note, entretanto, que as variáveis de marcação finais podem ser necessárias para criar fechamentos em novos aspectos functionnal de Java8
Newtopian

Sua comparação é tendenciosa como @ user102008 apontado. Atribuição de variável não é o mesmo que seu valor de atualização
ericn

64

Seu estilo e pensamentos de programação Java são bons - não precisa duvidar de si mesmo.

Por outro lado, acho muito menos útil para locais e parâmetros e, geralmente, evito marcá-los como finais, mesmo que nunca sejam reatribuídos (com a exceção óbvia quando eles precisam ser usados ​​em uma classe interna) )

É exatamente por isso que você deve usar a finalpalavra - chave. Você afirma que você sabe que nunca vai ser re-atribuído, mas ninguém mais sabe disso. Usar finalimediatamente desambigua seu código um pouquinho mais.


8
Se o seu método for claro e fizer apenas uma coisa, o leitor também saberá. A palavra final apenas torna o código ilegível. E se a sua ilegível seu muito mais ambígua
eddieferetro

4
@eddieferetro Discordou. A palavra-chave finalindica intenção, o que torna o código mais legível. Além disso, uma vez que você precise lidar com o código do mundo real, notará que ele raramente é claro e limpo, e a adição generosa de finals em todos os lugares que você puder ajudará a detectar bugs e a entender melhor o código legado.
Andrés F.

4
É a "intenção" de que a variável nunca mude e que seu código se apóie nesse fato ? Ou é a intenção "acontece que essa variável nunca muda, então estou marcando como final". O último é ruído inútil e possivelmente prejudicial. E desde que você parece defender a marcação de todos os locais, você está fazendo o mais tarde. Big -1.
user949300

2
finaldeclara a intenção, que geralmente é uma coisa boa em um pedaço de código, mas tem o preço de adicionar desorganização visual. Como a maioria das coisas em programação, há uma troca aqui: está expressando o fato de que sua variável será usada apenas uma vez que vale a verbosidade adicional?
Christopheml

30

Uma vantagem de usar final/ constsempre que possível é que reduz a carga mental para o leitor do seu código.

Ele pode ter certeza de que o valor / referência nunca será alterado posteriormente. Portanto, ele não precisa prestar atenção às modificações para entender o cálculo.

Eu mudei de idéia sobre isso depois de aprender linguagens de programação funcionais. Rapaz, que alívio, se você pode confiar em uma "variável" para manter sempre seu valor inicial.


16
Bem, em Java finalnão garante que você não possa alterar o valor / estado de uma variável (não-prioritária). Só que você não pode reatribuir a referência a esse objeto depois de inicializado.
Péter Török

9
Eu sei, é por isso que distingui entre valor e referência. O conceito é o mais útil no contexto de estruturas de dados imutáveis ​​e / ou funcionalidade pura.
LennyProgrammers

7
@ PéterTörök É imediatamente útil com tipos primitivos e um pouco útil com referências a objetos mutáveis ​​(pelo menos você sabe que está sempre lidando com o mesmo objeto!). É imensamente útil ao lidar com código projetado para ser imutável desde o início.
Andrés F.

18

Eu considero finalnos parâmetros do método e variáveis ​​locais como ruído de código. As declarações de método Java podem ser bastante longas (especialmente com genéricos) - não há mais necessidade de fazê-las.

Se os testes de unidade forem escritos corretamente, a atribuição de parâmetros "prejudiciais" será detectada, portanto nunca deve ser realmente um problema. A clareza visual é mais importante do que evitar um possível bug que não é detectado porque seus testes de unidade têm cobertura insuficiente.

Ferramentas como FindBugs e CheckStyle que podem ser configuradas para interromper a compilação se a atribuição for feita a parâmetros ou variáveis ​​locais, se você se importar profundamente com essas coisas.

Obviamente, se você precisar finalizá-las, por exemplo, porque você está usando o valor em uma classe anônima, então não há problema - essa é a solução mais simples e limpa.

Além do efeito óbvio de adicionar palavras-chave extras aos seus parâmetros e, assim, camuflar o IMHO, adicionar parâmetros finais ao método geralmente pode tornar o código no corpo do método menos legível, o que torna o código pior - para ser "bom", código deve ser o mais legível e simples possível. Para um exemplo artificial, digamos que eu tenha um método que precise trabalhar com maiúsculas e minúsculas.

Sem final:

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

Simples. Limpar \ limpo. Todo mundo sabe o que está acontecendo.

Agora com 'final', opção 1:

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

Embora a finaldefinição dos parâmetros impeça o codificador de adicionar o código mais longe de pensar que ele está trabalhando com o valor original, há um risco igual que o código mais abaixo possa usar em inputvez de lowercaseInput, o que não deveria e o que não pode ser protegido, porque você pode ' t levá-lo fora do escopo (ou mesmo atribuir nullpara inputse isso fosse mesmo ajudar de qualquer maneira).

Com 'final', opção 2:

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

Agora, acabamos de criar ainda mais ruído no código e introduzimos um impacto no desempenho de ter que invocar toLowerCase()n vezes.

Com 'final', opção 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
}

Fale sobre o ruído do código. Isto é um acidente de trem. Temos um novo método, um bloco de exceção necessário, porque outro código pode invocá-lo incorretamente. Mais testes de unidade para cobrir a exceção. Tudo para evitar uma linha simples, e IMHO, preferível e inofensiva.

Há também a questão de que os métodos não devem ser tão longos que você não pode visualmente visualizá-lo facilmente e saber rapidamente que uma atribuição ao parâmetro ocorreu.

Eu acho que é uma boa prática / estilo que, se você atribuir a um parâmetro, faça isso todo o começo do método, de preferência na primeira linha ou logo após a verificação básica de entrada, substituindo -o efetivamente por todo o método, o que tem um efeito consistente dentro do método método. Os leitores sabem que qualquer tarefa é óbvia (próximo à declaração da assinatura) e em um local consistente, o que atenua bastante o problema que a adição final está tentando evitar. Na verdade, raramente atribuo a parâmetros, mas, se o faço, sempre o faço no topo de um método.


Observe também que final, na verdade, não o protege como pode parecer à primeira vista:

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

final não o protege completamente, a menos que o tipo de parâmetro seja primitivo ou imutável.


No último caso, finalfornece a garantia parcial de que você está lidando com a mesma Dateinstância. Algumas garantias são melhores que nada (se é que você está argumentando por classes imutáveis ​​desde o início!). De qualquer forma, na prática, muito do que você diz deve afetar os idiomas imutáveis ​​por padrão existentes, mas não afeta, o que significa que não é um problema.
Andrés F.

+1 para apontar para o risco "esse código mais abaixo pode usar entrada". Ainda assim, eu prefiro que meus parâmetros sejam final, com a possibilidade de torná-los não finais para casos como o descrito acima. Não vale a pena enviar a assinatura com uma palavra-chave.
maaartinus

7

Deixei o eclipse colocar finalantes de cada variável local, já que considero facilitar a leitura do programa. Não faço isso com parâmetros, já que quero manter a lista de parâmetros o mais curta possível, idealmente, ela deve caber em uma linha.


2
Além disso, para parâmetros, o compilador pode emitir um aviso ou erro se um parâmetro for atribuído.
Oberlies

3
Muito pelo contrário, acho mais difícil de ler, pois apenas confunde o código.
Steve Kuo

3
@ Steve Kuo: Apenas me permite encontrar rapidamente todas as variáveis. sem grandes ganhos, mas, juntamente com a prevenção de tarefas acidentais, vale os 6 caracteres. Eu ficaria muito mais feliz se houvesse algo como varmarcar variáveis ​​não finais. YMMV.
Maaartinus

1
@maaartinus Concordou em var! Infelizmente, não é o padrão em Java e agora é tarde demais para mudar o idioma. Portanto, eu estou disposto a colocar-se com o pequeno inconveniente de escrever final:)
Andres F.

1
@maaartinus Concordou. Acho que cerca de 80-90% das minhas variáveis ​​(locais) não precisam ser modificadas após a inicialização. Assim, 80-90% deles têm a finalpalavra-chave prefixado, enquanto que apenas 10-20% seria necessário um var...
JimmyB
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.