PECS 원칙
생산자는 Extends, 소비자는 Super입니다
PECS 원칙은(는) CoddyKit의 무료 Java Academy 강의입니다. 이것은 4개 중 3번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 Java Academy 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. Java Academy 강의에는 총 4개의 강의가 포함되어 있습니다.
PECS가 의미하는 것
PECS는 “생산자는 상한, 소비자는 하한”을 의미합니다. Joshua Bloch의 『Effective Java』에서 소개된 기억법입니다.
- 매개변수가 T를 생산한다면(매개변수에서 읽는다면)
? extends T를 사용합니다. - 매개변수가 T를 소비한다면(매개변수에 쓴다면)
? super T를 사용합니다.
public class Main {
public static void main(String[] args) {
System.out.println("Producer Extends, Consumer Super");
}
}전형적인 copy 메서드
대표적인 PECS 예제는 copy 메서드입니다. 원본은 생산자이므로 ? extends T를 사용합니다. 대상은 소비자이므로 ? super T를 사용합니다.
import java.util.ArrayList;
import java.util.List;
public class Main {
static <T> void copy(List<? extends T> src, List<? super T> dest) {
for (T t : src) dest.add(t);
}
public static void main(String[] args) {
List<Integer> src = List.of(1, 2, 3);
List<Object> dest = new ArrayList<>();
copy(src, dest);
System.out.println(dest);
}
}두 와일드카드가 유연성을 높이는 이유
PECS 덕분에 copy는 다양한 형식 조합에서 작동합니다. 예를 들어 Integer 원본을 Number 또는 Object 대상에 복사할 수 있고, String 원본을 CharSequence 대상에 복사할 수 있습니다.
와일드카드가 없다면 형식이 정확히 일치해야 하므로 재사용성이 크게 떨어집니다.
import java.util.ArrayList;
import java.util.List;
public class Main {
static <T> void copy(List<? extends T> src, List<? super T> dest) {
for (T t : src) dest.add(t);
}
public static void main(String[] args) {
List<Integer> ints = List.of(10, 20);
List<Number> nums = new ArrayList<>();
copy(ints, nums);
System.out.println(nums);
}
}생산자 예제
컬렉션에서 읽기만 하는 메서드는 extends를 사용해야 합니다. 여기서는 생산자의 값을 합산합니다.
import java.util.List;
public class Main {
static double sum(List<? extends Number> producer) {
double total = 0;
for (Number n : producer) total += n.doubleValue();
return total;
}
public static void main(String[] args) {
System.out.println(sum(List.of(1, 2, 3)));
System.out.println(sum(List.of(1.1, 2.2)));
}
}소비자 예제
컬렉션에 쓰기만 하는 메서드는 super를 사용해야 합니다. 여기서는 소비자에게 값을 넣습니다.
import java.util.ArrayList;
import java.util.List;
public class Main {
static void pushDigits(List<? super Integer> consumer) {
for (int i = 0; i < 5; i++) consumer.add(i);
}
public static void main(String[] args) {
List<Number> nums = new ArrayList<>();
pushDigits(nums);
System.out.println(nums);
}
}두 역할을 모두 맡는 경우: 일반 형식 사용
매개변수가 생산자이면서 소비자라면 와일드카드를 사용하지 않아야 합니다.
같은 리스트에서 읽고 쓰는 메서드에는 정확한 형식 T가 필요합니다.
import java.util.ArrayList;
import java.util.List;
public class Main {
static <T> void rotate(List<T> list) {
if (list.isEmpty()) return;
T last = list.remove(list.size() - 1);
list.add(0, last);
}
public static void main(String[] args) {
List<Integer> list = new ArrayList<>(List.of(1, 2, 3));
rotate(list);
System.out.println(list);
}
}Comparator에서의 PECS
JDK는 PECS를 광범위하게 적용합니다. Collections.max는 Comparator<? super T>를 받습니다. 비교기가 비교를 위해 T 값을 소비하기 때문입니다.
import java.util.Comparator;
import java.util.List;
public class Main {
public static void main(String[] args) {
Comparator<Object> byHash = Comparator.comparingInt(Object::hashCode);
List<String> words = List.of("a", "bb", "ccc");
String max = java.util.Collections.max(words, byHash);
System.out.println(max);
}
}Stream.collect에서의 PECS
많은 함수형 인터페이스가 PECS를 따릅니다. Consumer<? super T>는 더 넓은 형식을 대상으로 작성된 처리기도 받아들일 수 있습니다.
import java.util.List;
import java.util.function.Consumer;
public class Main {
public static void main(String[] args) {
Consumer<Object> printer = o -> System.out.println("got: " + o);
List<String> items = List.of("x", "y");
items.forEach(printer); // forEach takes Consumer<? super T>
}
}Function의 생산자와 소비자
Function<? super T, ? extends R>는 PECS의 전체 형태입니다. 입력은 소비되고(super), 출력은 생산됩니다(extends).
따라서 이 변환은 더 넓은 입력을 받아 더 좁은 출력을 반환할 수 있습니다.
import java.util.function.Function;
public class Main {
static <T, R> R apply(Function<? super T, ? extends R> fn, T value) {
return fn.apply(value);
}
public static void main(String[] args) {
Function<Object, Integer> len = o -> o.toString().length();
System.out.println(apply(len, "hello"));
}
}반환 형식에서는 와일드카드 피하기
반환 형식에는 와일드카드를 사용하지 마십시오. 호출자도 와일드카드를 처리해야 하므로 복잡성이 밖으로 드러납니다.
구체적인 형식을 반환하고, 유연성이 필요한 매개변수에만 와일드카드를 사용하십시오.
import java.util.ArrayList;
import java.util.List;
public class Main {
// Good: concrete return type
static List<Number> doubled(List<? extends Number> src) {
List<Number> out = new ArrayList<>();
for (Number n : src) out.add(n.doubleValue() * 2);
return out;
}
public static void main(String[] args) {
System.out.println(doubled(List.of(1, 2, 3)));
}
}완전한 PECS 병합
지금까지의 내용을 합치면, 두 생산자에서 읽어 하나의 소비자에 병합하는 작업이 됩니다.
import java.util.ArrayList;
import java.util.List;
public class Main {
static <T> void merge(List<? extends T> a, List<? extends T> b, List<? super T> out) {
for (T t : a) out.add(t);
for (T t : b) out.add(t);
}
public static void main(String[] args) {
List<Integer> a = List.of(1, 2);
List<Integer> b = List.of(3, 4);
List<Number> out = new ArrayList<>();
merge(a, b, out);
System.out.println(out);
}
}빠른 확인
PECS 규칙을 적용해 보십시오.
복습
PECS 원칙을 배웠습니다.
- 생산자는 상한: 읽기 전용 매개변수에는
? extends T를 사용합니다. - 소비자는 하한: 쓰기 전용 매개변수에는
? super T를 사용합니다. - 두 역할을 모두 맡는 매개변수에는 일반 형식 매개변수를 사용합니다.
- 반환 형식에는 와일드카드를 사용하지 마십시오.
다음에는 이러한 개념을 실제 API 설계에 적용합니다.
public class Main {
public static void main(String[] args) {
System.out.println("PECS recap: Producer Extends, Consumer Super");
}
}자주 묻는 질문
“PECS 원칙” 강의는 무료인가요?
네 — “PECS 원칙” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 Java Academy 강의 전체를 잠금 해제할 수 있습니다. Java Academy 강의에는 총 4개의 강의가 포함되어 있습니다.
“PECS 원칙”에서 뭘 배우나요?
생산자는 Extends, 소비자는 Super입니다 브라우저에서 직접 실행하는 실습 코드로 Java Academy을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.
Java Academy을(를) 시작하는 데 경험이 필요한가요?
사전 경험은 필요하지 않습니다. CoddyKit의 Java Academy은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 3번째 강의입니다.
“PECS 원칙” 강의는 얼마나 걸리나요?
대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.
이 Java Academy 강의에서 코드를 작성하고 실행할 수 있나요?
네. 모든 Java Academy 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.