1. Transformação de elementos de coleções
Em programação, uma das operações mais frequentes — transformação de coleção: temos uma coleção de dados de um tipo e, com base nela, precisamos criar uma nova coleção de outro tipo. Por exemplo, a partir de uma lista de objetos do tipo User obter a lista de seus nomes (String), ou a partir de uma lista de números — a lista de seus quadrados.
A maneira mais fundamental e compreensível em Java é usar a abordagem imperativa, isto é, o laço for comum (muitas vezes — for-each).
Exemplo: de uma lista de strings obter a lista de seus comprimentos
Suponha que temos uma lista de nomes de cidades:
List<String> cities = List.of("Londres", "Paris", "Tóquio", "Nova York");
Nossa tarefa é criar uma nova lista que contenha o comprimento de cada nome. O resultado final deve ser: [7, 5, 6, 9].
Solução com o laço for:
import java.util.ArrayList;
import java.util.List;
public class CollectionTransform {
public static void main(String[] args) {
List<String> cities = List.of("Londres", "Paris", "Tóquio", "Nova York");
List<Integer> lengths = new ArrayList<>(); // Criamos uma nova lista vazia para o resultado
for (String city : cities) {
// Para cada elemento de cities calculamos seu comprimento...
int length = city.length();
// ...e adicionamos esse resultado à nova lista
lengths.add(length);
}
System.out.println(lengths); // Irá imprimir: [7, 5, 6, 9]
}
}
Começamos criando uma nova coleção vazia para os resultados — a transformação não altera a coleção original. Para percorrer a coleção original, usamos um laço for-each e, dentro dele, aplicamos a lógica necessária (length()) a cada elemento, e adicionamos o resultado à nova lista por meio de add.
2. Transformação de objetos
Frequentemente trabalhamos com tipos de dados mais complexos. Suponha que temos a classe Product e precisamos obter a lista de seus nomes (ou preços).
public class Product {
private String name;
private double price;
public Product(String name, double price) {
this.name = name;
this.price = price;
}
public String getName() {
return name;
}
public double getPrice() {
return price;
}
}
Agora, tendo uma lista de produtos, vamos obter a lista de seus nomes:
import java.util.ArrayList;
import java.util.List;
public class ProductExample {
public static void main(String[] args) {
List<Product> products = List.of(
new Product("Notebook", 1200.0),
new Product("Mouse", 25.5),
new Product("Teclado", 75.0)
);
// Criamos uma nova lista para os nomes
List<String> productNames = new ArrayList<>();
for (Product product : products) {
// Para cada objeto Product obtemos seu nome
productNames.add(product.getName());
}
System.out.println(productNames); // Irá imprimir: [Notebook, Mouse, Teclado]
}
}
A lógica é a mesma: iteramos pela coleção original, aplicamos a cada elemento o método necessário (por exemplo, getName()) e adicionamos o resultado à nova coleção.
3. Trabalhando com coleções aninhadas
Considere o caso de uma lista de listas: vários departamentos, em cada um — sua própria lista de funcionários. A tarefa é obter uma única lista (“plana”) de todos os funcionários.
Exemplo: união de listas de funcionários
List<List<String>> departments = List.of(
List.of("Anna", "Boris"),
List.of("Viktoriya", "Gleb", "Dmitriy"),
List.of("Elena")
);
Queremos obter uma lista única: [Anna, Boris, Viktoriya, Gleb, Dmitriy, Elena].
Modo 1: usando o método addAll()
import java.util.ArrayList;
import java.util.List;
public class NestedCollectionExample {
public static void main(String[] args) {
List<List<String>> departments = List.of(
List.of("Anna", "Boris"),
List.of("Viktoriya", "Gleb", "Dmitriy"),
List.of("Elena")
);
List<String> allEmployees = new ArrayList<>();
// Iteramos por cada lista (departamento)
for (List<String> department : departments) {
// Adicionamos todos os elementos da lista atual à lista geral
allEmployees.addAll(department);
}
System.out.println(allEmployees); // [Anna, Boris, Viktoriya, Gleb, Dmitriy, Elena]
}
}
Modo 2: laço aninhado — o mesmo, mas “na mão”:
List<List<String>> departments = List.of(...);
List<String> allEmployees = new ArrayList<>();
for (List<String> department : departments) { // Laço externo por departamentos
for (String employee : department) { // Laço interno por funcionários
allEmployees.add(employee);
}
}
System.out.println(allEmployees);
Ambos os modos são equivalentes no resultado. O método addAll() basicamente encapsula a lógica do laço aninhado e torna o código mais curto.
4. Casos complexos e transformações condicionais
Às vezes a transformação precisa ser feita apenas para elementos que satisfazem uma condição. Por exemplo, obter a lista de nomes de cidades que começam com a letra "N" e pegar seus comprimentos. Aqui combinamos filtragem e transformação: if + chamada de método (length()).
import java.util.ArrayList;
import java.util.List;
public class ConditionalTransform {
public static void main(String[] args) {
List<String> cities = List.of("Londres", "Paris", "Tóquio", "Nova York", "Nurembergue");
List<Integer> lengths = new ArrayList<>();
for (String city : cities) {
// Primeiro verificamos a condição
if (city.startsWith("N")) {
// Se a condição for satisfeita, aplicamos a transformação
lengths.add(city.length());
}
}
System.out.println(lengths); // Irá imprimir: [9, 11]
}
}
O padrão é: dentro do laço, primeiro filtramos o elemento por uma condição (startsWith, comparação, verificação de faixa etc.), depois aplicamos a transformação necessária e colocamos o resultado na nova lista.
5. Erros comuns e armadilhas
Erro nº 1: modificar a coleção original durante a iteração. Um erro comum é tentar adicionar/remover elementos da coleção de origem diretamente no laço for-each. Isso leva a erros e comportamento imprevisível. Solução: sempre crie uma nova lista para os resultados e a preencha.
Erro nº 2: conversão de tipos incorreta. Se você trabalha com coleções “cruas” (por exemplo, via Object) e faz cast dos elementos para um tipo incorreto, terá ClassCastException. Use generics (List<T>) e fique atento às assinaturas.
Erro nº 3: uso ineficiente de recursos. Para coleções muito grandes, a criação constante de novas listas e a cópia de elementos pode impactar memória e tempo. Na maioria das tarefas do dia a dia isso é aceitável, mas ao processar grandes volumes de dados, considere a complexidade e, se necessário, otimize a abordagem.
GO TO FULL VERSION