0

13 dicas e truques da linguagem Java

#Java
Gustavo Lima
Gustavo Lima

Junta uma dica daqui, outra dali, e voilá, seguem 13 dicas para aumentar sua produtividade enquanto programa usando a linguagem Java. Nada milagroso, mas apenas práticas muitas vezes pouco conhecidas para aumento de produtividade, redução de código e por aí vai. Muitas só funcionam a partir do Java 7 ou 8, mas acredito que nesta altura do campeonato ninguém deveria estar programando em 6 ou frameworks inferiores…

Neste artigo você vai ver:

  1. Operador ternário
  2. ++x, x++, x+= y, etc
  3. Inicializadores de arrays
  4. A diretiva ‘try-with’
  5. Javadocs!
  6. Inicialização double-braces
  7. Instance initializers e static initializers
  8. Diamond notation
  9. Expressões Lambda
  10. Tipo de retorno co-variante
  11. Argumentos variáveis
  12. Use os snippets de código
  13. Generics


Vamos lá!


1. Operador Ternário ‘?’

Use-o com moderação ou ele prejudicará a legibilidade de seu código. Basicamente o operador ternário substitui um bloco if tradicional em uma única linha de código. Novidade? NOT! Operadores ternários existem na linguagem C desde a década de 70. Na prática funciona assim, imagine o seguinte bloco de código:

int x = 10;

int y = 20;

int max;

 

if(x > y)

  max = x;

else

  max = y;

Você pode substituí-lo usando o operador ternário que funciona da seguinte forma (Questão) ? (Ação Positiva) : (Ação Negativa). Vamos ver o exemplo anterior usando operador ternário:

int x = 10;

int y = 20;

int max = (x > y) ? x : y;

2. ++x, x++, x += y, etc

Quantas vezes você tem de incrementar variáveis? Isso vale também, na maioria dos casos para outros operadores também, que acabam consumindo bastante o nosso tempo programando seus cálculos. Muitas vezes, códigos de mais de uma linha podem facilmente ser reduzidos a poucas instruções em uma única.

Por exemplo, você quer guardar um valor em uma posição de um array e imediatamente incrementar o índice de posições. Um jeito de fazer seria:

int indice = 0;

array[indice] = valor;

indice = indice + 1;

Mas este não é o único, nem o melhor jeito de fazer. Primeiro, se você quer acessar a posição do array primeiro e depois incrementá-la, pode fazê-lo da seguinte forma:


int indice = 0;

array[indice++] = valor;


Isso por que o incremento unário à direita o faz depois do valor original de indice ter sido lido. O mesmo vale para indice– que decrementaria o valor de índice após sua leitura. Agora, se você quer que ele seja incrementado ANTES da leitura de seu valor:


int indice = 0;

array[++indice] = valor;


Assim, o indice já começará em 1 ao invés de 0, o mesmo é válido para –indice, que decrementaria antes de ler o valor.

Mas e se eu quiser aplicar um valor superior a 1, nesse caso os operadores de incremento e decremento unário não me ajudam, certo?

Certo. Para isso temos os auto-operadores, que são aqueles que aplicam uma operação aritmética sobre a própria variável que quer o resultado da operação. Vamos voltar ao primeiro exemplo de código que tivemos neste tópico, apenas mudando o incremento:


int indice = 0;

array[indice] = valor;

indice = indice + 2;


Usando os auto-operadores, podemos reescrever este trecho como:


int indice = 0;

array[indice] = valor;

indice += 2;


Sendo assim, a variável indice receberá ela mesma acrescida do valor 2. O mesmo vale para -=, *= e /=.

3. Inicializadores de arrays

Cansado de perder várias linhas de código para ficar setando valores em arrays? A linguagem Java te permite várias formas de construir seu array, a saber:


int[] array = null;

int[] array = new int[3];

int array[] = new int[3];

int[] array = new int[]{ 1, 2, 3 };

int[] array = { 1, 2, 3 };


No primeiro exemplo inicializamos ele com null, para que seja inicializado de fato mais tarde.

Na segunda e terceira linhas, temos inicializações comuns, apenas mudando um pouco a sintaxe, mas ambas possuem efeito idêntico.

