Por que devo usar a palavra-chave "final" em um parâmetro de método em Java?

381

Não consigo entender onde a finalpalavra-chave é realmente útil quando usada nos parâmetros do método.

Se excluirmos o uso de classes anônimas, legibilidade e declaração de intenção, isso parecerá quase inútil para mim.

A imposição de que alguns dados permaneçam constantes não é tão forte quanto parece.

  • Se o parâmetro for um primitivo, ele não terá efeito, pois o parâmetro é passado para o método como um valor e a alteração não terá efeito fora do escopo.

  • Se estamos passando um parâmetro por referência, a própria referência é uma variável local e se a referência for alterada de dentro do método, isso não teria nenhum efeito fora do escopo do método.

Considere o exemplo de teste simples abaixo. Este teste é aprovado, embora o método tenha alterado o valor da referência fornecida, ele não tem efeito.

public void testNullify() {
    Collection<Integer> c  = new ArrayList<Integer>();      
    nullify(c);
    assertNotNull(c);       
    final Collection<Integer> c1 = c;
    assertTrue(c1.equals(c));
    change(c);
    assertTrue(c1.equals(c));
}

private void change(Collection<Integer> c) {
    c = new ArrayList<Integer>();
}

public void nullify(Collection<?> t) {
    t = null;
}
Basil Bourque
fonte
101
Um ponto rápido na terminologia - o Java não tem passagem por referência. Tem referência de passagem por valor que não é a mesma coisa. Com a verdadeira passagem por semântica de referência, os resultados do seu código seriam diferentes.
911 Jon Skeet
6
Qual é a diferença entre passar por referência e passar referência por valor?
NobleUplift 14/10
É mais fácil descrever essa diferença em um contexto C (pelo menos para mim). Se eu passar um ponteiro para um método como: <code> int foo (int bar) </code>, esse ponteiro está sendo passado por valor. O que significa que ele é copiado; portanto, se eu fizer algo dentro desse método como <code> free (bar); bar = malloc (...); </code> então acabei de fazer uma coisa muito ruim. A chamada gratuita liberará o pedaço de memória que está sendo apontado (então, qualquer ponteiro que eu passei agora está oscilando). No entanto, <code> int foo (int & bar) </bar> significa que o código é válido e o valor do ponteiro passado será alterado.
jerslan
11
O primeiro deveria ser int foo(int* bar)e o último int foo(int* &bar). O último está passando um ponteiro por referência, o primeiro está passando uma referência por valor.
jerslan
2
@ Martin, na minha opinião, é uma boa pergunta ; veja o título da pergunta e o conteúdo da postagem como uma explicação do motivo da pergunta . Talvez eu esteja entendendo mal as regras aqui, mas esta é exatamente a pergunta que eu queria ao procurar "usos de parâmetros finais nos métodos" .
Victor Zamanian

Respostas:

239

Interromper a reatribuição de uma variável

Embora essas respostas sejam intelectualmente interessantes, não li a resposta curta e simples:

Use a palavra-chave final quando desejar que o compilador impeça a atribuição de uma variável a um objeto diferente.

Seja a variável estática, variável membro, variável local ou variável argumento / parâmetro, o efeito é totalmente o mesmo.

Exemplo

Vamos ver o efeito em ação.

Considere este método simples, onde as duas variáveis ​​( arg e x ) podem ser atribuídas novamente a objetos diferentes.

// Example use of this method: 
//   this.doSomething( "tiger" );
void doSomething( String arg ) {
  String x = arg;   // Both variables now point to the same String object.
  x = "elephant";   // This variable now points to a different String object.
  arg = "giraffe";  // Ditto. Now neither variable points to the original passed String.
}

Marque a variável local como final . Isso resulta em um erro do compilador.

void doSomething( String arg ) {
  final String x = arg;  // Mark variable as 'final'.
  x = "elephant";  // Compiler error: The final local variable x cannot be assigned. 
  arg = "giraffe";  
}

Em vez disso, vamos marcar a variável de parâmetro como final . Isso também resulta em um erro do compilador.

