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

96

O Java permite marcar variáveis (campos / locais / parâmetros) como final , para evitar a nova atribuição dentro delas. Eu acho isso muito útil com campos, pois me ajuda a ver rapidamente se alguns atributos - ou uma classe inteira - são imutáveis.

Por outro lado, acho muito menos útil com locais e parâmetros, e geralmente evito marcá-los como final mesmo que eles nunca sejam atribuídos novamente (com a exceção óbvia quando precisam ser usado em uma classe interna). Ultimamente, no entanto, eu encontrei o código que usava final sempre que podia, o que eu acho que tecnicamente fornece mais informações.

Não mais confiante em meu estilo de programação, gostaria de saber quais são as outras vantagens e desvantagens de aplicar final em qualquer lugar, qual é o estilo de mercado mais comum e por quê.

    
por Oak 16.02.2011 / 09:23
fonte

5 respostas

61

Eu uso final da mesma forma que você. Para mim, parece supérfluo em variáveis locais e parâmetros de método, e não fornece 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á-las inadvertidamente.

Além disso, como você certamente sabe, final não garante que você não possa alterar o valor / estado de uma variável (não primitiva). Só que você não pode reatribuir a referência àquele objeto depois de inicializado. Em outras palavras, funciona perfeitamente com variáveis de tipos primitivos ou imutáveis. Considere

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 é mais fácil entender o que acontece dentro do código, e entender mal isso pode, de fato, causar erros sutis que são difíceis de detectar.

    
por 16.02.2011 / 09:28
fonte
57

Seu estilo de programação Java e seus pensamentos estão bem - não precisa duvidar de você mesmo.

On the other hand, I find it a lot less useful with locals and parameters, and usually I avoid marking them as final even if they will never be re-assigned into (with the obvious exception when they need to be used in an inner class).

É exatamente por isso que você deve usar a palavra-chave final . Você afirma que VOCÊ sabe que isso nunca será reatribuído, mas ninguém mais sabe disso. Usar final imediatamente desambigua seu código um pouquinho mais.

    
por 16.02.2011 / 09:44
fonte
29

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

Ele pode estar certo de que o valor / referência nunca é alterado mais tarde. Portanto, ele não precisa prestar atenção às modificações para entender a computação.

Eu mudei de ideia em relação a isso depois de aprender linguagens de programação puramente funcionais. Rapaz, que alívio, se você puder confiar em uma "variável" para sempre manter seu valor inicial.

    
por 16.02.2011 / 09:36
fonte
15

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

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

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

Claro, se você precisar para torná-los finais, 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 simples.

Além do efeito óbvio de adicionar palavras-chave extras aos seus parâmetros e, portanto, IMHO camuflá-los, adicionar parâmetros finais ao método pode frequentemente tornar o código no corpo do método menos legível, o que torna o código pior - ser "bom" ", o código deve ser tão legível e tão simples quanto possível. Para um exemplo artificial, digamos que eu tenha um método que precise trabalhar insensivelmente em 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
}

Ao fazer os parâmetros final pararem o codificador adicionando código mais abaixo ao pensar que ele está trabalhando com o valor original, há um risco igual de que o código mais baixo use input em vez de lowercaseInput , o que não deveria e que não podem ser protegidos, porque você não pode tirá-lo do escopo (ou até mesmo atribuir null a input se isso puder ajudar mesmo assim).

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ídos de 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. Este é um acidente de trem. Temos um novo método, um bloco de exceção obrigató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 o problema de que os métodos não devem ser tão longos que você não consiga visualizá-los com facilidade e saber de imediato que uma atribuição ao parâmetro ocorreu.

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

Note também que final na verdade não protege você da maneira que parece:

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

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

    
por 26.08.2016 / 03:28
fonte
7

Eu deixo o eclipse colocar final antes de cada variável local, já que eu considero isso para tornar o programa mais fácil de ler. Eu não faço isso com parâmetros, uma vez que eu quero manter a lista de parâmetros o mais curta possível, idealmente ela deve caber em uma linha.

    
por 16.02.2011 / 10:35
fonte