O uso da palavra-chave final em Java melhora o desempenho?

347

Em Java, vemos muitos lugares onde a finalpalavra - chave pode ser usada, mas seu uso é incomum.

Por exemplo:

String str = "abc";
System.out.println(str);

No caso acima, strpode ser, finalmas isso geralmente é deixado de lado.

Quando um método nunca será substituído, podemos usar a palavra-chave final. Da mesma forma, no caso de uma classe que não será herdada.

O uso da palavra-chave final em algum ou em todos esses casos realmente melhora o desempenho? Se sim, então como? Por favor explique. Se o uso adequado finalrealmente importa para o desempenho, que hábitos um programador Java deve desenvolver para fazer o melhor uso da palavra-chave?

Abhishek Jain
fonte
Eu não penso assim pal, o método de envio (caching local chamada e ...) é uma questão em linguagens dinâmicas não em linguagens tipo estático
Jahan
Se eu executar minha ferramenta PMD (plugin para eclipse) usada para fins de revisão, é recomendável fazer alterações na variável, conforme mostrado acima. Mas eu não entendi seu conceito. Realmente o desempenho atinge tanto?
Abhishek Jain
5
Eu pensei que essa era uma pergunta típica para o exame. Lembro-me de que Final faz tem influência no desempenho, IIRC classes final pode ser otimizado pelo JRE, de alguma forma, porque eles não podem ser uma subclasse.
Kawu
Na verdade, eu testei isso. Em todas as JVMs Eu testei o uso de final sobre as variáveis locais fez melhorar o desempenho (um pouco, mas, no entanto, pode ser um factor de métodos utilitários). O código fonte está na minha resposta abaixo.
Rustyx
11
"Otimização prematura é a raiz de todo o mal". Apenas deixe o compilador fazer seu trabalho. Escreva código legível e bom comentado. Essa é sempre a melhor escolha!
Kaiser

Respostas:

285

Geralmente não. Para métodos virtuais, o HotSpot controla se o método foi realmente substituído e é capaz de executar otimizações, como alinhar na suposição de que um método não foi substituído - até carregar uma classe que substitui o método, nesse momento pode desfazer (ou desfazer parcialmente) essas otimizações.

(Obviamente, isso supõe que você esteja usando o HotSpot - mas é de longe a JVM mais comum, então ...)

Na minha opinião, você deve usar com finalbase em design e legibilidade claros, e não por razões de desempenho. Se você quiser alterar alguma coisa por motivos de desempenho, execute medições apropriadas antes de alterar o código mais claro de forma - para que você possa decidir se qualquer desempenho extra alcançado vale a pena a legibilidade / design mais baixos. (Na minha experiência, quase nunca vale a pena; YMMV.)

EDIT: Como os campos finais foram mencionados, vale ressaltar que eles geralmente são uma boa ideia, em termos de design claro. Eles também alteram o comportamento garantido em termos de visibilidade de encadeamento cruzado: depois que um construtor é concluído, todos os campos finais ficam visíveis em outros encadeamentos imediatamente. Esse é provavelmente o uso mais comum finalna minha experiência, embora, como defensor da regra básica de Josh Bloch "design para herança ou proibição", eu provavelmente deva usar finalmais frequentemente para aulas ...

Jon Skeet
fonte
11
@Abhishek: Sobre o que em particular? O ponto mais importante é o último - que você quase certamente não deveria se preocupar com isso.
precisa
9
@Abishek: finalgeralmente é recomendado porque facilita a compreensão do código e ajuda a encontrar bugs (porque torna explícita a intenção dos programadores). O PMD provavelmente recomenda o uso finalpor causa desses problemas de estilo, não por motivos de desempenho.
sleske
3
@Abhishek: Muito provavelmente é específico da JVM e pode depender de aspectos muito sutis do contexto. Por exemplo, acredito que a JVM do servidor HotSpot ainda permitirá a inclusão de métodos virtuais quando substituída em uma classe, com uma verificação rápida de tipo, quando apropriado. Mas os detalhes são difíceis de definir e podem mudar entre os lançados.
precisa
5
Aqui eu citaria Effective Java, 2nd Edition, ponto 15, Minimizar mutabilidade: Immutable classes are easier to design, implement, and use than mutable classes. They are less prone to error and are more secure.. Além disso An immutable object can be in exactly one state, the state in which it was created.vs Mutable objects, on the other hand, can have arbitrarily complex state spaces.. Da minha experiência pessoal, o uso da palavra final- chave deve destacar a intenção do desenvolvedor de se inclinar para a imutabilidade, não de "otimizar" o código. Encorajo-vos a ler este capítulo, fascinante!
18776 Louis
2
Outras respostas mostram que o uso da finalpalavra-chave em variáveis ​​pode reduzir a quantidade de bytecode, o que pode produzir um efeito no desempenho.
Julien Kronegg
86

