TIL - 20260101

juni·2025년 12월 31일

TIL

목록 보기
225/316

0101 Java 중급으로의 도약: 제네릭, 람다와 스트림


✅ 1. 제네릭 (Generics)

  • 제네릭이란 클래스나 메서드를 작성할 때, 다룰 데이터의 타입(Type)을 미리 지정하지 않고, 실제 사용할 때 지정할 수 있도록 하는 기능입니다. List<String>에서 <String> 부분이 바로 제네릭입니다.

  • 왜 사용하는가? (제네릭의 장점)

    1. 타입 안정성 (Type Safety): 컴파일 시점에 잘못된 타입의 데이터가 들어오는 것을 컴파일러가 미리 체크해줍니다.
    2. 형 변환(Casting) 제거: 컬렉션에서 데이터를 꺼낼 때마다 번거롭게 형 변환을 할 필요가 없어 코드가 간결해집니다.

➕ 제네릭이 없을 때의 문제점

// 제네릭이 없는 경우
List list = new ArrayList();
list.add("hello");
list.add(123); // 컴파일 시점에는 오류를 잡지 못함

String text = (String) list.get(0); // 정상
String number = (String) list.get(1); // 실행 시 ClassCastException 발생!

➕ 제네릭을 사용한 해결

// 제네릭을 사용한 경우
List<String> list = new ArrayList<>();
list.add("hello");
// list.add(123); // 컴파일 시점에 바로 오류 발생! (타입 안정성)

String text = list.get(0); // 형 변환이 필요 없음

✅ 2. 람다 표현식 (Lambda Expression)

  • 람다 표현식은 Java 8에서 도입된 혁신적인 기능으로, 이름이 없는 함수(Anonymous Function)를 간결하게 표현하는 방법입니다.

  • 주로 함수형 인터페이스(Functional Interface)를 구현할 때 사용됩니다.

    • 함수형 인터페이스: 단 하나의 추상 메서드만을 가지는 인터페이스. (e.g., Runnable, Comparator)
  • 목적: 불필요한 익명 클래스 코드를 줄여, 코드를 훨씬 더 간결하고 가독성 높게 만듭니다.

➕ 람다 표현식의 진화

// 1. 기존 방식 (익명 클래스)
new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("Thread is running.");
    }
}).start();

// 2. 람다 표현식 적용
// 어차피 Runnable 인터페이스는 run() 메서드 하나 뿐이므로,
// () -> { ... } 부분이 run() 메서드의 구현부임을 컴파일러가 추론함
new Thread(() -> System.out.println("Thread is running.")).start();
  • 기본 문법: (매개변수) -> { 실행문 }

✅ 3. 스트림 API (Stream API)

  • 스트림 API는 Java 8에서 람다와 함께 도입된 기능으로, 컬렉션(배열, 리스트 등)의 요소들을 하나씩 참조하며 반복적으로 처리할 수 있게 해주는 "데이터의 흐름"입니다.
  • for문이나 while문과 같은 반복문을 사용하지 않고, 선언형(Declarative)으로 데이터를 처리할 수 있어 코드가 매우 간결해집니다.

➕ 스트림의 3단계 구조

  1. 스트림 생성 (Creation): 컬렉션 객체로부터 .stream() 메서드를 호출하여 스트림을 생성합니다.
  2. 중간 연산 (Intermediate Operations): 생성된 스트림을 가공하는 단계. 여러 개의 중간 연산을 연결(chaining)할 수 있습니다.
    • filter(조건): 조건에 맞는 요소만 남김.
    • map(함수): 각 요소를 다른 값으로 변환.
    • sorted(): 요소를 정렬.
  3. 최종 연산 (Terminal Operations): 스트림의 처리를 마무리하고 결과를 만들어내는 단계. 최종 연산이 호출되어야 모든 중간 연산이 실행됩니다.
    • collect(Collectors.toList()): 스트림을 다시 리스트로 변환.
    • forEach(동작): 각 요소를 순회하며 동작 수행.
    • count(): 요소의 개수를 반환.

➕ 스트림 API 사용 예시

List<String> names = Arrays.asList("Java", "Python", "JavaScript", "Go");

// 전통적인 for문 방식
List<String> result = new ArrayList<>();
for (String name : names) {
    if (name.startsWith("J")) {
        result.add(name.toUpperCase());
    }
}

// 스트림 API 방식
List<String> streamResult = names.stream() // 1. 스트림 생성
    .filter(name -> name.startsWith("J")) // 2. 중간 연산: "J"로 시작하는 이름만 필터링
    .map(String::toUpperCase)             // 2. 중간 연산: 대문자로 변환
    .collect(Collectors.toList());        // 3. 최종 연산: 결과를 리스트로 수집

// streamResult: ["JAVA", "JAVASCRIPT"]

📌 요약

  • 제네릭(<>)컴파일 시점의 타입 안정성을 제공하여, 런타임에 발생할 수 있는 ClassCastException을 예방하고 코드를 깔끔하게 만듭니다.
  • 람다 표현식(->)함수형 인터페이스의 익명 구현체를 매우 간결하게 표현하는 방법으로, 현대 Java 코드의 가독성을 높이는 핵심 요소입니다.
  • 스트림 APIfor문을 대체하여 컬렉션 데이터를 선언형으로 처리하는 강력한 도구입니다. filter, map과 같은 중간 연산과 collect와 같은 최종 연산을 조합하여 복잡한 데이터 처리를 간결하게 표현할 수 있습니다.
  • 이 세 가지 기능은 현대 Java 개발의 필수 소양이므로 반드시 숙지해야 합니다.

0개의 댓글