Nas duas últimas linhas, temos inicializações já preenchidas com seus respectivos valores iniciais. Note que neste caso eu não precisei dizer quantas posições o meu array irá possuir, ele criará automaticamente com quantas posições forem necessárias para guardar os elementos à direita. Obviamente, eles serão inseridos no array na ordem em que foram descritos.

O último exemplo, em específico, é muito útil e produtivo quando já temos/sabemos os elementos iniciais que nosso array terá.

4. A diretiva ‘try-with’

Uma vez que você precisa alocar um objeto em memória um pouco mais pesado que o normal, como streams e arquivos de mídia, é importante que seja feito um controle mais rigoroso na hora de liberar a memória novamente, evitando problemas futuros. Ou seja, sempre temos de fazer três etapas: criamos o objeto, usamos ele e o descartamos. Isso pode ser ilustrado com o trecho de código abaixo:


BufferedReader br = new BufferedReader(new FileReader(path));

try {

    return br.readLine();

} finally {

    if (br != null) br.close();

}

A diretiva try, com uso try-with, permite comprimir o código anterior em:


try (BufferedReader br = new BufferedReader(new FileReader(path))) {

    return br.readLine();

}

A diretiva try-with somente pode ser utilizada em objetos que implementem a interface java.lang.AutoCloseable.

5. Javadocs!

É comum em projetos Java termos muitas classes e consequentemente muitos métodos, muitos atributos, etc. A existência de cada um destes muitos elementos torna a compreensão de cada um deles complicada com o passar do tempo, principalmente em equipes com vários programadores, sendo que velhos saem e novos entram, mas mesmo quando você programa sozinho, mas não está o tempo todo envolvido com alguns dos projetos que ocasionalmente precisam de manutenção.

Ou seja, depois de um ano ou dois, se você precisar escrever algumas linhas de código ou usar alguma classe complexa que você criou há muito tempo atrás, é uma boa ter comentários nela, certo? Mais do que isso, é bom ter javadocs nela!

Você adiciona Javadocs facilmente em ferramentas como NetBeans usando o snippet (veja mais adiante) /** + ENTER, mas de qualquer forma, você pode fazê-lo manualmente. Por exemplo o Javadoc de um método como esse:


public static Usuario autenticar(String nome, String senha){

    for(Usuario usuario : retornarTodos()){

        if(usuario.isValido(nome, senha))

            return usuario;

        }

    return null;

}

Poderia ser esse (coloque isso logo acima da assinatura do método em questão):


/**

* Método que verificar se é possível autenticar no sistema com as credenciais informadas.

* @param nome O nome de usuário que deseja se autenticar.

* @param senha A senha do usuário em questão.

* @return Retorna um objeto Usuario já autenticado, ou null caso não seja possível.

*/

Mas qual a diferença de um Javadoc para um comentário comum? Esse:

Essa janelinha aí aparece sempre que for chamar este método em uma IDE como o NetBeans, lhe dizendo exatamente o que o método faz, os parâmetros, o retorno, etc. E o mesmo pode ser feito para suas classes atributos, etc, lhe ajudando muito com sua produtividade futura!

6. Inicialização double-braces

Sabe a dica 3 sobre inicialização de arrays? Pois é, tenho uma dica parecida para a inicialização de Java Collections: a double-braces initialization. Com ela, você pode instanciar sua Java Collection favorita (como HashSet por exemplo) e já adicionar os primeiros valores nela, da seguinte forma:


removeProductsWithCodeIn(new HashSet<String>() {{

add("XZ13s");

add("AB21/X");

add("YYLEX");

add("AR5E");

}});

Legal, não?!

7. Instance initializers e Static initializers

Todo mundo conhece os construtores da linguagem Java, certo? Mas e quando temos um algoritmo que deve ser executado em mais de um construtor da mesma classe, como fazemos?

Uma possibilidade é criarmos um método que será chamado dentro dos construtores. Mas apesar de ser uma prática comum, não estaríamos aí repetindo a linha de código de chamada do método, ferindo o princípio DRY? Que tal usar um instance initializer?