Resposta curta: não se preocupe!

Resposta longa:

Ao falar sobre variáveis ​​locais finais, lembre-se de que o uso da palavra-chave finalajudará o compilador a otimizar o código estaticamente , o que pode resultar em um código mais rápido. Por exemplo, as seqüências finais a + bno exemplo abaixo são concatenadas estaticamente (em tempo de compilação).

public class FinalTest {

    public static final int N_ITERATIONS = 1000000;

    public static String testFinal() {
        final String a = "a";
        final String b = "b";
        return a + b;
    }

    public static String testNonFinal() {
        String a = "a";
        String b = "b";
        return a + b;
    }

    public static void main(String[] args) {
        long tStart, tElapsed;

        tStart = System.currentTimeMillis();
        for (int i = 0; i < N_ITERATIONS; i++)
            testFinal();
        tElapsed = System.currentTimeMillis() - tStart;
        System.out.println("Method with finals took " + tElapsed + " ms");

        tStart = System.currentTimeMillis();
        for (int i = 0; i < N_ITERATIONS; i++)
            testNonFinal();
        tElapsed = System.currentTimeMillis() - tStart;
        System.out.println("Method without finals took " + tElapsed + " ms");

    }

}

O resultado?

Method with finals took 5 ms
Method without finals took 273 ms

Testado no Java Hotspot VM 1.7.0_45-b18.

Então, quanto é a melhoria real de desempenho? Não ouso dizer. Na maioria dos casos, provavelmente marginal (~ 270 nanossegundos neste teste sintético porque a concatenação de cadeias é totalmente evitada - um caso raro), mas no código de utilitário altamente otimizado, pode ser um fator. De qualquer forma, a resposta à pergunta original é sim, pode melhorar o desempenho, mas marginalmente na melhor das hipóteses .

Além dos benefícios em tempo de compilação, não pude encontrar nenhuma evidência de que o uso da palavra final- chave tenha algum efeito mensurável no desempenho.

rustyx
fonte
2
Reescrevi seu código um pouco para testar os dois casos 100 vezes. Eventualmente, o tempo médio da final foi de 0 ms e 9 ms para a não final. Aumentar a contagem de iterações para 10M define a média para 0 ms e 75 ms. A melhor corrida para a não final foi de 0 ms. Talvez seja a VM detectando resultados que estão sendo jogados fora ou algo assim? Não sei, mas, independentemente disso, o uso de final faz uma diferença significativa.
Casper Færgemand
5
Teste defeituoso. Os testes anteriores aquecerão a JVM e beneficiarão as invocações de teste posteriores. Reordene seus testes e veja o que acontece. Você precisa executar cada teste em sua própria instância da JVM.
Steve Kuo
16
Não, o teste não tem falhas, o aquecimento foi levado em consideração. O segundo teste é mais lento, não mais rápido. Sem aquecimento, o segundo teste seria MESMO MAIS LENTO.
rustyx
6
Em testFinal (), o tempo todo retornou o mesmo objeto, do pool de strings, porque a suspeita de strings finais e concatenação de literais de strings são avaliadas em tempo de compilação. testNonFinal () toda vez que retorna um novo objeto, isso explica a diferença de velocidade.
Anber
4
O que faz você pensar que o cenário não é realista? Stringconcatenação é uma operação muito mais cara do que adicionar Integers. Fazer isso estaticamente (se possível) é mais eficiente, é o que o teste mostra.
Rustyx
62

Sim pode. Aqui está um exemplo em que final pode aumentar o desempenho:

