본 내용은 강의 내용 뿐 아니라, 추가 공부 정리 내용도 포함합니다. 혹시 잘못된 내용이 있다면 지적 적극환영! :)
✅ 섹션 4 람다활용 - 필터, 맵
🔹 1. 람다의 활용: 필터(Filter)와 맵(Map)
- Filter (Predicate<T>): 조건에 맞는 요소만 골라내기
- Map (Function<T, R>): 요소를 다른 값으로 변환하기
// 짝수 필터
filter(numbers, n -> n % 2 == 0);
// 문자열 길이로 변환
map(strings, s -> s.length());
2. 유틸리티 패턴화 → 재사용성 증가
- GenericFilter<T> 와 GenericMapper<T, R> 클래스를 만들어 모든 타입에 유연하게 대응 가능함.
GenericFilter.filter(numbers, n -> n > 5);
GenericMapper.map(strings, s -> s.toUpperCase());
🔹 3. 명령형 vs 선언형 프로그래밍
스타일 | 로직 어떻게 수행할지 직접 명시 | 결과를 무엇을 원하는지 선언 |
예시 | for, if, 변수 생성 | filter(), map() |
특징 | 제어 명확함, 코드 길어짐 | 가독성 좋음, 코드 간결 |
🔹 4. 스트림 직접 구현: MyStream
- filter(), map(), forEach() 메서드 체이닝 구현
- 내부 반복 (internal iteration) 방식 사용 → 가독성 향상
MyStream.of(list)
.filter(e -> 조건)
.map(e -> 변환)
.forEach(e -> System.out.println(e));
🔹 5. 제네릭 스트림 (MyStreamV3<T>)
- 타입 제한 없이 사용 가능
- filter(), map(), forEach() 모두 체이닝 가능
- 내부적으로 List<T> 유지하며 각 단계마다 새로운 스트림 반환
패턴 정리
필터 + 맵
List<String> names = GenericMapper.map(
GenericFilter.filter(students, s -> s.getScore() >= 80),
s -> s.getName()
);
스트림 메서드
List<String> result = MyStreamV3.of(students)
.filter(s -> s.getScore() >= 80)
.map(s -> s.getName())
.map(name -> name.toUpperCase())
.toList();
최종 결과 (내부 반복 포함)
MyStreamV3.of(list)
.filter(조건)
.map(변환)
.forEach(e -> System.out.println(e));
✅참고 - Predicate<T> vs Function<T, R> vs Consumer<T>
Predicate<T> | 조건 검사 | boolean | n -> n % 2 == 0 |
Function<T, R> | 값 변환 | R | s -> s.length() |
Consumer<T> | 소비(출력 등) | void | s -> System.out.println(s) |
✅ 정적 팩토리 메서드 (static factory method)
객체를 생성하는 static 메서드로, new 키워드 대신 사용하여 객체를 생성해서 인스턴스를 반환하는 메서드
→ 생성자보다 더 유연하고 명확하게 객체를 만들 수 있게 해준다.
public class MyClass {
private MyClass() {} // 생성자는 감춘다
public static MyClass of() {
return new MyClass();
}
}
// 사용 예
MyClass obj = MyClass.of();
✅ 왜 정적 팩토리 메서드를 사용하는가?
1. 이름으로 의미를 명확히 표현할 수 있어서
- 생성자는 new Something()처럼 이름을 못 붙이지만,
정적 메서드는 from(), of(), createAdminUser()처럼 용도/상황을 설명할 수 있음
User user = User.createAdminUser("hyunjung");
2. 객체를 재사용하거나 캐싱할 수 있어서 (성능↑)
- 매번 new로 만들지 않고, 미리 만들어둔 객체를 반환해 성능 향상 가능
// 아래는 두개의 객체가 생김
Integer a = new Integer(100);
Integer b = new Integer(100);
// 자주쓰이는 값을캐싱해놓음으로써 아래는 기존 객체 재사용
Integer a = Integer.valueOf(100);
Integer b = Integer.valueOf(100);
System.out.println(a == b); // true!
// 내부 동작 (간단히 설명)
if (value >= -128 && value <= 127) {
return cached[value + 128];
} else {
return new Integer(value);
}
3. 상속/하위 타입 객체도 반환할 수 있어서
- 인터페이스 또는 추상 클래스에서 실제 구현체를 숨기고,
다른 객체나 하위 클래스 인스턴스를 반환 가능
Animal a = AnimalFactory.create("cat"); // 실제로는 new Cat()
4. 생성자보다 유연하게 로직을 제어할 수 있어서
- 조건 분기, 생성 제한, validation 등을 메서드 내부에 구현 가능
public static User from(String email) {
if (!email.contains("@")) throw new IllegalArgumentException();
return new User(email);
}
5. 생성자 숨기고 객체 생성을 통제할 수 있어서
- 외부에서 new를 막고, 객체 생성 방식 통일 + 의도된 방식만 허용
private User() {} // 생성자 숨김
public static User of(String name) { return new User(name); }
✅ 섹션 5 람다 vs 익명 클래스
1️⃣ 문법 차이 - 람다 vs 익명클래스
- 익명 클래스:
클래스를 즉석에서 정의하고 사용하는 방식
→ new 인터페이스() { ... } 이렇게 길다
→ 메서드를 반드시 오버라이딩해야 함 - 람다 표현식:
→ 매개변수 -> 실행문 한 줄이면 끝
→ 메서드 하나만 있는 인터페이스(함수형 인터페이스)일 때만 사용 가능
👉 같은 기능이면 람다가 더 짧고 간단하다.
2️⃣ this 키워드 – "내가 누구지?"
- 익명 클래스 안의 this: 익명 클래스 자기 자신을 가리킴
- 람다 안의 this: 람다가 선언된 외부 클래스를 가리킴
(람다는 외부 클래스랑 이어져 있고, 익명 클래스는 따로 노는 느낌..?)
3️⃣ 상태 관리 – "내가 값 저장할 수 있어?"
- 익명 클래스는 필드(변수)를 만들 수 있어서 내부에 값을 저장하거나 상태를 유지할 수 있음
- 람다는 그냥 함수처럼 작동함 → 상태 저장 X
상태를 유지해야 한다면 익명 클래스를 써야 함
4️⃣ 상속 가능성 – "상속이나 메서드 여러 개 구현할 수 있어?"
- 익명 클래스: 가능함 (클래스 상속도 되고, 메서드 여러 개 오버라이드 가능)
- 람다: 함수형 인터페이스만 구현 가능 (메서드 딱 하나)
복잡하거나 구현할 게 많으면 익명 클래스
5️⃣ 변수 캡처 (capturing) – "외부 변수 쓸 수 있나?"
- 둘 다 외부 지역 변수는 final이거나 사실상 final이어야 사용 가능
→ 즉, 값이 바뀌지 않는 변수만 접근 가능
람다와 익명 클래스 안에서는 값을 바꾸지 않는 외부 변수만 사용할 수 있다.
📌 왜 final 또는 값이 안 바뀌는 변수만 사용할 수 있을까?
- num은 makeRunnable() 안에서 선언된 지역 변수인데 r.run()은 나중에 다른 메서드에서 실행되고 있다고 가정해보자.
Runnable r = null;
public void makeRunnable() {
int num = 10;
r = () -> System.out.println(num); // 외부 변수 사용
}
public void runLater() {
r.run(); // 나중에 호출
}
- 지역 변수 num은 makeRunnable()이 끝나면 사라진다. (스택 메모리에서 날아감)
- 근데 람다 안에서 그 변수를 참조하고 있다.
- 그럼 없는 값을 참조하게 되니까 위험하다.
6️⃣ 생성 방식 – "JVM이 내부에서 어떻게 처리?"
- 익명 클래스: 진짜 클래스를 하나 만들어서 .class 파일이 생김 (예: Outer$1.class)
- 람다: .class 안 만들고, 실행할 때 invokeDynamic으로 동적으로 처리함
람다가 조금 더 메모리 관리 효율적이지만, 성능 차이는 거의 없음!
7️⃣ 결정기준
간단한 동작, 메서드 1개만 구현 | ✅ 람다 |
필드 값 저장(상태 유지), 메서드 여러 개 | ✅ 익명 클래스 |
가독성/유지보수 중요, 복잡하지 않음 | ✅ 람다 |
자바 8 이전 버전 | ✅ 익명 클래스만 가능 |
'Java' 카테고리의 다른 글
[Java] 김영한의 실전 자바 - 고급 3편 섹션7 스트림API (1) | 2025.04.12 |
---|---|
[Java] 김영한의 실전 자바 - 고급 3편 섹션6 메서드 참조 (0) | 2025.04.12 |
[Java] 익명 클래스 vs 람다: 자바 람다, 내부에선 어떤 일이 벌어질까?(Java Lambda & invokedynamic) (0) | 2025.04.03 |
[Java] 김영한의 실전 자바 - 고급 3편 섹션3 함수형 인터페이스 (0) | 2025.04.03 |
[Java] 김영한의 실전 자바 - 고급 3편 섹션1,2 람다 (2) | 2025.03.31 |