public class App {

    public App(String name) { System.out.println(name + "'s constructor called"); }

 

    { System.out.println("instance initializer called"); }

 

    public static void main( String[] args ) {

        new App("one");

        new App("two");

  }

}

Rode esse programa Java acima e você verá que o código “solto” entre chaves é chamado antes do construtor de App passando “one” e novamente antes do construtor de App passando “two’. Sendo assim, é uma inicialização de instância bem útil quando queremos que um algoritmo aconteça em todas as chamadas ao construtor de uma classe.

Mas e se queremos que um código aconteça tão logo uma classe passe a existir no sistema, antes mesmo de seus objetos surgirem? Nesse caso, usamos static initializers, como abaixo:


public class App {

    public App(String name) { System.out.println(name + "'s constructor called"); }

 

    static { System.out.println("static initializer called"); }

 

    { System.out.println("instance initializer called"); }

 

    public static void main( String[] args ) {

        new App("one");

  }

}

Nesse caso, basta criar um bloco de inicialização dentro da classe usando a palavra reservada static antes dele. Se você rodar essa aplicação, verá que o bloco static executa antes mesmo da instanciação de App. Mesmo que você nem mesmo instancie App na sua aplicação irá rodar aquele bloco static.

Super interessante!

8. Diamond Notation

Voltando ao assunto de collections, nas versões mais recentes do Java, visando torná-lo um pouco menos verboso quando precisamos ficar instanciando Java Collections foi criada a diamond notation.


List<String> lista = new ArrayList<>();


O princípio aqui é o seguinte: se o tipo da direita da chamada será o mesmo da esquerda da variável, então não é mais necessário repeti-lo, evitando essa tediosa reescrita que tínhamos antigamente. Imagino a mente do engenheiro criativo que chamou aquele losango formado pelos <> de diamante…

9. Expressões Lambda

Expressões Lambda são expressões de consulta feitas sobre coleções de dados de forma extremamente otimizada e simples, criando um poderosa ferramenta de produtividade. Por exemplo, se tivéssemos uma lista de livros e quiséssemos saber todos os livros que começam com a letra ‘a’?

Tradicionalmente teríamos de fazer um laço para percorrer todos os elementos da lista comparando o título de cada livro e adicionando-os em uma outra fila à medida em que forem encontrados os livros que começam com ‘a’. Com Lambda, tal tarefa se restringe somente a:


livros.stream().filter((Livro l) -> l.getTitulo().startsWith("a"))


Note que este é apenas um dos milhares de uso possíveis de expressões Lambda em Java. Esse poderoso recurso funcional estava demorando pra chegar na linguagem e, se bem compreendido e aplicado, pode mudar drasticamente a sua produtividade enquanto programador Java. Usando uma IDE poderosa como o NetBeans lhe ajuda bastante nesta tarefa pois ela lhe sugere o uso de lambda expressions em diversos momentos que você não se lembraria de usar, sem contar o autocomplete que vai lhe mostrar diversas funcionalidades da mesma, como forEach, removeIf, allMatch, findFirst, etc.

Em todos eles, você terá de trabalhar com o conceito de predicados, que são aquelas expressões anônimas que eu passei por parâmetro no método filter, onde você começa declarando variáveis e depois usa o sinal ‘->’ para dizer que na sequência vem o algoritmo deste método.

Estude a respeito vale a pena. Quem sabe no futuro eu não lance um post focado nisso aqui no blog?

10. Tipo de retorno co-variante

Essa não é uma feature nova, veio no Java 1.5, mas pouca gente conhece, apesar de ser extremamente útil em cenários onde usamos o princípio LSP ou mesmo Generics (mais adiante):


class Coisa { }

 

class Livro extends Coisa { }

 

class Estoque {

    Collection<Coisa> retornarCoisas() {

        ...

    }

}

 

class Livraria extends Estoque {

    @Override

    Collection<Livro> retornarCoisas() {

        ...

    }

}