Compilação condicional é uma técnica na qual linhas de código não são compiladas no arquivo de classe com base em uma condição específica. Isso pode ser usado para remover toneladas de código de depuração em uma construção de produção.

considere o seguinte:

public class ConditionalCompile {

  private final static boolean doSomething= false;

    if (doSomething) {
       // do first part. 
    }

    if (doSomething) {
     // do second part. 
    }

    if (doSomething) {     
      // do third part. 
    }

    if (doSomething) {
    // do finalization part. 
    }
}

Ao converter o atributo doSomething em um atributo final, você disse ao compilador que sempre que vir doSomething, ele deve ser substituído por false, conforme as regras de substituição em tempo de compilação. A primeira passagem do compilador altera o código para algo assim:

public class ConditionalCompile {

  private final static boolean doSomething= false;

    if (false){
       // do first part. 
    }

    if (false){
     // do second part. 
    }

    if (false){
      // do third part. 
    }

    if (false){
    // do finalization part. 

    }
}

Uma vez feito isso, o compilador dá uma outra olhada e vê que há instruções inacessíveis no código. Como você está trabalhando com um compilador de alta qualidade, ele não gosta de todos esses códigos de bytes inacessíveis. Portanto, eles são removidos e você acaba com isso:

public class ConditionalCompile {


  private final static boolean doSomething= false;

  public static void someMethodBetter( ) {

    // do first part. 

    // do second part. 

    // do third part. 

    // do finalization part. 

  }
}

reduzindo assim códigos excessivos ou qualquer verificação condicional desnecessária.

Editar: Como exemplo, vamos dar o seguinte código:

public class Test {
    public static final void main(String[] args) {
        boolean x = false;
        if (x) {
            System.out.println("x");
        }
        final boolean y = false;
        if (y) {
            System.out.println("y");
        }
        if (false) {
            System.out.println("z");
        }
    }
}

Ao compilar esse código com Java 8 e descompilar com javap -c Test.class, obtemos:

public class Test {
  public Test();
    Code:
       0: aload_0
       1: invokespecial #8                  // Method java/lang/Object."<init>":()V
       4: return

  public static final void main(java.lang.String[]);
    Code:
       0: iconst_0
       1: istore_1
       2: iload_1
       3: ifeq          14
       6: getstatic     #16                 // Field java/lang/System.out:Ljava/io/PrintStream;
       9: ldc           #22                 // String x
      11: invokevirtual #24                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      14: iconst_0
      15: istore_2
      16: return
}

Podemos observar que o código compilado inclui apenas a variável não final x. Isso prova que as variáveis ​​finais têm impacto no desempenho, pelo menos neste caso simples.

mel3kings
fonte
11
@ ŁukaszLech Aprendi isso com um livro da Oreilly: Hardcore Java, em seu capítulo sobre a palavra-chave final.
precisa saber é o seguinte
15
Isso fala sobre otimização no momento da compilação, significa que o desenvolvedor conhece o VALUE da variável booleana final no momento da compilação, qual é o objetivo de escrever se o bloco é bloqueado em primeiro lugar e depois nesse cenário em que as IF-CONDITIONS não são necessárias e não estão qualquer sentido? Na minha opinião, mesmo que isso melhore o desempenho, esse é um código errado em primeiro lugar e pode ser otimizado pelo próprio desenvolvedor, em vez de passar a responsabilidade do compilador, e a pergunta principalmente pretende perguntar sobre as melhorias de desempenho nos códigos usuais onde final é usado e faz sentido programático.
Bhavesh Agarwal
7
O objetivo disso seria adicionar instruções de depuração como indicado pela mel3kings. Você pode inverter a variável antes de uma construção de produção (ou configurá-la em seus scripts de construção) e remover automaticamente todo esse código quando a distribuição é criada.
Adam
16

Estou surpreso que ninguém tenha realmente postado algum código real que seja descompilado para provar que há pelo menos alguma diferença menor.

Para a referência este foi testado contra a javacversão 8, 9e 10.

Suponha que este método:

public static int test() {
    /* final */ Object left = new Object();
    Object right = new Object();

    return left.hashCode() + right.hashCode();
}

Compilar esse código como ele é, produz exatamente o mesmo código de byte de quando finalestaria presente ( final Object left = new Object();).

