본문 바로가기
TIL

[TIL] 2025-05-21 스트림, 내부클래스, 스레드, 람다식

by Enhydra lutris 2025. 5. 21.
728x90

오늘 배웠던 내용들은 프로젝트나 코딩테스트 문제를 풀면서 대충 훑어보기만 하고 제대로 공부한적 없던 내용들이 나와서 전체적으로 정리해보았습니다.

 

구분 설명 예시
Stream 컬렉션 데이터를 선언형 방식으로 처리하는 API. 중간 연산(map, filter 등)과 최종 연산(forEach, sum 등) 구분 list.stream().filter(...).map(...).forEach(...)
내부 클래스 (Inner Class) 클래스 안에 정의된 클래스 (다른 클래스에 종속) 클래스 A 안에 클래스 B 정의
└ 인스턴스 내부 클래스 외부 클래스의 인스턴스가 있어야 생성 가능 Outer outer = new Outer(); Outer.Inner in = outer.new Inner();
└ 정적 내부 클래스 (static) 외부 클래스와 독립적으로 생성 가능 Outer.StaticInner in = new Outer.StaticInner();
└ 지역 내부 클래스 메서드 내부에서 정의된 클래스. 해당 메서드 안에서만 사용 가능 void method() { class Local { } }
└ 익명 내부 클래스 이름 없이 일회용 구현. 보통 인터페이스/추상클래스 구현 시 사용 new Runnable() { public void run() { ... } }
스레드 (Thread) 동시에 여러 작업을 처리하기 위한 실행 흐름 단위. Thread 클래스나 Runnable 인터페이스로 구현 new Thread(() -> { ... }).start();
     
람다식 (Lambda) 함수형 인터페이스를 간결하게 구현하는 문법. 익명 내부 클래스보다 짧고 읽기 쉬움  

 

 

1. Stream (스트림)

개요

컬렉션 데이터를 반복문 없이 선언적 방식으로 처리하는 API. Java 8에 도입되었으며, 중간 연산과 최종 연산을 체이닝 형태로 구성한다.

기존 방식과의 차이

기존에는 for문이나 while문 등 명령형 방식으로 처리했지만, 스트림은 map, filter, reduce 등의 메서드 체이닝으로 데이터를 처리한다.

장점

  • 코드 간결성 향상
  • 병렬 처리 용이 (parallelStream())
  • 지연 연산 지원 (lazy evaluation)
  • 불변 객체 기반 처리 권장

단점 및 주의사항

  • 한 번 소비한 스트림은 재사용 불가
  • 디버깅이 어렵고, 복잡한 로직은 오히려 가독성이 떨어질 수 있음
  • 소량 데이터는 일반 루프보다 오버헤드가 클 수 있음
List<String> names = Arrays.asList("Kim", "Lee", "Park");
names.stream()
     .filter(s -> s.length() >= 3)
     .sorted()
     .forEach(System.out::println);

 


2. 내부 클래스 (Inner Class)

자바 클래스 내부에 정의된 클래스이며, 크게 4가지로 나뉜다.

2-1. 인스턴스 내부 클래스

  • 외부 클래스의 인스턴스가 있어야 생성 가능
  • 외부 클래스의 멤버에 자유롭게 접근 가능
  • Outer outer = new Outer(); Outer.Inner in = outer.new Inner();

장점

  • 외부 클래스와 밀접한 관계가 있는 경우 코드 구조화에 유리

주의사항

  • 외부 인스턴스와 생명 주기를 공유하므로 메모리 누수 주의

2-2. 정적 내부 클래스 (static nested class)

  • 외부 클래스와 독립적으로 생성 가능
  • 외부 클래스의 정적 멤버만 접근 가능
  • Outer.StaticInner inner = new Outer.StaticInner();

장점

  • 메모리 효율적
  • 유틸리티 클래스나 빌더 패턴 구현 시 적합

2-3. 지역 내부 클래스

  • 메서드 내부에 선언되는 클래스
  • 해당 메서드 내에서만 사용 가능
  • 외부 변수는 final 또는 effectively final이어야 참조 가능

주의사항

  • 수명이 짧고 지역 변수 접근 시 제약이 많아 가독성이 떨어질 수 있음

2-4. 익명 내부 클래스

  • 이름 없이 일회성으로 인터페이스나 추상 클래스를 구현
  • 이벤트 처리나 콜백 등에서 사용

장점

  • 간단한 처리 로직에 적합

단점

  • 재사용 불가
  • 가독성 및 디버깅 불리
Runnable r = new Runnable() {
    public void run() {
        System.out.println("실행");
    }
};

3. 스레드 (Thread)

개요

프로세스 내에서 독립적으로 실행되는 흐름 단위. 자바에서 병렬 처리를 위한 기본 단위다.

구현 방식

  • Thread 클래스 상속
  • Runnable 인터페이스 구현
  • Java 8 이후에는 람다식을 통한 구현 가능

장점

  • 병렬 처리를 통해 대기 시간 단축
  • 사용자 응답성 향상

단점 및 주의사항

  • 공유 자원 접근 시 동기화 필요 (synchronized)
  • 스레드가 너무 많으면 오히려 성능 저하
  • 생명 주기 관리 복잡 → ExecutorService 권장
new Thread(() -> {
    System.out.println("작업 실행");
}).start();

4. 람다식 (Lambda Expression)

개요

함수형 인터페이스를 간단하게 구현할 수 있는 표현식. Java 8부터 도입.

기존 방식과 차이

기존에는 익명 내부 클래스로 구현했으나, 람다식은 훨씬 간결한 표현 가능

장점

  • 코드 간결
  • 불필요한 클래스 제거
  • 가독성 향상

단점 및 주의사항

  • 디버깅 어려움 (스택 트레이스 분석 힘듦)
  • this는 익명 내부 클래스와 다르게 람다를 감싸는 외부 객체를 가리킴
  • 상태 있는 객체 사용에 부적합 (가급적 순수 함수로 활용)

예시

java
복사편집
Comparator<Integer> comp = (a, b) -> Integer.compare(a, b);

5. 함수형 인터페이스 (Functional Interface)

개요

추상 메서드가 하나만 존재하는 인터페이스. 람다식의 대상이 되기 위해 필요

특징

  • @FunctionalInterface 애노테이션 사용 가능 (선택사항이지만 권장)
  • 디폴트 메서드는 여러 개 정의 가능하나 추상 메서드는 하나만 있어야 함

장점

  • 람다식과 결합해 코드 간결성 극대화
  • 콜백이나 일회성 작업 처리에 유용

주의사항

  • 추상 메서드가 2개 이상이면 컴파일 오류 발생
  • 인터페이스가 의도하지 않게 변경될 경우 람다식 사용 불가

자바 제공 주요 함수형 인터페이스

  • Runnable - 매개변수 없음, 반환값 없음
  • Consumer<T> - 매개변수 있음, 반환값 없음
  • Supplier<T> - 매개변수 없음, 반환값 있음
  • Function<T, R> - 매개변수 T, 반환값 R
  • Predicate<T> - 조건 검사 (boolean 반환)

댓글