void doSomething( final String arg ) {  // Mark argument as 'final'.
  String x = arg;   
  x = "elephant"; 
  arg = "giraffe";  // Compiler error: The passed argument variable arg cannot be re-assigned to another object.
}

Moral da história:

Se você deseja garantir que uma variável sempre aponte para o mesmo objeto, marque a variável final .

Nunca reatribuir argumentos

Como boa prática de programação (em qualquer idioma), você nunca deve atribuir novamente uma variável de parâmetro / argumento a um objeto que não seja o objeto passado pelo método de chamada. Nos exemplos acima, nunca se deve escrever a linhaarg = . Como os humanos cometem erros e os programadores são humanos, vamos pedir ao compilador para nos ajudar. Marque cada variável de parâmetro / argumento como 'final' para que o compilador possa encontrar e sinalizar essas redesignações.

Em retrospecto

Como observado em outras respostas ... Dado o objetivo de design original do Java de ajudar os programadores a evitar erros estúpidos, como ler além do final de uma matriz, o Java deveria ter sido projetado para impor automaticamente todas as variáveis ​​de parâmetro / argumento como 'final'. Em outras palavras, argumentos não devem ser variáveis . Mas a retrospectiva é uma visão 20/20, e os designers de Java estavam com as mãos cheias na época.

Então, sempre adicione finala todos os argumentos?

Devemos adicionar finala cada parâmetro de método declarado?

  • Em teoria, sim.
  • Na prática, não.
    ➥ Adicione finalapenas quando o código do método for longo ou complicado, onde o argumento pode ser confundido com uma variável local ou membro e possivelmente redesignado.

Se você adotar a prática de nunca reatribuir um argumento, estará inclinado a adicionar um finala cada um. Mas isso é tedioso e torna a declaração um pouco mais difícil de ler.

Para um código simples e curto, onde o argumento é obviamente um argumento, e não uma variável local nem uma variável membro, não me importo em adicionar o final. Se o código for bastante óbvio, sem nenhuma chance de eu ou qualquer outro programador fazer manutenção ou refatorar acidentalmente confundir a variável de argumento como algo que não seja um argumento, não se preocupe. No meu próprio trabalho, adiciono finalapenas códigos mais longos ou mais envolvidos, nos quais um argumento pode ser confundido com uma variável local ou membro.

Outro caso adicionado para a integridade

public class MyClass {
    private int x;
    //getters and setters
}

void doSomething( final MyClass arg ) {  // Mark argument as 'final'.

   arg =  new MyClass();  // Compiler error: The passed argument variable arg  cannot be re-assigned to another object.

   arg.setX(20); // allowed
  // We can re-assign properties of argument which is marked as final
 }
Basil Bourque
fonte
23
"Como boa prática de programação (em qualquer idioma), você nunca deve reatribuir uma variável de parâmetro / argumento [..]" Desculpe, eu realmente preciso chamar você sobre esta. Reatribuir argumentos é uma prática padrão em linguagens como Javascript, onde a quantidade de argumentos passados ​​(ou mesmo se houver algum) não é ditada pela assinatura do método. Por exemplo, com uma assinatura como: "function say (msg)" as pessoas garantirão que o argumento 'msg' seja atribuído, assim: "msg = msg || 'Hello World!';". Os melhores programadores de Javascript do mundo estão quebrando suas boas práticas. Basta ler a fonte do jQuery.
Stijn de Witt
37
@StijndeWitt Seu exemplo mostra o grande problema de reatribuir a variável de argumento. Você perde informações sem obter nada em troca: (a) Você perdeu o valor original passado, (b) Você perdeu a intenção do método de chamada (o chamador passou 'Hello World!' Ou foi o padrão). Tanto a & b são úteis para teste, código longo e quando o valor é alterado posteriormente. Eu mantenho minha afirmação: arg vars nunca devem ser reatribuídos. O código deve ser: message = ( msg || 'Hello World"' ). Simplesmente não há razão para não usar um var separado. O único custo é de alguns bytes de memória.
Basil Bourque
9
@ Basil: É mais código (em bytes) e em Javascript que conta. Pesadamente. Como em muitas coisas, é baseado em opiniões. É perfeitamente possível ignorar completamente essa prática de programação e ainda escrever um código excelente. A prática de programação de uma pessoa não a torna prática de todos. Aguente tudo o que quiser, eu escolho escrever diferente de qualquer maneira. Isso me torna um programador ruim ou meu código com código ruim?
Stijn de Witt
14
Usar message = ( msg || 'Hello World"' )me arrisca depois acidentalmente msg. Quando o contrato que pretendo é "comportamento sem argumento nulo / indefinido é indistinguível de aprovação "Hello World"", é uma boa prática de programação comprometê-lo no início da função. [Isso pode ser alcançado sem reatribuição, começando com, if (!msg) return myfunc("Hello World");mas isso se torna complicado com vários argumentos.] Nos raros casos em que a lógica na função deve se importar se o padrão foi usado, prefiro designar um valor sentinela especial (de preferência público) .
Beni Cherniavsky-Paskin
6
@ BeniCherniavsky-Paskin o risco que você descreve é ​​apenas por causa da semelhança entre messagee msg. Mas se ele chamar isso de algo como processedMsgou algo mais que forneça contexto adicional - a chance de um erro é muito menor. Concentre-se no que ele diz e não em "como" ele diz. ;)
alfasin 13/05
230

