Introdução: Por que o desempenho do código Java é importante desde os primeiros passos
\nCada desenvolvedor, mesmo iniciante, enfrenta situações em que o programa funciona lentamente. No mundo Java, onde o código é executado na máquina virtual (JVM), a otimização de desempenho não é apenas "aceleração", mas a arte de usar recursos com sabedoria. Compreender os princípios básicos de otimização ajudará você a escrever não apenas código funcional, mas também rápido, que utilizará memória e processador de forma eficiente.
\nNeste artigo, abordaremos os principais aspectos da otimização de aplicações Java para iniciantes: desde a escolha correta de estruturas de dados até a configuração do coletor de lixo. Você aprenderá como evitar "gargalos" típicos e tornar seu código mais performático sem complexidade desnecessária.
\n\n1. Uso eficiente de memória: String Pool e primitivos
\n1.1. Evite criar objetos String desnecessários
\nUm dos erros mais comuns de iniciantes é criar muitas strings em loops. Java usa o String Pool (pool de strings) para otimização, mas isso funciona apenas com a abordagem correta.
\nExemplo ruim:
\nString result = "";\nfor (int i = 0; i < 1000; i++) {\n result += "número " + i + " ";\n}\nSystem.out.println(result);\nNeste caso, a cada iteração um novo objeto String é criado, causando uma enorme carga no coletor de lixo.
Exemplo bom:
\nStringBuilder sb = new StringBuilder();\nfor (int i = 0; i < 1000; i++) {\n sb.append("número ").append(i).append(" ");\n}\nString result = sb.toString();\nSystem.out.println(result);\nUse StringBuilder (ou StringBuffer para multithreading) para concatenação de strings em loops. Isso economiza memória e acelera a execução dezenas de vezes.
1.2. Use primitivos em vez de wrappers
\nEm Java, existem tipos primitivos (int, double, boolean) e seus wrappers de objeto (Integer, Double, Boolean). Wrappers são convenientes, mas criam objetos extras no heap.
// Lento: muitos objetos Integer são criados\nInteger sum = 0;\nfor (int i = 0; i < 100000; i++) {\n sum += i; // autoboxing\n}\n\n// Rápido: usa primitivo\nint sumPrimitive = 0;\nfor (int i = 0; i < 100000; i++) {\n sumPrimitive += i;\n}\nUse primitivos sempre que possível, especialmente em loops e operações matemáticas.
\n\n2. Otimização do trabalho com coleções e loops
\n2.1. Escolha da coleção correta
\nNem todas as coleções são igualmente úteis. Por exemplo, ArrayList é mais rápido para acesso por índice, enquanto LinkedList é melhor para inserção/remoção no meio da lista. Mas para a maioria das tarefas, ArrayList é a escolha ideal.
- \n
- ArrayList — para leitura frequente e acesso por índice. \n
- LinkedList — para inserção/remoção frequente no início ou meio. \n
- HashSet — para verificação rápida de existência de elemento (O(1)). \n
- HashMap — para armazenar pares chave-valor com acesso rápido. \n
Dica: Se o tamanho da coleção for conhecido antecipadamente, defina a capacidade inicial (capacity) no construtor para evitar reconstruções desnecessárias do array.
\n// Ruim: ArrayList será reconstruído várias vezes\nList<String> list = new ArrayList<>();\n\n// Bom: define capacidade inicial\nList<String> list = new ArrayList<>(1000);\n\n2.2. Otimização de loops: mova invariantes para fora
\nSe dentro do loop um valor é calculado que não muda de iteração para iteração, mova-o para fora do loop.
\n// Lento: list.size() é calculado a cada vez\nfor (int i = 0; i < list.size(); i++) {\n