Experience/LG CNS AM Inspire Camp 1기

[LG CNS AM Inspire Camp] 12. 자바 제네릭, 컬렉션, 함수형 프로그래밍, 람다식, 스트림

chillmyh 2025. 2. 10. 17:51

1. 자바 제네릭(Generic)

1.1. 제네릭이란?

제네릭(Generic)은 컴파일 시 타입을 지정하여 타입 안정성을 보장하고 코드 재사용성을 높이는 기능입니다. 클래스, 인터페이스, 메서드에서 사용할 수 있으며, 타입을 명시하지 않아도 다양한 데이터 타입을 처리할 수 있도록 해줍니다.

 

타입 안정성 보장: 컴파일 시 타입을 체크하여 오류를 방지할 수 있음

코드 중복 방지: 동일한 코드에서 여러 타입을 지원할 수 있음

1.2. 제네릭을 사용하지 않는 경우의 문제점

class Apple {}
class Pencil {}

class ManageApple {
    private Apple apple = new Apple();
    public Apple get() { return this.apple; }
    public void set(Apple apple) { this.apple = apple; }
}

class ManagePencil {
    private Pencil pencil = new Pencil();
    public Pencil get() { return this.pencil; }
    public void set(Pencil pencil) { this.pencil = pencil; }
}

 

새로운 상품이 추가될 때마다 새로운 관리 클래스를 만들어야 하므로 유지보수성이 떨어짐.

1.3. 제네릭을 이용한 해결 방법

class Manage<T> {
    private T item;
    public T get() { return this.item; }
    public void set(T item) { this.item = item; }
}

 

T를 이용하여 다양한 데이터 타입을 저장할 수 있도록 설계

✅ 코드 중복 제거 및 재사용 가능

public class Test {
    public static void main(String[] args) {
        Manage<Apple> appleManager = new Manage<>();
        appleManager.set(new Apple());
        Apple apple = appleManager.get();

        Manage<Pencil> pencilManager = new Manage<>();
        pencilManager.set(new Pencil());
        Pencil pencil = pencilManager.get();
    }
}

 

Manage<T> 클래스를 통해 Apple, Pencil 등 다양한 객체를 저장하고 관리할 수 있음.

1.4. 제네릭 메서드 활용

제네릭을 메서드에 적용하면 다양한 타입의 데이터를 유연하게 처리할 수 있습니다.

public class GenericMethod {
    public static <T> void printArray(T[] array) {
        for (T item : array) {
            System.out.print(item + " ");
        }
        System.out.println();
    }

    public static void main(String[] args) {
        Integer[] intArray = {1, 2, 3};
        String[] strArray = {"A", "B", "C"};
        printArray(intArray); // 1 2 3
        printArray(strArray); // A B C
    }
}

 

T 타입을 사용하여 다양한 배열을 처리할 수 있음.

1.5. 와일드카드(Wildcard) 활용

제네릭에서 ?를 사용하면 다양한 타입을 유연하게 처리할 수 있습니다.

import java.util.List;

public class WildcardExample {
    public static void printList(List<?> list) {
        for (Object obj : list) {
            System.out.println(obj);
        }
    }
}

 

?를 사용하면 특정 타입이 아닌 모든 리스트를 처리 가능함

 

2. 컬렉션(Collection)

2.1. 컬렉션이란?

컬렉션은 데이터를 저장하고 관리하는 자료구조로, 자바에서는 Collection 인터페이스를 통해 다양한 컬렉션 클래스를 제공합니다.

 

List: 순서가 있는 데이터 저장 (ArrayList, LinkedList 등)

Set: 중복을 허용하지 않는 데이터 저장 (HashSet, TreeSet 등)

Map: 키-값 쌍으로 데이터 저장 (HashMap, TreeMap 등)

2.2. List 컬렉션

ArrayList vs. LinkedList

특징 ArrayList LinkedList
데이터 저장 방식 동적 배열 기반 연결 리스트 기반
검색 속도 빠름 (O(1)) 느림 (O(n))
삽입/삭제 속도 느림 (O(n)) 빠름 (O(1) - 중간 삽입 시)
List<String> list = new ArrayList<>();
list.add("사과");
list.add("바나나");
System.out.println(list); // [사과, 바나나]

 

ArrayList는 배열 기반이라 검색이 빠르고, LinkedList는 삽입/삭제가 빠름.

2.3. Set 컬렉션

HashSet vs. TreeSet

특징 HashSet TreeSet
중복 허용 X X
정렬 여부 X O (정렬됨)
Set<String> set = new HashSet<>();
set.add("사과");
set.add("바나나");
System.out.println(set); // [사과, 바나나]

 

HashSet은 중복을 허용하지 않으며, TreeSet은 정렬된 상태로 유지됨.

2.4. Map 컬렉션 상세 설명

HashMap vs. TreeMap

특징 HashMap TreeMap
정렬 여부 X O (키 기준 정렬)
성능 빠름 느림
Map<Integer, String> map = new HashMap<>();
map.put(1, "사과");
map.put(2, "바나나");
System.out.println(map.get(1)); // 사과

 

HashMap은 키-값 쌍을 저장하며, TreeMap은 키 기준으로 정렬된 데이터를 유지함.

 

3. 함수형 프로그래밍

3.1. 함수형 프로그래밍이란?