Às vezes, é bom ser explícito (para facilitar a leitura) que a variável não muda. Aqui está um exemplo simples em que o uso finalpode salvar algumas dores de cabeça possíveis:

public void setTest(String test) {
    test = test;
}

Se você esquecer a palavra-chave 'this' em um setter, a variável que você deseja definir não será definida. No entanto, se você usasse a finalpalavra-chave no parâmetro, o bug seria detectado no momento da compilação.

Jerry Sha
fonte
65
btw você vai ver aviso "A atribuição de teste variável não tem efeito" de qualquer maneira
AvrDragon
12
@AvrDragon Mas, podemos ignorar o aviso também. Portanto, é sempre melhor ter algo que nos impeça de avançar, como um erro de compilação, que obteremos usando a palavra-chave final.
Sumit Desai
10
@AvrDragon Depende do ambiente de desenvolvimento. Você não deve confiar no IDE para capturar coisas assim para você, a menos que queira desenvolver maus hábitos.
b1nary.atr0phy
25
@ b1naryatr0phy na verdade, é um aviso do compilador, não apenas IDE-ponta
AvrDragon
8
@SumitDesai "Mas podemos ignorar o aviso também. Portanto, é sempre melhor ter algo que nos impeça de avançar, como um erro de compilação, que obteremos usando a palavra-chave final". Entendo, mas esta é uma afirmação muito forte com a qual acho que muitos desenvolvedores de Java discordariam. Os avisos do compilador existem por um motivo e um desenvolvedor competente não deve precisar de um erro para 'forçá-lo' a considerar suas implicações.
Stuart Rossiter #
127

Sim, excluir classes anônimas, legibilidade e declaração de intenção é quase inútil. Essas três coisas são inúteis?

Pessoalmente, costumo não usar finalvariáveis ​​e parâmetros locais, a menos que esteja usando a variável em uma classe interna anônima, mas certamente consigo entender o ponto daqueles que desejam deixar claro que o valor do parâmetro em si não será alterado (mesmo se o objeto ao qual ele se refere muda seu conteúdo). Para aqueles que acham que isso aumenta a legibilidade, acho uma coisa totalmente razoável de se fazer.

Seu ponto seria mais importante se alguém foram realmente alegando que se mantenha constante de dados de uma forma que não faz - mas eu não me lembro de ver tais reivindicações. Você está sugerindo que há um corpo significativo de desenvolvedores sugerindo que finaltem mais efeito do que realmente tem?

Edição: Eu realmente deveria ter resumido tudo isso com uma referência Monty Python; a pergunta parece um pouco semelhante a perguntar "O que os romanos já fizeram por nós?"

