Guia rápido de inferência de tipos locais para Java 10 e versões posteriores!
Stuart Marks
26 de abril de 2018
0 minutos de leitura
Boas-vindas à primeira edição de uma nova série de guias rápidos que publicaremos no blog da Snyk. Vamos oferecer conteúdo para você imprimir e deixar à vista, ajudando você a se tornar uma pessoa desenvolvedora melhor. Nesta primeira edição, logo após o lançamento do Java 10, vamos abordar a tão comentada inferência de tipos para variáveis locais. Você pode baixar a versão em PDF clicando na imagem acima ou neste link! A ideia por trás do recurso de inferência de tipos locais é bem simples: substitua o tipo explícito na declaração pelo novo nome de tipo reservado ‘var’, e o tipo será inferido. Assim, podemos substituir:
por:
E o tipo de outputStream será inferido como ByteArrayOutputStream. Espere aí: isso quer dizer que agora Java permite tipagem dinâmica? De jeito nenhum! TODA inferência de tipos ocorre em tempo de compilação, e o compilador grava os tipos explícitos no bytecode. Em tempo de execução, Java continua tão estático quanto sempre foi. Como o uso é tão simples, este guia rápido se concentra no aspecto mais importante da inferência de tipos locais: o uso prático. Ele orienta você sobre quando usar tipagem explícita e quando considerar a inferência de tipos.
Enquanto eu preparava este guia rápido, Stuart Marks, engenheiro do JDK na Oracle, escreveu o artigo perfeito, com princípios de programação e orientações sobre o uso da inferência de tipos. Então, quando decidi criar um guia rápido, procurei Stuart para saber se poderíamos incluir as ideias dele e resumir tudo em um material para pessoas desenvolvedoras deixarem à vista e consultarem no dia a dia! Recomendo muito que você leia o artigo completo de Stuart. Vale mesmo a pena!
Princípios
1. Ler código > Escrever código.
Não importa se você leva 10 minutos ou 10 dias para escrever uma linha de código: é quase certo que vai lê-la por muitos anos. O código só será fácil de manter e entender no futuro se for claro, conciso e, acima de tudo, contiver todas as informações necessárias para que seu propósito seja compreendido. O objetivo é maximizar a compreensão.
2. O código deve ser claro com base apenas no contexto local.
Inclua o máximo de informações possível no código para evitar que quem o lê tenha de consultar diferentes partes da base de código para entender o que está acontecendo. Isso pode ser feito com nomes de métodos ou variáveis.
3. A legibilidade do código não deve depender das IDEs.
As IDEs podem ser ótimas. Quero dizer, ótimas mesmo! Elas podem aumentar a produtividade ou a precisão de quem desenvolve. Mas o código precisa ser legível e compreensível sem depender de uma IDE. Muitas vezes, ele é lido fora de uma IDE. Além disso, as IDEs podem diferir na quantidade de informações que oferecem a quem lê. O código deve ser autoexplicativo: precisa ser compreensível por si só, sem ajuda de ferramentas.
A decisão é sua.
Escolher entre declarar o tipo de uma variável explicitamente ou deixar que o compilador Java o determine envolve uma troca. Por um lado, você quer reduzir a poluição visual, o código repetitivo e a burocracia. Por outro, não quer prejudicar a compreensão do código. A declaração do tipo não é a única forma de transmitir informações a quem lê. O nome da variável e a expressão que a inicializa também ajudam. Ao decidir se vale a pena omitir o tipo explícito em cada variável, devemos levar em conta todos os recursos disponíveis.
Orientações
1. Escolha nomes de variáveis que tragam informações úteis.
Essa é uma boa prática em geral, mas é ainda mais importante ao usar var. Em uma declaração com var, o nome da variável pode transmitir informações sobre seu significado e uso. Substituir um tipo explícito por var muitas vezes deve vir acompanhado de um nome melhor para a variável. Em alguns casos, pode ser útil incluir o tipo no nome. Por exemplo:
2. Minimize o escopo das variáveis locais.
Limitar o escopo de variáveis locais é uma recomendação de Effective Java (3ª edição), item 57. Ela é ainda mais importante quando se usa var. O problema surge quando a variável tem um escopo amplo, ou seja, há muitas linhas de código entre sua declaração e seu uso. Com a manutenção do código, alterações nos tipos e outros ajustes podem mudar o comportamento. Por exemplo, trocar uma List por um Set pode parecer inofensivo, mas será que o código depende da ordenação mais adiante nesse mesmo escopo? Embora os tipos sejam sempre definidos estaticamente, diferenças sutis entre implementações que usam a mesma interface podem causar problemas. Em vez de simplesmente evitar var nesses casos, altere o código para reduzir o escopo das variáveis locais e só então declare-as com var. Considere este código:
Agora há um bug no código, porque os conjuntos não têm uma ordem de iteração definida. No entanto, é provável que a pessoa que programou corrija esse bug imediatamente, já que os usos da variável items ficam próximos à declaração. Agora imagine que esse código faça parte de um método grande, com um escopo igualmente amplo para a variável items:
Agora fica bem mais difícil encontrar o bug, pois a linha que tenta adicionar um item ao final do conjunto não está perto o bastante da declaração do tipo para que o problema fique evidente.
3. Considere usar var quando a inicialização já fornecer informações suficientes a quem lê.
Variáveis locais são frequentemente inicializadas com construtores. Muitas vezes, o nome da classe instanciada se repete como tipo explícito à esquerda. Se o nome do tipo for longo, var deixa o código mais conciso sem perda de informação:
Também é razoável usar var quando a inicialização é uma chamada de método, como Files.newBufferedReader(…) ou List stringList = List.of("a", "b", "c").
4. Use var para dividir expressões encadeadas ou aninhadas em variáveis locais.
Considere um código que recebe uma coleção de strings e encontra a que aparece com mais frequência. Ele poderia ser assim:
Esse código está correto, mas fica mais legível se for dividido em várias instruções. O problema de dividir o código em instruções como mostrado é:
Mas provavelmente a pessoa que escreveu o código evitou fazer isso porque a tipagem explícita fica extremamente confusa e desvia a atenção do que importa. Com var, podemos expressar o código de forma mais natural sem pagar o alto preço de declarar explicitamente os tipos das variáveis intermediárias:
É perfeitamente válido preferir o primeiro trecho, com sua longa cadeia de chamadas de métodos. No entanto, em alguns casos, é melhor dividir cadeias longas de métodos.
5. Não se preocupe tanto em “programar para a interface” ao usar variáveis locais.
Um padrão comum em Java é criar uma instância de um tipo concreto, mas atribuí-la a uma variável de tipo interface. Por exemplo:
Porém, se var for usado, o tipo concreto será inferido em vez da interface:
O código que usa a variável list pode passar a depender da implementação concreta. Se a inicialização da variável mudar no futuro, o tipo inferido também pode mudar, causando erros ou bugs no código subsequente que usa a variável.
Isso é menos problemático quando a orientação 2 é seguida: se o escopo da variável local for pequeno, os riscos de a implementação concreta “vazar” e afetar o código subsequente serão limitados.
6. Tenha cuidado ao usar var com o operador diamante ou métodos genéricos.
Tanto var quanto o recurso “diamante” permitem omitir informações explícitas de tipo quando elas podem ser deduzidas de outras informações disponíveis. Porém, usados em conjunto, podem acabar omitindo todas as informações úteis de que o compilador precisa para inferir corretamente o tipo desejado.
Considere o seguinte:
Os métodos genéricos também usam inferência de tipos com tanto sucesso que é raro quem programa fornecer argumentos de tipo explícitos. A inferência em métodos genéricos depende do tipo de destino quando não há argumentos reais do método que forneçam informações suficientes sobre o tipo. Em uma declaração com var, não há tipo de destino, então pode ocorrer um problema semelhante ao do operador diamante. Por exemplo:
Tanto com o operador diamante quanto com métodos genéricos, os argumentos reais do construtor ou método podem fornecer informações adicionais sobre o tipo, permitindo inferir o tipo pretendido. Isso acrescenta um nível a mais de indireção, mas o resultado continua previsível. Assim:
7. Tenha cuidado ao usar var com literais.
É improvável que usar var com literais traga grandes vantagens, pois os nomes dos tipos costumam ser curtos. Ainda assim, var pode ser útil em alguns casos, por exemplo, para alinhar nomes de variáveis.
Não há problema em usar literais booleanos, de caracteres, longos e de strings. O tipo inferido desses literais é preciso, então o significado de var é inequívoco. É preciso ter cuidado especial quando a inicialização usa um valor numérico, principalmente um literal inteiro. Com um tipo explícito à esquerda, o valor numérico pode ser ampliado ou reduzido silenciosamente para tipos diferentes de int. Com var, o valor será inferido como int, o que pode não ser o desejado.
Conclusão
Usar var nas declarações pode melhorar o código ao reduzir a poluição visual e destacar informações mais importantes. Por outro lado, usar var indiscriminadamente pode piorar as coisas. Quando usado corretamente, var ajuda a aprimorar um bom código, deixando-o mais curto e claro sem prejudicar a compreensão. Ao usar var, pergunte a si mesmo: o código ficou mais ambíguo ou está claramente compreensível sem exigir muita investigação?
Comece a jogar Capture the Flag
Aprenda a resolver desafios de Capture the Flag assistindo à gravação sob demanda do nosso workshop virtual introdutório.