함수형 프로그래밍은 데이터와 상태 변경을 최소화하고 순수 함수(Pure Function)를 기반으로 프로그래밍하는 방식입니다.

 

불변성 (immutability): 데이터를 변경하지 않고 새로운 값을 반환

순수 함수 (pure function): 동일한 입력값에 대해 항상 같은 출력값을 반환

@FunctionalInterface
interface Calculator {
    int calculate(int x, int y);
}

public class LambdaExample {
    public static void main(String[] args) {
        Calculator add = (x, y) -> x + y;
        System.out.println(add.calculate(5, 3)); // 8
    }
}

 

함수형 인터페이스를 활용한 간결한 연산 구현 가능.

4. 람다식(Lambda Expression)

4.1. 람다식이란?

람다식은 자바에서 함수형 프로그래밍을 지원하는 문법으로, 간결한 코드 블록을 작성할 수 있습니다.

 

✅ 익명 함수(anonymous function)로 사용할 수 있음

✅ 기존 익명 클래스보다 가독성이 뛰어남

@FunctionalInterface
interface Printer {
    void print(String message);
}

public class LambdaExample {
    public static void main(String[] args) {
        Printer printer = message -> System.out.println(message);
        printer.print("Hello, Lambda!");
    }
}

 

Printer 인터페이스를 람다식으로 구현하여 코드 간결화

4.2. 다양한 람다식 표현

// 매개변수와 반환값이 없는 경우
() -> System.out.println("Hello World");

// 매개변수는 있고, 반환값이 없는 경우
x -> System.out.println(x);

// 매개변수와 반환값이 있는 경우
(x, y) -> x + y;

 

람다식을 활용하면 코드의 가독성과 유지보수성이 향상됨.

 

5. 스트림(stream)

5.1 스트림이란?

스트림(Stream)은 자바 8에서 추가된 기능으로, 컬렉션(배열 포함)의 저장 요소를 하나씩 참조하면서 람다식을 이용하여 처리할 수 있도록 해주는 반복자입니다.

 

컬렉션을 함수형으로 처리할 수 있음

내부 반복자를 사용하여 코드 가독성을 높임

원본 데이터를 변경하지 않으며, 불변성을 유지

병렬 처리를 활용하여 성능을 최적화 가능

5.2 스트림 생성 방법

import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;

public class StreamExample {
    public static void main(String[] args) {
        List<String> list = Arrays.asList("Apple", "Banana", "Cherry");
        
        // 컬렉션을 스트림으로 변환
        Stream<String> stream = list.stream();
        
        // 스트림 요소 출력
        stream.forEach(System.out::println);
    }
}

stream() 메서드를 사용하여 리스트를 스트림으로 변환하고, forEach()를 통해 출력할 수 있습니다.

5.3 스트림의 주요 연산

스트림은 **중간 연산(Intermediate Operation)**과 **최종 연산(Terminal Operation)**으로 나뉩니다.

5.3.1 중간 연산 (Intermediate Operations)

filter(): 특정 조건을 만족하는 요소만 걸러내는 연산

List<String> list = Arrays.asList("Apple", "Banana", "Cherry", "Blueberry");
list.stream()
    .filter(s -> s.startsWith("B"))
    .forEach(System.out::println); // Banana, Blueberry

map(): 요소를 다른 값으로 변환하는 연산

List<String> list = Arrays.asList("Apple", "Banana", "Cherry");
list.stream()
    .map(String::toUpperCase)
    .forEach(System.out::println); // APPLE, BANANA, CHERRY

sorted(): 요소를 정렬하는 연산

List<Integer> numbers = Arrays.asList(3, 1, 4, 1, 5, 9);
numbers.stream()
    .sorted()
    .forEach(System.out::println); // 1, 1, 3, 4, 5, 9

distinct(): 중복 요소 제거

List<String> list = Arrays.asList("Apple", "Banana", "Apple", "Cherry");
list.stream()
    .distinct()
    .forEach(System.out::println); // Apple, Banana, Cherry

5.3.2 최종 연산 (Terminal Operations)

forEach(): 모든 요소를 반복하면서 처리

List<String> list = Arrays.asList("Apple", "Banana", "Cherry");
list.stream()
    .forEach(System.out::println);

count(): 요소의 개수를 반환

long count = list.stream().count();
System.out.println("개수: " + count);

reduce(): 요소를 하나의 결과로 축소

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.stream().reduce(0, Integer::sum);
System.out.println("합계: " + sum); // 15

5.4 병렬 스트림(Parallel Stream)

순차 처리와 병렬 처리 비교

import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;

public class ParallelStreamExample {
    public static void main(String[] args) {
        List<String> list = Arrays.asList("Apple", "Banana", "Cherry", "Blueberry");

        // 순차 처리
        list.stream().forEach(System.out::println);
        
        System.out.println("----");
        
        // 병렬 처리
        list.parallelStream().forEach(System.out::println);
    }
}

 

parallelStream()을 사용하면 멀티코어를 활용하여 병렬로 데이터를 처리할 수 있습니다.

6. 마무리

이번 강의를 통해 자바 제네릭, 컬렉션, 함수형 프로그래밍, 람다식의 개념과 활용법을 학습하였습니다.


이 글은 LG CNS AM Inspire Camp 1기 진행 중 Obsidian에 정리해뒀던 글을 블로그에 옮긴 글입니다.