Jon Skeet
fonte
17
Mas, parafraseando Krusty com seu dinamarquês, o que eles fizeram por nós ultimamente ? =)
James Schek 05/02
Yuval. Isso é engraçado! Eu acho que a paz pode acontecer mesmo se for imposta por uma espada!
gonzobrains
11
A pergunta parece mais semelhante a perguntar: "O que os romanos não fizeram por nós?", Porque é mais uma crítica ao que a palavra-chave final não faz.
NobleUplift 14/10
"Você está sugerindo que há um corpo significativo de desenvolvedores sugerindo que final tem mais efeito do que realmente tem?" Para mim, esse é o principal problema: desconfio fortemente de uma proporção significativa de desenvolvedores que o utilizam, que impõem a imutabilidade dos itens passados do chamador quando não o fazem. Obviamente, alguém se empolga no debate sobre se os padrões de codificação devem "proteger-se" de mal-entendidos conceituais (dos quais um desenvolvedor "competente" deve estar ciente) ou não (e isso então se encaminha para opiniões fora do escopo do SO) pergunta do tipo)!
Stuart Rossiter
11
@SarthakMittal: O valor não será copiado a menos que você realmente o use, se é isso que você está se perguntando.
Jon tiro ao prato
76

Deixe-me explicar um pouco sobre o único caso em que você tem de usar final, que Jon já mencionado:

Se você criar uma classe interna anônima no seu método e usar uma variável local (como um parâmetro do método) dentro dessa classe, o compilador o obrigará a tornar o parâmetro final:

public Iterator<Integer> createIntegerIterator(final int from, final int to)
{
    return new Iterator<Integer>(){
        int index = from;
        public Integer next()
        {
            return index++;
        }
        public boolean hasNext()
        {
            return index <= to;
        }
        // remove method omitted
    };
}

Aqui, os parâmetros frome toprecisam ser finais para que possam ser usados ​​dentro da classe anônima.

O motivo desse requisito é o seguinte: Variáveis ​​locais vivem na pilha; portanto, elas existem apenas enquanto o método é executado. No entanto, a instância de classe anônima é retornada do método, portanto, pode permanecer por muito mais tempo. Você não pode preservar a pilha, porque ela é necessária para chamadas de método subsequentes.

Portanto, o que o Java faz é colocar cópias dessas variáveis ​​locais como variáveis ​​de instância ocultas na classe anônima (você pode vê-las se examinar o código de bytes). Mas, se não foram finais, pode-se esperar a classe anônima e o método que vê mudanças na outra variável na variável. Para manter a ilusão de que há apenas uma variável em vez de duas cópias, ela deve ser final.

Michael Borgwardt
fonte
11
Você me perdeu de "Mas se não forem finais ..." Você pode tentar reformular, talvez eu não tenha tido café suficiente.
21239 hhafez
11
Você tem variáveis ​​locais de + a questão é o que acontece se você usar a instância da classe anon dentro do método e alterar os valores de + as pessoas esperariam que a alteração fosse visível no método, uma vez que elas veem apenas uma variável. Para evitar essa confusão, ela deve ser final.
227 Michael Borgwardt
Não faz uma cópia, é simplesmente uma referência a qualquer objeto que foi referenciado.
vickirk
11
@vickirk: certifique-se de fazer uma cópia da referência, no caso de tipos de referência.
Michael Borgwardt
Supondo que não tenhamos classes anônimas fazendo referência a essas variáveis, você sabe se há alguma diferença entre um finalparâmetro de função e um parâmetro de função não final aos olhos do HotSpot?
Pacerier 17/11
25

Eu uso final o tempo todo em parâmetros.

Isso adiciona muito? Na verdade não.

Eu desligaria? Não.

O motivo: eu encontrei 3 bugs em que as pessoas haviam escrito código incorreto e não conseguiram definir uma variável de membro nos acessadores. Todos os erros foram difíceis de encontrar.

Eu gostaria de ver isso tornar o padrão em uma versão futura do Java. A passagem por valor / referência gera um grande número de programadores juniores.

Mais uma coisa ... meus métodos tendem a ter um número baixo de parâmetros, portanto o texto extra em uma declaração de método não é um problema.

