| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | 7 |
| 8 | 9 | 10 | 11 | 12 | 13 | 14 |
| 15 | 16 | 17 | 18 | 19 | 20 | 21 |
| 22 | 23 | 24 | 25 | 26 | 27 | 28 |
- 라피신
- openAI API
- UNICON2023
- bastion host
- UNICON
- 전국대학생게임개발동아리연합회
- 도커
- Route53
- Spring boot
- EC2
- 캡스톤디자인프로젝트
- 체크인미팅
- spring ai
- 생활코딩
- 프로그래밍
- 프리티어
- AWS
- 티스토리챌린지
- 42서울
- 인프라
- NAT gateway
- 스프링부트
- 개발공부
- 오블완
- 게임개발동아리
- 프롬프트엔지니어링
- Redis
- UNIDEV
- 백엔드개발자
- CICD
- Today
- Total
Hyun's Wonderwall
[Java] 이펙티브 자바 - 6장(2/2), 7장(1/2) (아이템 37~45) 본문
6장 열거 타입과 애너테이션 (2/2)
아이템 37. ordinal 인덱싱 대신 EnumMap을 사용하라
EnumMap: 열거 타입을 키로 사용하도록 설계한 아주 빠른 맵 구현체
- 내부에서 배열을 사용해 빠름..
- EnumMap의 생성자가 받는 키 타입의 Class 객체는 한정적 타입 토큰. 런타임 제네릭 타입 정보를 제공.
Map<LifeCycle, Set<Plant>> plantsByLifeCycle = new EnumMap<>(LifeCycle.class);
for (LifeCycle lc : LifeCycle.values()) {
plantsByLifeCycle.put(lc, new HashSet<>()); // 빈 Set 초기화
}
for (Plant p : garden) {
plantsByLifeCycle.get(p.lifeCycle).add(p);
}
System.out.println(plantsByLifeCycle);
(모든 enum 키가 항상 존재. garden에 없더라도 출력.)
스트림을 사용해 맵을 관리하면 코드를 더 줄일 수 있다.
- 동작에 차이가 존재: EnumMap은 열거 타입의 모든 상수를 키로 만들지만, 스트림 버전에서는 실제 데이터가 있는 상수만 키로 만든다.
System.out.println(Arrays.stream(garden)
.collect(Collectors.groupingBy(p -> p.lifeCycle,
() -> new EnumMap<>(LifeCycle.class),toSet())));
(즉, 등장한 lifeCycle만 키로 출력)
배열의 인덱스를 얻기 위해 ordinal을 사용하지 말고, EnumMap을 사용하자!
아이템 38. 확장할 수 있는 열거 타입이 필요하면 인터페이스를 사용하라
열거 타입은 거의 모든 상황에서 '타입 안전 열거 패턴'(typesafe enum pattern)보다 우수하다. 확장할 수 없다는 점만이 따지자면 단점이나, 사실 열거 타입의 쓰임 상황에서는 대개 확장하는 것이 좋지 않기 때문에 그렇게 설계된 것.
열거 타입을 자체는 확장할 수 없지만, 인터페이스와 그 인터페이스를 구현하는 기본 열거 타입을 사용해 같은 효과를 낼 수 있다.
- 클라이언트는 이 인터페이스를 구현해 자신만의 열거 타입을 만들 수 있다.
- API가 인터페이스 기반으로 작성되었다면 기본 열거 타입의 인스턴스가 쓰이는 모든 곳을 확장한 열거 타입의 인스턴스로 대체해 사용할 수 있다.
아이템 39. 명명 패턴보다 애너테이션을 사용하라
애너테이션으로 할 수 있는 일을 명명 패턴으로 처리할 이유는 없다. 자바가 제공하는 애너테이션 타입들을 잘 사용하자.
아이템 40. @Override 애너테이션을 일관되게 사용하라
@Override는 메서드 선언에만 달 수 있으며, 이 애너테이션이 달렸다는 것은 상위 타입의 메서드를 재정의했음을 뜻한다.
- 안 달면 파라미터가 다른 경우 다중정의(오버로딩)으로 인식되는 버그가 발생한다.(컴파일러가 찾아내준다)
- 애너테이션을 통해 오버로딩이 아니라 오버라이딩한 것이라고 명시해야 한다.
- 상위 클래스의 메서드를 재정의하는 모든 메서드에 @Override 애너테이션을 달자.
- 구체 클래스에서 상위 클래스의 추상 메서드를 재정의할 때는 달지 않아도 되긴 하다.(구현하지 않은 추상 메서드가 남아 있다면 컴파일러가 그 사실을 바로 알림. 달아도 문제는 x.)
@Override 는 인터페이스 메서드를 재정의 할 때도 사용할 수 있다. 디폴트 메서드를 지원하기 시작해서.
추상 클래스나 인터페이스에서는 상위 클래스나 상위 인터페이스 메서드를 재정의하는 모든 메서드에 @Override를 다는 것이 좋다.
=> 가독성 + IDE 경고 등 종합해보면 재정의 메서드에는 다 @Override 붙이는 게 좋은 듯
아이템 41. 정의하려는 것이 타입이라면 마커 인터페이스를 사용하라
마커 인터페이스: 아무 메서드도 담고 있지 않고, 단지 자신을 구현하는 클래스가 특정 속성을 가짐을 표시해주는 인터페이스.
- ex. Serializable: 자신을 구현한 클래스의 인스턴스는 직렬화할 수 있다고 알려준다. (ObjectOutputStream을 통해 쓸 수 있다고)
새로 등장한 '마커 애너테이션'보다 마커 인터페이스가 나은 점 2가지
1. 마커 인터페이스는 이를 구현한 클래스들을 구분하는 타입으로 쓸 수 있으나, 마커 애너테이션은 X
2. 마커 인터페이스는 적용 대상을 더 정밀하게 지정할 수 있다.
반대로 마커 애너테이션이 나은 점: 거대한 애너테이션 시스템의 지원을 받는다.
각자의 쓰임이 있다.
- 새로 추가하는 메서드 없이 단지 타입 정의가 목적이라면 -> 마크 인터페이스
- 클래스나 인터페이스 외의 프로그램 요소에 마킹해야 하거나, 애너테이션을 적극 활용하는 프레임워크의 일부로 그 마커를 편입시키고자 한다면 -> 마커 애너테이션
7장 람다와 스트림
자바 8에서 함수형 인터페이스, 람다, 메서드 참조라는 개념이 추가되면서 함수 객체를 쉽게 만들 수 있게 되었다. 이와 함께 스트림 API까지 추가되어 데이터 원소의 시퀀스 처리를 라이브러리 차원에서 지원하기 시작했다.
아이템 42. 익명 클래스보다는 람다를 사용하라
람다식(lambda expression): 함수형 인터페이스 인스턴스 생성 가능. 작은 함수 객체를 아주 쉽게 표현할 수 있게 됨.
- 람다에서의 this는 바깥 인스턴스를 가리킨다.
타입을 명시해야 코드가 더 명확할 때만을 제외하고 람다의 모드는 매개변수 타입은 생략하자.
Collections.sort(words, (s1, s2) -> Integer.compare(s1.length(), s2.length()));
람다 자리에 비교자 생성 메서드를 사용하면 이 코드를 더 간결하게 만들 수 있다.
Collections.sort(words, comparingInt(String::length));
더 나아가 List 인터페이스의 sort 메서드를 이용하면 더욱 짧아진다.
words.sort(comparingInt(String::length)));
(아이템34) 열거 타입 상수별 동작을 정의하기 위해서도, 함수 객체(람다)를 인스턴스 필드에 저장해 상수별 동작을 구현하면 더욱 간단해진다.
- 열거 타입 상수의 동작을 표현한 람다를 DoubleBinaryOperator 인터페이스 변수에 할당했다.
- DoubleBinaryOperator 함수 인터페이스는 double 타입 인수 2개를 받아 double 타입 결과를 돌려준다.
익명 클래스를 사용하던 자리들 대부분이 람다로 대체
람다는 이름이 없고 문서화도 못 한다. 코드 자체로 동작이 설명되지 않거나 코드 줄 수가 많아지는 경우는 사용하지 x.
- 열거 타입 생성자에 넘겨지는 인수들의 타입도 컴파일타임에 추론됨. 따라서 열거 타입 생성자 안의 람다는 열거 타입의 인스턴스 멤버에 접근할 수 없다(인스턴스는 런타임에 만들어지기 때문에)
But 람다를 사용하지 못하고 익명 클래스를 사용해야 할 때: (함수형 인터페이스가 아닌) 타입의 인스턴스를 만들 때
- 추상 클래스의 인스턴스를 만들 때
- 추상 메서드가 여러 개인 인터페이스의 인스턴스를 만들 때
- 자신을 참조해야 할 때 (익명 클래스의 this는 인스턴스 자신을 가리킴)
람다 및 익명 클래스를 직렬화하는 일은 극히 삼가야 한다. 직렬화해야 하는 함수 객체가 있다면 (가령 Comparator) private 정적 중첩 클래스의 인스턴스를 사용하자.
아이템 43. 람다보다는 메서드 참조를 사용하라
람다는 간결. 그런데 메서드 참조가 더 간결.
메서드 참조 쪽이 짧고 명확하다면 메서드 참조를 쓰고, 그렇지 않을 때만 람다를 사용하자.
예제
Integer::parseInt
Instant.now()::isAfter
String::toLowerCase
TreeMap<K, V>::new
int[]::new
메서드 참조 표현식은 함수형 인터페이스를 위한 제네릭 함수 타입 구현 가능하지만, 람다식으로는 불가능하다.
아이템 44. 표준 함수형 인터페이스를 사용하라
java.util.function 패키지에 다양한 표준 함수형 인터페이스가 담겨있으므로, 필요한 용도에 맞는 게 있다면 직접 구현하지 말고 표준 함수형 인터페이스를 활용해라.
함수형 인터페이스 시그니처 ↔ 메서드 참조 매핑 예
- String::toLowerCase → UnaryOperator<String>에 매핑
- BigInteger::add → BinaryOperator<BigInteger> 에 매핑
- Collection::isEmpty → Predicate<Collection<?>> 에 매핑
- Arrays::asList → Function<T[], List<T>> 에 매핑
- Instant::now -> Supplier<Instant> 또는 Function<Clock, Instant>에 매핑
- System.out::println → Consumer<String> 에 매핑
함수형 인터페이스 대부분은 기본 타입만 지원하는데, 그렇다고 기본 함수형 인터페이스에 박싱된 기본 타입을 넣어 사용하지는 말자. 계산량이 많을 때는 성능이 처참히 느려질 수 있다.
직접 만들어 쓸 수도 있다. 직접 만든 함수형 인터페이스에는 항상 @FunctionalInterface 애너테이션을 사용하라.
아이템 45. 스트림은 주의해서 사용하라
스트림 API는 다량의 데이터 처리 작업을 돕고자 추가되었다.
- 플루언트 API. 파이프라인 하나를 구성하는 모든 호출을 연결해 하나의 표현식으로 완성할 수 있다. 파이프라인 여러 개를 표현식 하나로 만들 수도 있다.
스트림: 데이터 원소의 유한/무한 시퀀스.
스트림 파이프라인: 이 원소들로 수행하는 연산 단계를 표현하는 개념.
- 스트림 파이프라인은 소스 스트림에서 시작, 하나 이상의 중간 연산이 있을 수 있고, 종단 연산으로 끝난다.
- 중간 연산은 스트림을 변환한다.
스트림 파이프라인은 '지연 평가'되어, 평가되는 시점이 종단 연산 호출 때이다.
- 종단 연산에 쓰이지 않는 데이터 원소는 계산에 쓰이지 않는다. -> 이로써 무한 스트림을 다룰 수 있다.
- 파이프라인은 순차적으로 수행된다.
스트림을 과용하면 프로그램이 읽거나 유지보수하기 어려워진다. 적절히 사용하자.
char값들을 처리할 때는 스트림을 삼가는 편이 낫다. (.chars()가 int 스트림을 반환해서 헷갈릴 수 있어서. .forEach(x-> System.out.println((char)x)); 하면 된다)
기존 코드를 스트림을 사용하도록 리팩터링하고 새 코드가 더 나아 보일 때만 반영하자.
반복문으로만 할 수 있는 일들도 있다.(람다로는 불가능)
- 범위 안의 지역변수 읽고 수정
- return, break, continue로 빠져나가거나 종료/건너뛰는 것, 메서드 선언에 명시된 검사 예외 던지는 것
스트림이 적절한 때
- 원소들의 시퀀스를 일관되게 변환, 필터링, 하나의 연산을 사용해 결합(합산, 연결, 최솟값 구하기))할 때.
- 원소들의 시퀀스를 컬렉션에 모을 때(공통된 속성을 기준으로).
- 원소들의 시퀀스에서 특정 조건에 맞는 원소를 찾을 때.
스트림으로 처리하기 어려운 일
- 각 단계에서의 값들에 동시 접근 (스트림은 한 값을 다른 값에 매핑하고 나면 원래의 값을 잃는 구조이다)
+ 스트림 중간 연산 flatMap: 평탄화를 한다. (스트림의 원소 각각을 하나의 스트림으로 매핑한 다음 그 스트림들을 다시 하나의 스트림으로 합침)
스트림/반복 둘다 더 적합한 때가 있고, 대체로 어느 쪽이 나은지 확연히 드러난다.
스트림과 반복 중 어느 쪽이 더 나은지 확신하기 어렵다면 둘 다 해보고 더 나은 쪽을 택하라.
'Study > Java' 카테고리의 다른 글
| [Java] 이펙티브 자바 - 8장(2/2), 9장(1/2) (아이템 55-63) (1) | 2025.08.30 |
|---|---|
| [Java] 이펙티브 자바 - 7장(2/2), 8장(1/2) (아이템 46~54) (0) | 2025.08.23 |
| [Java] 이펙티브 자바 - 5장(2/2), 6장(1/2) (아이템 28~36) (4) | 2025.08.16 |
| [Java] 이펙티브 자바 - 4장(2/2), 5장(1/2) (아이템 19~27) (4) | 2025.08.02 |
| [Java] 이펙티브 자바 - 3장, 4장(1/2) (아이템 10~18) (2) | 2025.07.26 |