Mas este:

public static int test() {
    /* final */ int left = 11;
    int right = 12;
    return left + right;
}

Produz:

   0: bipush        11
   2: istore_0
   3: bipush        12
   5: istore_1
   6: iload_0
   7: iload_1
   8: iadd
   9: ireturn

Deixar finalde estar presente produz:

   0: bipush        12
   2: istore_1
   3: bipush        11
   5: iload_1
   6: iadd
   7: ireturn

O código é praticamente auto-explicativo, caso exista uma constante de tempo de compilação, ele será carregado diretamente na pilha de operandos (não será armazenado na matriz de variáveis ​​locais, como o exemplo anterior faz via bipush 12; istore_0; iload_0) - o que meio que faz sentido já que ninguém pode mudar isso.

Por outro lado, por que no segundo caso o compilador não produz istore_0 ... iload_0está além de mim, não é como se esse slot 0fosse usado de alguma forma (poderia encolher a matriz de variáveis ​​dessa maneira, mas pode estar faltando alguns detalhes internos, não é possível diga com certeza)

Fiquei surpreso ao ver essa otimização, considerando o quanto os pequenos javacfazem. Como devemos sempre usarfinal ? Eu nem vou escrever um JMHteste (o que eu queria inicialmente), tenho certeza de que o diff está na ordem de ns(se possível, pode ser capturado). O único lugar em que isso pode ser um problema é quando um método não pode ser incorporado devido ao seu tamanho (e a declaração finalreduziria esse tamanho em alguns bytes).

Há mais dois finals que precisam ser abordados. Primeiro, quando um método é final(de uma JITperspectiva), esse método é monomórfico - e estes são os mais amados pelo JVM.

Depois, existem finalvariáveis ​​de instância (que devem ser definidas em todos os construtores); estes são importantes, pois garantirão uma referência publicada corretamente, conforme tocado um pouco aqui e também especificado exatamente pelo JLS.

Eugene
fonte
Compilei o código usando Java 8 (JDK 1.8.0_162) com as opções de depuração ( javac -g FinalTest.java), descompilei o código usando javap -c FinalTest.classe não final int left=12obtive os mesmos resultados (com , obtive bipush 11; istore_0; bipush 12; istore_1; bipush 11; iload_1; iadd; ireturn). Portanto, sim, o código de byte gerado depende de muitos fatores e é difícil dizer se ter finalou não produzir um efeito no desempenho. Mas como o bytecode é diferente, pode existir alguma diferença de desempenho.
Julien Kronegg
13

Você está realmente perguntando sobre dois (pelo menos) casos diferentes:

  1. final para variáveis ​​locais
  2. final para métodos / classes

Jon Skeet já respondeu 2). Cerca de 1):

Eu não acho que isso faça diferença; para variáveis ​​locais, o compilador pode deduzir se a variável é final ou não (simplesmente verificando se foi atribuída mais de uma vez). Portanto, se o compilador quiser otimizar variáveis ​​atribuídas apenas uma vez, poderá fazê-lo, independentemente de a variável ser realmente declarada finalou não.

final poderia fazer a diferença para campos protegidos / de classe pública; é muito difícil para o compilador descobrir se o campo está sendo definido mais de uma vez, pois isso pode acontecer a partir de uma classe diferente (que pode até não ter sido carregada). Mas, mesmo assim, a JVM poderia usar a técnica que Jon descreve (otimizar de maneira otimista, reverter se houver uma classe carregada que altere o campo).

Em resumo, não vejo nenhum motivo para ajudar o desempenho. Portanto, é improvável que esse tipo de micro-otimização ajude. Você pode tentar compará-lo para ter certeza, mas duvido que faça alguma diferença.

Editar:

Na verdade, de acordo com a resposta de Timo Westkämper, final pode melhorar o desempenho dos campos de classe em alguns casos. Eu estou corrigido.