Fortyrunner
fonte
4
Eu estava prestes a sugerir isso também, que final seja o padrão em versões futuras e que você precise especificar uma palavra-chave "mutável" ou melhor que seja concebida. Aqui está um artigo agradável sobre isto: lpar.ath0.com/2008/08/26/java-annoyance-final-parameters
Jeff Axelrod
Já faz muito tempo, mas você poderia fornecer um exemplo do bug detectado?
user949300
Veja a resposta mais votada. Esse é realmente um bom exemplo em que a variável membro não está definida e, em vez disso, o parâmetro é alterado.
Fortyrunner 27/09/17
18

Usar final em um parâmetro de método não tem nada a ver com o que acontece com o argumento no lado do chamador. Ele serve apenas para marcá-lo como não sendo alterado dentro desse método. Enquanto tento adotar um estilo de programação mais funcional, meio que vejo o valor disso.

Alemão
fonte
2
Exatamente, não faz parte da interface da função, apenas da implementação. É confuso que o java permita (mas dirsregards) os final parâmetros nas interfaces / declarações de método abstrato.
usar o seguinte
8

Pessoalmente, eu não uso final nos parâmetros do método, porque adiciona muita confusão às listas de parâmetros. Prefiro impor que os parâmetros do método não sejam alterados através de algo como o Checkstyle.

Para variáveis ​​locais que uso final sempre que possível, deixo o Eclipse fazer isso automaticamente na minha configuração para projetos pessoais.

Eu certamente gostaria de algo mais forte como C / C ++ const.

starblue
fonte
Não tenho certeza se as referências de IDE e ferramenta são aplicáveis ​​à postagem do OP ou ao tópico. Ou seja, "final" é uma verificação em tempo de compilação para que a referência não seja alterada / oculta. Além disso, para realmente aplicar essas coisas, consulte a resposta sobre ausência de proteção para crianças membros de referências finais. Ao criar uma API, por exemplo, o uso de uma IDE ou ferramenta não ajudará terceiros a usar / estender esse código.
Darrell Teague
4

Como o Java passa cópias de argumentos, sinto que a relevância finalé bastante limitada. Eu acho que o hábito vem da era C ++, onde você pode proibir que o conteúdo de referência seja alterado fazendo um const char const *. Eu sinto que esse tipo de coisa faz você acreditar que o desenvolvedor é inerentemente estúpido pra caralho e precisa ser protegido contra todos os personagens que ele digita. Com toda a humildade, posso dizer, escrevo muito poucos bugs, embora o omita final(a menos que não queira que alguém substitua meus métodos e classes). Talvez eu seja apenas um desenvolvedor da velha escola.

Lawrence
fonte
1

Eu nunca uso final em uma lista de parâmetros, apenas adiciona confusão como os entrevistados anteriores disseram. Também no Eclipse, você pode definir a atribuição de parâmetros para gerar um erro, portanto, usar final em uma lista de parâmetros parece bastante redundante para mim. Curiosamente, quando ativei a configuração do Eclipse para a atribuição de parâmetros, gerando um erro, ele capturou esse código (é assim que me lembro do fluxo, não do código real.): -

private String getString(String A, int i, String B, String C)
{
    if (i > 0)
        A += B;

    if (i > 100)
        A += C;

    return A;
}

Brincando de advogado do diabo, o que exatamente há de errado em fazer isso?

Chris Milburn
fonte
Seja grato por diferenciar um IDE de uma JVM de tempo de execução. Tudo o que um IDE faz é irrelevante quando o código de bytes compilado é executado no servidor, a menos que o IDE tenha adicionado código para proteger contra invasão de variável de membro, como uma falha no código, quando se pretendia que uma variável não deveria ser reatribuída, mas por engano era - daí o objetivo de palavra-chave final.
Darrell Teague
1

Resposta curta: final ajuda um pouquinho, mas ... em vez disso, use programação defensiva no lado do cliente.

