1. 컬렉션 요소 변환
프로그래밍에서 가장 흔한 작업 중 하나는 컬렉션 변환입니다. 한 타입의 데이터로 이루어진 컬렉션을 바탕으로 다른 타입의 새로운 컬렉션을 만들어야 할 때가 많습니다. 예를 들어 User 객체 리스트에서 그들의 이름(String) 리스트를 얻거나, 숫자 리스트에서 각 숫자의 제곱값 리스트를 만드는 식입니다.
Java에서 가장 기본적이고 이해하기 쉬운 방법은 명령형 접근, 즉 일반적인 for 루프(자주 쓰이는 것은 for-each)를 사용하는 것입니다.
예: 문자열 리스트에서 길이 리스트 만들기
도시 이름 리스트가 있다고 가정해 봅시다:
List<String> cities = List.of("London", "Paris", "Tokyo", "New-York");
우리의 목표는 각 이름의 길이를 담는 새로운 리스트를 만드는 것입니다. 최종 결과는 [6, 5, 5, 8]이어야 합니다.
for 루프를 사용하는 해결책:
import java.util.ArrayList;
import java.util.List;
public class CollectionTransform {
public static void main(String[] args) {
List<String> cities = List.of("London", "Paris", "Tokyo", "New-York");
List<Integer> lengths = new ArrayList<>(); // 결과를 담을 새 빈 리스트를 생성
for (String city : cities) {
// 각 요소의 길이를 계산하고...
int length = city.length();
// ...그 결과를 새 리스트에 추가한다
lengths.add(length);
}
System.out.println(lengths); // 출력: [6, 5, 5, 8]
}
}
먼저 결과용 빈 컬렉션을 만듭니다 — 변환은 원본 컬렉션을 변경하지 않습니다. 원본을 순회할 때는 for-each 루프를 사용하고, 내부에서 각 요소에 필요한 로직(length())을 적용한 뒤 결과를 add로 새 리스트에 추가합니다.
2. 객체 변환
더 복잡한 데이터 타입을 다루는 경우가 많습니다. 예를 들어 Product 클래스가 있고, 그 이름(또는 가격) 리스트를 얻어야 할 수 있습니다.
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;
}
}
이제 제품 리스트가 있을 때, 그 이름 리스트를 만들어 봅시다:
import java.util.ArrayList;
import java.util.List;
public class ProductExample {
public static void main(String[] args) {
List<Product> products = List.of(
new Product("Laptop", 1200.0),
new Product("Mouse", 25.5),
new Product("Keyboard", 75.0)
);
// 이름을 담을 새 리스트 생성
List<String> productNames = new ArrayList<>();
for (Product product : products) {
// 각 Product 객체에서 이름을 꺼낸다
productNames.add(product.getName());
}
System.out.println(productNames); // 출력: [Laptop, Mouse, Keyboard]
}
}
로직은 동일합니다. 원본 컬렉션을 순회하면서 각 요소에 필요한 메서드(예: getName())를 적용하고, 결과를 새 컬렉션에 추가합니다.
3. 중첩 컬렉션 다루기
리스트의 리스트처럼 중첩 구조를 다루는 경우를 생각해 봅시다. 예를 들어 여러 부서가 있고, 각 부서마다 직원 리스트가 있을 수 있습니다. 목표는 모든 직원을 하나의 “평탄한” 리스트로 얻는 것입니다.
예: 직원 리스트 합치기
List<List<String>> departments = List.of(
List.of("Anna", "Boris"),
List.of("Victoria", "Gleb", "Dmitry"),
List.of("Elena")
);
하나로 합친 리스트를 얻고자 합니다: [Anna, Boris, Victoria, Gleb, Dmitry, Elena].
방법 1: 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("Victoria", "Gleb", "Dmitry"),
List.of("Elena")
);
List<String> allEmployees = new ArrayList<>();
// 각 리스트(부서)를 순회한다
for (List<String> department : departments) {
// 현재 리스트의 모든 요소를 전체 리스트에 추가한다
allEmployees.addAll(department);
}
System.out.println(allEmployees); // [Anna, Boris, Victoria, Gleb, Dmitry, Elena]
}
}
방법 2: 중첩 루프 — 같은 작업을 “수동으로” 수행:
List<List<String>> departments = List.of(...);
List<String> allEmployees = new ArrayList<>();
for (List<String> department : departments) { // 바깥 루프: 부서 순회
for (String employee : department) { // 안쪽 루프: 직원 순회
allEmployees.add(employee);
}
}
System.out.println(allEmployees);
두 방법 모두 결과는 동일합니다. addAll() 메서드는 사실상 중첩 루프의 로직을 캡슐화하여 코드를 더 짧게 만들어 줍니다.
4. 조건이 있는 변환
어떤 요소가 조건을 만족할 때만 변환해야 하는 경우가 있습니다. 예를 들어, 도시 이름 중에서 "N"으로 시작하는 것들만 골라 그 길이를 구하는 식입니다. 여기서는 필터링과 변환을 결합합니다: if + 메서드 호출(length()).
import java.util.ArrayList;
import java.util.List;
public class ConditionalTransform {
public static void main(String[] args) {
List<String> cities = List.of("London", "Paris", "Tokyo", "New-York", "Nuremberg");
List<Integer> lengths = new ArrayList<>();
for (String city : cities) {
// 먼저 조건을 확인한다
if (city.startsWith("N")) {
// 조건을 만족하면 변환을 적용한다
lengths.add(city.length());
}
}
System.out.println(lengths); // 출력: [8, 9]
}
}
패턴은 이렇습니다. 루프 내부에서 먼저 조건(startsWith, 비교, 범위 검사 등)으로 요소를 필터링한 다음, 필요한 변환을 적용하고 결과를 새 리스트에 넣습니다.
5. 흔한 실수와 함정
오류 №1: 순회 중 원본 컬렉션 변경. 자주 하는 실수는 for-each 루프 안에서 원본 컬렉션의 요소를 추가/삭제하는 것입니다. 이는 오류와 예측 불가능한 동작을 유발합니다. 해결책: 항상 결과를 위한 새 리스트를 만들고 그 리스트를 채우세요.
오류 №2: 잘못된 타입 캐스팅. “로(raw)” 컬렉션(예: Object로 다룸)을 사용하면서 요소를 잘못된 타입으로 캐스팅하면 ClassCastException이 발생합니다. 제네릭(List<T>)을 사용하고 시그니처를 주의 깊게 확인하세요.
오류 №3: 비효율적인 자원 사용. 매우 큰 컬렉션에 대해 새로운 리스트를 계속 만들고 요소를 복사하면 메모리와 시간 측면에서 부담이 될 수 있습니다. 대부분의 일상적인 작업에서는 무리가 없지만, 대용량 데이터를 처리할 때는 복잡도를 고려하고 필요하다면 접근 방식을 최적화하세요.
GO TO FULL VERSION