sleske
fonte
Eu não acho que o compilador possa verificar corretamente o número de vezes que uma variável local é atribuída: e quanto a estruturas if-then-else com inúmeras atribuições?
gyorgyabraham
11
@gyabraham: O compilador já verifica esses casos se você declarar uma variável local como final, para garantir que você não a atribua duas vezes. Tanto quanto posso ver, a mesma lógica de verificação pode ser (e provavelmente é) usada para verificar se uma variável poderia ser final.
sleske
A final-ness de uma variável local não é expresso no bytecode, assim, a JVM ainda não sabe que ele era final
Steve Kuo
11
@SteveKuo: Mesmo que não seja expresso no bytecode, pode ajudar javaca otimizar melhor. Mas isso é apenas especulação.
sleske
11
O compilador pode descobrir se uma variável local foi atribuída exatamente uma vez, mas, na prática, não é (além da verificação de erros). Por outro lado, se uma finalvariável é de um tipo ou tipo primitivo Stringe é imediatamente atribuída com uma constante em tempo de compilação, como no exemplo da pergunta, o compilador deve incorporá-la, pois a variável é uma constante em tempo de compilação por especificação. Mas para a maioria dos casos de uso, o código pode parecer diferente, mas ainda não faz diferença se a constante é incorporada ou lida a partir de uma variável local em termos de desempenho.
Holger
6

Nota: Não é um especialista em java

Se eu me lembro corretamente do java, haveria muito pouca maneira de melhorar o desempenho usando a palavra-chave final. Eu sempre soube que ele existe para "bom código" - design e legibilidade.

Neowizard
fonte
1

Não sou especialista, mas acho que você deve adicionar uma finalpalavra-chave à classe ou método, se não for substituída e deixar as variáveis ​​em paz. Se houver alguma maneira de otimizar essas coisas, o compilador fará isso por você.

Crozin
fonte
1

Na verdade, ao testar algum código relacionado ao OpenGL, descobri que o uso do modificador final em um campo privado pode prejudicar o desempenho. Aqui está o início da classe que testei:

public class ShaderInput {

    private /* final */ float[] input;
    private /* final */ int[] strides;


    public ShaderInput()
    {
        this.input = new float[10];
        this.strides = new int[] { 0, 4, 8 };
    }


    public ShaderInput x(int stride, float val)
    {
        input[strides[stride] + 0] = val;
        return this;
    }