De fato, o problema finalé que ele apenas impõe a referência inalterada, permitindo que os membros do objeto referenciado sejam mutados, sem o conhecimento do chamador. Portanto, a melhor prática a esse respeito é a programação defensiva do lado do chamador, criando instâncias profundamente imutáveis ​​ou cópias profundas de objetos que correm o risco de serem invadidas por APIs inescrupulosas.

Darrell Teague
fonte
2
"o problema com final é que ele apenas impõe a referência inalterada" - Falso, o próprio Java impede isso. Uma variável passada para um método não pode ter sua referência alterada por esse método.
Madbreaks
Pesquise antes de postar ... stackoverflow.com/questions/40480/…
Darrell Teague
Simplificando, se fosse verdade que as referências a referências não podem ser alterados, não haveria discussão de defesa de cópia, imutabilidade, não há necessidade de palavra-chave final, etc.
Darrell Teague
Ou você está me entendendo mal ou está enganado. Se eu passar uma referência de objeto a um método, e esse método a redesignar, a referência original permanecerá intacta para (eu) o chamador quando o método concluir sua execução. Java é estritamente transmitido por valor. E você é descaradamente presunçoso em afirmar que não fiz nenhuma pesquisa.
precisa saber é o seguinte
Voto negativo porque o op perguntou por que usar final, e você forneceu apenas um motivo incorreto.
precisa saber é o seguinte
0

Um motivo adicional para adicionar declarações finais aos parâmetros é que ajuda a identificar variáveis ​​que precisam ser renomeadas como parte de uma refatoração "Extrair método". Eu descobri que adicionar final a cada parâmetro antes de iniciar uma refatoração de método grande rapidamente informa se há algum problema que eu precise resolver antes de continuar.

No entanto, geralmente os removo como supérfluos no final da refatoração.

Michael Rutherfurd
fonte
-1

Acompanhamento pelo que Michel post. Eu mesmo me fiz outro exemplo para explicá-lo. Espero que possa ajudar.

public static void main(String[] args){
    MyParam myParam = thisIsWhy(new MyObj());
    myParam.setArgNewName();

    System.out.println(myParam.showObjName());
}

public static MyParam thisIsWhy(final MyObj obj){
    MyParam myParam = new MyParam() {
        @Override
        public void setArgNewName() {
            obj.name = "afterSet";
        }

        @Override
        public String showObjName(){
            return obj.name;
        }
    };

    return myParam;
}

public static class MyObj{
    String name = "beforeSet";
    public MyObj() {
    }
}

public abstract static class MyParam{
    public abstract void setArgNewName();
    public abstract String showObjName();
}

A partir do código acima, no método thisIsWhy () , na verdade , não atribuímos o [argumento MyObj obj] a uma referência real em MyParam. Em vez disso, usamos apenas o [argumento MyObj obj] no método dentro de MyParam.

Mas, após concluirmos o método thisIsWhy () , o argumento (objeto) MyObj ainda existe?

Parece que deveria, porque podemos ver no main que ainda chamamos o método showObjName () e ele precisa atingir obj . O MyParam ainda usa / alcança o argumento do método, mesmo o método já foi retornado!

Como o Java realmente consegue isso é gerar uma cópia também é uma referência oculta do argumento MyObj obj dentro do objeto MyParam (mas não é um campo formal em MyParam para que não possamos vê-lo)

Como chamamos "showObjName", ele usará essa referência para obter o valor correspondente.

Mas se não colocarmos o argumento final, o que leva a uma situação, podemos reatribuir uma nova memória (objeto) ao argumento MyObj obj .

Tecnicamente, não há conflito! Se nos for permitido fazer isso, abaixo será a situação:

  1. Agora temos um ponto [MyObj obj] oculto para um [Memory A in heap] agora ao vivo no objeto MyParam.
  2. Também temos outro [MyObj obj], que é o ponto de argumento para uma [Memory B in heap] que agora vive neste método thisIsWhy.

Sem confrontos, mas "CONFUSO !!" Porque todos eles estão usando o mesmo "nome de referência", que é "obj" .

Para evitar isso, defina-o como "final" para evitar que o programador faça o código "propenso a erros".

KunYu Tsai
fonte