Note que Livro estende Coisa, e que na classe original (Estoque) o método retornarCoisas() traz uma coleção de Coisas, algo bem genérico. Mas na classe filha, Livraria (que nada mais é do que um estoque de livros), sobrescrevemos o método retornarCoisas() para que ele retorne uma coleção de Livro. Muito poderoso!

11. Argumentos variáveis

Essa é bem útil quando você tem um método e ele possui vários parâmetros de mesmo tipo, como se fosse um array, mas você não quer o programador tendo de instanciar um array, popular ele, etc pra poder chamar o método. Sendo assim, a chamada abaixo:


void metodo(String[] words){

  for(String s : words)

      System.out.prinln(s);

}

 

String[] array = {"um", "dois", "três"};

metodo(array);

Pode ser substituído por:


void metodo(String... words){

  for(String s : words)

      System.out.prinln(s);

}

 

metodo("um", "dois", "três");

O efeito será o mesmo, mas a chamada ficará muito mais simples sem a necessidade de um array por parâmetro. Note que o uso de argumentos variáveis (as reticências após o tipo do parâmetro) deve ser sempre feito no último parâmetro do método. Note também que se ele for o único parâmetro do método, ele acaba sendo opcional.

12. Use os snippets de código

Snippets são palavras encurtadas que quando usamos no NetBeans (e outras IDEs), seguidas de um TAB, elas se transformam em estruturas de código completas. Experimente digitar os seguintes snippets seguido de TAB na IDE NetBeans:

  • for: monta um laço for padrão;
  • if: monta uma estrutura if padrão;
  • sout: monta um System.out.println();
  • do: cria um bloco do/while;
  • try: cria um bloco try/catch padrão;
  • while: cria um bloco while padrão;
  • psvm: cria um “public void static main…”

13. Generics

Java possui um poderoso recurso de Generics que permite a você associar um tipo de classe à sua classe para que os objetos que ela manipula sejam fortemente tipados ao invés de trabalhar com Object. Um exemplo? ArrayList (lembra do <> do ArrayList? Estamos usando Generics ali), mas podemos usar para criar nossas classes que manipulam outras classes, como Controllers associados a Models no padrão MVC, por exemplo.

Basicamente, quando você cria a sua classe, você diz que ela usará Generics da seguinte forma:


public class Estoque<T> {

    T[] pesquisar(String pesquisa){

        return null;

    }

}

 

//usando

Estoque<Livro> estoque = new Estoque<>();

Livro[] livros = estoque.pesquisar("a");

Sendo assim, a classe Estoque se torna genérica para qualquer classe do meu sistema que eu queira “estocar”, porém, diferente de um estoque de Objects, quando eu inicializo o meu estoque eu tenho de dizer qual tipo de objeto estarei estocando, mantendo a tipagem forte.

O método pesquisar do Estoque<Livro> retornará um array de Livro, sendo que T é apenas uma letra arbitrária que será substituída em tempo de compilação pelo tipo especificado.

Assim como Lambda, Generics é muito mais poderoso que isso, mas já deu pra ter um gostinho, certo?!

E aí, tem alguma dica que eu não cobri aqui no post?

0
2

Comentários (6)

1
S

Sergio Santos

08/10/2021 14:12

Muito boa estas dicas Gustavo. Gostei e já anotei todas aqui, para não esquecer

1
Flavia Carmo

Flavia Carmo

07/10/2021 11:54

Parabéns Gustavo,


Que artigo maravilhoso , muito obrigada por compartilhar. São artigos assim que enriquecem nossa jornada de estudos.


Super bacana,


Esse aqui já vai para minha barra de favoritos.


ABS

1
Gustavo Lima

Gustavo Lima

07/10/2021 10:13

De nada, se puder curte o artigo.

1
Ezau Martins

Ezau Martins

07/10/2021 10:01

show. ter esse tipo de material assim resumido ajuda demais na hora de tentar relembrar algo,é bom principalmente para quem escreveu pois vc já sabe onde procurar.

1
M

Marcelo Mora

07/10/2021 09:31

Ótima dica.

1
Edinaldo Nogueira

Edinaldo Nogueira

07/10/2021 09:27

Muito bom Gustavo!!!

Jovem negro em busca de uma oportunidade no mercado de TI.

Brasil