    // more stuff ...

E este é o método que eu usei para testar o desempenho de várias alternativas, entre as quais a classe ShaderInput:

public static void test4()
{
    int arraySize = 10;
    float[] fb = new float[arraySize];
    for (int i = 0; i < arraySize; i++) {
        fb[i] = random.nextFloat();
    }
    int times = 1000000000;
    for (int i = 0; i < 10; ++i) {
        floatVectorTest(times, fb);
        arrayCopyTest(times, fb);
        shaderInputTest(times, fb);
        directFloatArrayTest(times, fb);
        System.out.println();
        System.gc();
    }
}

Após a terceira iteração, com a VM aquecida, obtive consistentemente esses números sem a palavra-chave final:

Simple array copy took   : 02.64
System.arrayCopy took    : 03.20
ShaderInput took         : 00.77
Unsafe float array took  : 05.47

Com a palavra-chave final:

Simple array copy took   : 02.66
System.arrayCopy took    : 03.20
ShaderInput took         : 02.59
Unsafe float array took  : 06.24

Observe as figuras para o teste ShaderInput.

Não importava se eu tornava os campos públicos ou privados.

Aliás, existem mais algumas coisas desconcertantes. A classe ShaderInput supera todas as outras variantes, mesmo com a palavra-chave final. Isso é notável porque basicamente é uma classe que envolve uma matriz de flutuação, enquanto os outros testam diretamente manipulam a matriz. Tem que descobrir isso. Pode ter algo a ver com a interface fluente do ShaderInput.

Também System.arrayCopy, na verdade, aparentemente é um pouco mais lento para pequenas matrizes do que simplesmente copiar elementos de uma matriz para outra em um loop for. E o uso do sun.misc.Unsafe (bem como do java.nio.FloatBuffer direto, não mostrado aqui) tem um desempenho abismal.

user3663845
fonte
11
Você esqueceu de tornar os parâmetros finais. <pre> <code> public ShaderInput x (final int stride, final float val) {input [strides [stride] + 0] = val; devolva isso; } </code> </pre> Nas minhas experiências, fazer qualquer variável ou final de campo pode realmente aumentar o desempenho.
Anticro 28/11
11
Ah, e também torne os outros finais também: <pre> <code> final int arraySize = 10; float final [] fb = new float [arraySize]; for (int i = 0; i <tamanho da matriz; i ++) {fb [i] = random.nextFloat (); } final int times = 1000000000; for (int i = 0; i <10; ++ i) {floatVectorTest (times, fb); arrayCopyTest (times, fb); shaderInputTest (times, fb); directFloatArrayTest (times, fb); System.out.println (); System.gc (); } </code> </pre>
Anticro 28/11
1

Final (pelo menos para variáveis ​​e parâmetros membros) é mais para seres humanos do que para a máquina.

É uma boa prática tornar as variáveis ​​finais sempre que possível. Eu gostaria que o Java tivesse feito "variáveis" finais por padrão e tivesse uma palavra-chave "Mutável" para permitir alterações. Classes imutáveis ​​levam a um código encadeado muito melhor, e apenas olhar para uma classe com "final" na frente de cada membro mostrará rapidamente que é imutável.

Outro caso - eu tenho convertido muito código para usar anotações @ NonNull / @ Nullable (você pode dizer que um parâmetro de método não deve ser nulo; o IDE pode avisar você em todos os lugares em que passar uma variável que não esteja marcada @ NonNull - a coisa toda se espalha a um nível ridículo). É muito mais fácil provar que uma variável de membro ou parâmetro não pode ser nulo quando é marcado como final, pois você sabe que não está sendo redesignado em nenhum outro lugar.

Minha sugestão é adquirir o hábito de aplicar final para membros e parâmetros por padrão. São apenas alguns caracteres, mas o levarão a melhorar seu estilo de codificação, se nada mais.

Final para métodos ou classes é outro conceito, pois não permite uma forma de reutilização muito válida e não diz muito ao leitor. O melhor uso é provavelmente o modo como eles fizeram o String e os outros tipos intrínsecos finais, para que você pudesse confiar em um comportamento consistente em todos os lugares - Isso evitou muitos bugs (embora haja momentos em que eu adoraria estender o string ... possibilidades)

Bill K
fonte
0

Como mencionado em outro lugar, 'final' para uma variável local e, em menor grau, uma variável membro, é mais uma questão de estilo.

'final' é uma declaração que você pretende que a variável não mude (ou seja, a variável não varia!). O compilador pode ajudá-lo, reclamando se você violar sua própria restrição.

Compartilho o sentimento de que Java teria sido uma linguagem melhor se os identificadores (desculpe, eu simplesmente não posso chamar uma coisa não variável de 'variável') foram finais por padrão, e exigi que você dissesse explicitamente que eram variáveis. Mas, tendo dito isso, geralmente não uso 'final' em variáveis ​​locais que são inicializadas e nunca atribuídas; apenas parece muito barulhento.

(Eu uso final em variáveis ​​de membro)

user13752845
fonte
-3

Os membros declarados com final estarão disponíveis durante todo o programa, porque, diferentemente dos não-finais, se esses membros não tiverem sido usados ​​no programa, eles ainda não serão atendidos pelo Garbage Collector, portanto, poderão causar problemas de desempenho devido ao mau gerenciamento de memória.

nams
fonte
2
Que tipo de bobagem é essa? Você tem alguma fonte por que acha que é esse o caso?
J. Doe
-4

final A palavra-chave pode ser usada de cinco maneiras em Java.

  1. Uma aula é final
  2. Uma variável de referência é final
  3. Uma variável local é final
  4. Um método é final

Uma classe é final: uma classe é final significa que não podemos ser estendidos ou herança significa que herança não é possível.

Da mesma forma - Um objeto é final: em algum momento não modificamos o estado interno do objeto, portanto, nesse caso, podemos especificar que o objeto é objeto final. Objeto final significa não variável e também final.

Depois que a variável de referência é finalizada, ela não pode ser reatribuída para outro objeto. Mas pode alterar o conteúdo do objeto, desde que seus campos não sejam finais

adityaraj
fonte
2
Por "objeto é final", você quer dizer "objeto é imutável".
gyorgyabraham
6
Você está certo, mas não está respondendo à pergunta. O OP não perguntou o que finalsignifica, mas se o uso finalinfluencia o desempenho.
Honza Zidek