A Inferência de Tipo de Variável Local Java (LVTI) ou, brevemente, o tipo de var (o identificador de var não é uma palavra-chave, mas um nome de tipo reservado) foi adicionada ao Java 10 usando o JEP 286: Inferência de Tipo de Variável Local . Sendo uma função de compilador 100%, não afeta o bytecode, o tempo de execução ou o desempenho. Basicamente, o compilador verifica o lado direito do operador de atribuição e, com base nele, determina o tipo específico da variável e a substitui por var .
Além disso, é útil para reduzir a verbosidade do código padrão e também acelera o próprio processo de programação. Por exemplo, é muito conveniente escrever var evenAndOdd =...
vez de Map<Boolean, List<Integer>> evenAndOdd =...
A aparência de var não significa que seja sempre e conveniente usá-lo em qualquer lugar; às vezes será mais prático fazer com ferramentas padrão.
Neste artigo, examinaremos 26 situações, com exemplos de quando você pode usar var e quando não vale a pena.
Ponto 1: tente atribuir nomes significativos às variáveis locais
Normalmente, nos concentramos em fornecer os nomes corretos para os campos das classes, mas não prestamos a mesma atenção aos nomes das variáveis locais. Quando nossos métodos são perfeitamente implementados, contêm pouco código e têm bons nomes, muitas vezes não prestamos atenção às variáveis locais, nem reduzimos completamente seus nomes.
Quando usamos var em vez de escrever tipos explícitos, o compilador os detecta automaticamente e substitui var . Mas, por outro lado, como resultado disso, fica mais difícil para as pessoas lerem e entenderem o código, pois o uso de var pode complicar sua legibilidade e compreensão. Na maioria dos casos, isso ocorre porque tendemos a considerar o tipo de uma variável como informação primária e o nome como secundário. Embora deva ser exatamente o oposto.
Exemplo 1:
Muitos provavelmente concordam que no exemplo abaixo, os nomes das variáveis locais são muito curtos:
Ao usar nomes abreviados, junto com var , o código fica ainda menos claro:
Opção mais preferida:
Exemplo 2:
Evite nomear variáveis como esta:
Use nomes mais significativos:
Exemplo 3:
Em um esforço para atribuir nomes mais compreensíveis às variáveis locais, não vá ao extremo:
Em vez disso, você pode usar uma opção mais curta, mas não menos compreensível:
Você sabia que o Java tem uma classe interna chamada:
InternalFrameInternalFrameTitlePaneInternalFrameTitlePaneMaximizeButtonWindowNotFocusedState
Bem, nomear variáveis com esse tipo pode ser complicado :)
Ponto 2: use literais para ajudar var a identificar o tipo de primitivo (int, long, float, double)
Sem o uso de literais para tipos primitivos, podemos achar que os tipos esperado e implícito podem diferir. Isso é causado pela conversão implícita de tipo usada pelas variáveis var .
Por exemplo, os dois fragmentos de código a seguir se comportam conforme o esperado. Aqui declaramos explicitamente os tipos booleano e char :
boolean flag = true;
Agora usamos var , em vez de declarar explicitamente os tipos:
var flag = true;
Até agora tudo bem. Agora faça o mesmo para os tipos int , long , float e double :
int intNumber = 20;
Embora o snippet de código acima seja simples e direto, agora vamos usar var em vez de especificar explicitamente os tipos.
Evite:
Todas as quatro variáveis serão exibidas como int . Para corrigir esse comportamento, precisamos usar literais Java:
Mas o que acontece se declararmos um número decimal?
Evite isso se você espera obter uma variável do tipo float :
Para evitar surpresa, use o literal apropriado:
Ponto 3: em alguns casos, conversões de tipo var e implícitas podem simplificar o suporte ao código
Por exemplo, vamos supor que nosso código esteja entre dois métodos. Um método obtém um carrinho de compras com produtos diferentes e calcula o melhor preço. Para fazer isso, ele compara vários preços no mercado e retorna o preço total na forma de um tipo flutuante . Outro método simplesmente deduz esse preço do cartão.
Primeiro, vejamos um método que calcula o melhor preço:
public float computeBestPrice(String[] items) { ... float price = ...; return price; }
Em segundo lugar, vamos dar uma olhada no método que funciona com o mapa:
public boolean debitCard(float amount, ...) { ... }
Agora, colocamos nosso código entre esses dois métodos de serviço externos como cliente. Nossos usuários podem escolher as mercadorias a serem compradas e calculamos o melhor preço para elas e, em seguida, baixamos os fundos do cartão:
Após algum tempo, a empresa proprietária da API decide abandonar a representação material dos preços em favor do decimal (em vez de float , int agora é usado). Então, eles modificaram o código da API da seguinte maneira:
public int computeBestPrice(String[] items) { ... float realprice = ...; ... int price = (int) realprice; return price; } public boolean debitCard(int amount, ...) { ... }
O fato é que nosso código usa uma declaração explícita de uma variável float como preço. Em sua forma atual, receberemos um erro no momento da compilação. Mas se tivéssemos previsto tal situação e usado var em vez de float , nosso código continuaria funcionando sem problemas, graças à conversão implícita de tipo:
Ponto 4: quando literais não são uma solução adequada, use conversão explícita ou descartar var
Alguns tipos primitivos em Java não possuem literais especiais, por exemplo, bytes e tipos curtos . Nesse caso, usando a designação explícita de tipo, podemos criar variáveis sem problemas.
Use isso em vez de var :
Mas por que nessa situação dar preferência à notação de tipo explícita em vez de apenas usar var ? Bem, vamos escrever esse código usando var . Observe que nos dois casos, o compilador assumirá que você precisa de variáveis do tipo int .
Evite este erro:
Não há literais aqui que viriam em nosso auxílio, portanto somos forçados a usar a conversão explícita de tipo descendente. Pessoalmente, evitarei essas situações, porque não vejo vantagens aqui.
Use esta entrada apenas se você realmente quiser usar var :
A vantagem de usar var é escrever um código mais conciso. Por exemplo, no caso de usar construtores, podemos evitar a necessidade de repetir o nome da classe e, portanto, eliminar a redundância de código.
Evite o seguinte:
Use em vez disso:
Para a construção abaixo, var também será uma boa maneira de simplificar o código sem perder informações.
Evite:
Use o seguinte código:
Então, por que estamos mais à vontade trabalhando com var nos exemplos apresentados? Porque toda a informação necessária está contida nos nomes das variáveis. Mas se var , em combinação com um nome de variável, reduz a clareza do código, é melhor recusar usá-lo.
Evite:
Use:
Considere, por exemplo, o uso da classe java.nio.channels.Selector
. Esta classe possui um método estático open()
que retorna um novo Seletor e o abre. Mas aqui você pode pensar facilmente que o método Selector.open()
pode retornar um tipo booleano , dependendo do sucesso de abrir um seletor existente ou até mesmo retornar nulo . Usar var aqui levará à perda de informações e confusão no código.
Ponto 6: o tipo var garante segurança em tempo de compilação
Isso significa que não podemos compilar um aplicativo que tente executar atribuições incorretas. Por exemplo, o código abaixo não compila:
Mas este compila:
var items = 10; items = 20;
E esse código compila com sucesso:
var items = "10"; items = "10 items";
Depois que o compilador definiu o valor da variável var , não podemos atribuir nada além desse tipo.
Ponto 7: var não pode ser usado para instanciar um tipo específico e atribuí-lo a uma variável do tipo de interface
Em Java, usamos a abordagem "programação com interfaces". Por exemplo, criamos uma instância da classe ArrayList, associando-a a uma abstração (interface):
List<String> products = new ArrayList<>();
E evitamos coisas como vincular um objeto a uma variável do mesmo tipo:
ArrayList<String> products = new ArrayList<>();
Essa é a prática mais comum e desejável, pois podemos substituir facilmente a implementação da interface por qualquer outra. Para isso, é necessário apenas declarar uma variável do tipo de interface.
Não seremos capazes de seguir esse conceito usando variáveis var, pois um tipo específico é sempre exibido para eles. Por exemplo, no seguinte fragmento de código, o compilador determinará o tipo da variável como ArrayList<String>
:
var productList = new ArrayList<String>();
Existem vários argumentos de defesa que explicam esse comportamento:
var é usado para variáveis locais, onde, na maioria dos casos, a programação usando interfaces é usada menos do que nos casos com parâmetros de método retornados por valores ou campos
O escopo das variáveis locais deve ser pequeno, portanto, resolver problemas causados pela mudança para outra implementação não deve ser muito difícil.
var trata o código à direita como o inicializador usado para determinar o tipo real. Se, em algum momento, o inicializador for alterado, o tipo que está sendo definido também poderá ser alterado, causando problemas no código que depende dessa variável.
Parágrafo 8: probabilidade de conclusão de um tipo inesperado
O uso de var em combinação com um operador de diamante (<>) na ausência de informações para identificar o tipo pode levar a resultados inesperados.
Antes do Java 7, a inferência explícita de tipo era usada para coleções:
A partir do Java 7, o operador diamante foi introduzido. Nesse caso, o compilador derivará independentemente o tipo necessário:
Que tipo será exibido no código abaixo?
Você deve evitar essas construções:
O tipo será definido como ArrayList<Object>
. Isso ocorre porque as informações necessárias para determinar corretamente o tipo não são fornecidas. Isso leva ao fato de que o tipo mais próximo será selecionado, o que pode ser compatível com o contexto do que está acontecendo. Nesse caso, Object
.
Assim, var só pode ser usado se fornecermos as informações necessárias para determinar o tipo esperado. O tipo pode ser especificado diretamente ou passado como argumento.
Especifique diretamente o tipo:
Passe argumentos do tipo necessário:
var productStack = new ArrayDeque<String>(); var productList = new ArrayList<>(productStack);
Product p1 = new Product(); Product p2 = new Product(); var listOfProduct = List.of(p1, p2);
Item 9: atribuir uma matriz a uma variável var não requer colchetes []
Todos nós sabemos como declarar matrizes em Java:
int[] numbers = new int[5];
Que tal usar var ao trabalhar com matrizes? Nesse caso, não há necessidade de usar colchetes no lado esquerdo.
Evite o seguinte (isso nem compila):
Use:
O código abaixo usando var também falha na compilação. Isso ocorre porque o compilador não pode determinar o tipo do lado direito:
Item 10: var não pode ser usado ao declarar várias variáveis na mesma linha
Se você deseja declarar variáveis do mesmo tipo de uma só vez, precisa saber que var não é adequado para isso. O código a seguir não compila:
Use em vez disso:
Ou é:
Ponto 11: variáveis locais devem se esforçar para minimizar seu escopo. O tipo var reforça essa afirmação.
Mantenha um pequeno escopo para variáveis locais - tenho certeza de que você ouviu essa declaração antes de var .
A legibilidade e as correções rápidas de bugs são argumentos a favor dessa abordagem. Por exemplo, vamos definir uma pilha da seguinte maneira:
Evite isso:
Observe que estamos chamando o método forEach()
, herdado de java.util.Vector
. Este método percorrerá a pilha como qualquer outro vetor e é disso que precisamos. Mas agora decidimos usar ArrayDeque
vez de Stack
. Quando fazemos isso, o método forEach()
receberá uma implementação de ArrayDeque que percorrerá a pilha como uma pilha padrão (LIFO)
Não é isso que queremos. É muito difícil rastrear o erro aqui, porque o código que contém a parte forEach()
não está localizado próximo ao código no qual as alterações foram feitas. Para aumentar a velocidade de busca e correção de erros, é muito melhor escrever código usando a variável de stack
, o mais próximo possível da declaração dessa variável.
É melhor fazer o seguinte:
Agora, quando o desenvolvedor alternar de Stack
para ArrayQueue
, ele poderá perceber rapidamente o erro e corrigi-lo.
Cláusula 12: o tipo var simplifica o uso de vários tipos em operadores ternários
Podemos usar diferentes tipos de operandos no lado direito do operador ternário.
Ao especificar explicitamente tipos, o código a seguir não é compilado:
No entanto, podemos fazer isso:
Collection code = containsDuplicates ? List.of(12, 1, 12) : Set.of(12, 1, 10); Object code = containsDuplicates ? List.of(12, 1, 12) : Set.of(12, 1, 10);
O código abaixo também não compila:
Mas você pode usar tipos mais gerais:
Serializable code = intOrString ? 12112 : "12112"; Object code = intOrString ? 12112 : "12112";
Em todos esses casos, é melhor preferir var :
A partir desses exemplos, não se segue que o tipo var define os tipos de objeto em tempo de execução. Isto não é assim!
E, é claro, o tipo var funcionará corretamente com os mesmos tipos de ambos os operandos:
Ponto 13: o tipo var pode ser usado dentro de loops
Podemos facilmente substituir a declaração explícita de types in for loops pelo tipo var .
Alterando um tipo int explícito para var :
Alterando o tipo explícito de Order
para var :
List<Order> orderList = ...;
Ponto 14: var funciona bem com fluxos em Java 8
É muito simples usar var do Java 10 com fluxos que apareceram no Java 8.
Você pode simplesmente substituir a declaração explícita do tipo Stream por var :
Exemplo 1:
Exemplo 2:
Cláusula 15: var pode ser usada ao declarar variáveis locais destinadas a quebrar grandes cadeias de expressões em partes
Expressões com muitos aninhamentos parecem impressionantes e geralmente parecem algum tipo de código inteligente e importante. No caso em que é necessário facilitar a legibilidade do código, é recomendável dividir uma expressão grande usando variáveis locais. Mas, às vezes, escrever muitas variáveis locais parece um trabalho muito exaustivo que eu gostaria de evitar.
Um exemplo de uma expressão grande:
List<Integer> intList = List.of(1, 1, 2, 3, 4, 4, 6, 2, 1, 5, 4, 5);
Melhor dividir o código em seus componentes:
List<Integer> intList = List.of(1, 1, 2, 3, 4, 4, 6, 2, 1, 5, 4, 5);
A segunda versão do código parece mais legível e mais simples, mas a primeira versão também tem o direito de existir. É absolutamente normal que nossa mente se adapte ao entendimento de expressões tão grandes e as prefira a variáveis locais. No entanto, o uso do tipo var pode ajudar a dividir grandes estruturas, reduzindo o esforço para declarar variáveis locais:
var intList = List.of(1, 1, 2, 3, 4, 4, 6, 2, 1, 5, 4, 5);
Cláusula 16: var não pode ser usada como um tipo de retorno ou como um tipo de argumento de método
Os dois trechos de código mostrados abaixo não serão compilados.
Usando var como o tipo de retorno:
Usando var como um tipo de argumento de método:
Cláusula 17: variáveis locais do tipo var podem ser passadas como parâmetros do método ou podem assumir o valor retornado pelo método
Os seguintes fragmentos de código serão compilados e funcionarão corretamente:
public int countItems(Order order, long timestamp) { ... } public boolean checkOrder() { var order = ...;
:
public <A, B> B contains(A container, B tocontain) { ... } var order = ...;
18: var
:
public interface Weighter { int getWeight(Product product); }
var :
public interface Weighter { int getWeight(Product product); }
19: var effectively final
, :
… Java SE 8, , final effectively final. , , effectively final .
, var effectively final. .
:
public interface Weighter { int getWeight(Product product); }
:
public interface Weighter { int getWeight(Product product); }
20: var- final-
var ( , effectively final). , final .
:
:
21:
var , . , var , :
:
Java 11 var - . Java 11:
22: var null'
var - .
( null ):
( ):
:
23: var
var , .
:
:
24: var catch
, try-with-resources
catch
, , .
:
:
Try-with-resources
, var try-with-resources .
, :
var :
25: var
, :
public <T extends Number> T add(T t) { T temp = t; ... return temp; }
, var , T var :
public <T extends Number> T add(T t) { var temp = t; ... return temp; }
, var :
codepublic <T extends Number> T add(T t) { List<T> numbers = new ArrayList<>(); numbers.add((T) Integer.valueOf(3)); numbers.add((T) Double.valueOf(3.9)); numbers.add(t); numbers.add("5");
List<T> var :
public <T extends Number> T add(T t) { var numbers = new ArrayList<T>();
26: var Wildcards (?),
? Wildcards
var :
Foo<?> var , , var .
, , , , . , ArrayList , Collection<?> :
(Foo <? extends T>) (Foo <? super T>)
, :
, , :
var :
, . – :
Conclusão
« var », Java 10. , . , var , .
var Java!