PECS 原則
Producer Extends、Consumer Super です。
「PECS 原則」はCoddyKit上の無料Java Academyレッスンです。 これはレッスン3/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはJava Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Java Academyコースには全4レッスンが含まれています。
PECS が表すもの
PECS は Producer Extends, Consumer Super を表します。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 を完全に適用したマージ
ここまでの内容をまとめると、2つのプロデューサーから読み取り、1つのコンシューマーに書き込むマージ処理になります。
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 の原則について学びました。
- Producer Extends:読み取り専用のパラメータには
? extends Tを使用します。 - Consumer Super:書き込み専用のパラメータには
? super Tを使用します。 - 両方の役割を持つパラメータには、通常の型パラメータを使用します。
- 戻り値の型ではワイルドカードを避けます。
次は、これらの考え方を実際の API 設計に適用します。
public class Main {
public static void main(String[] args) {
System.out.println("PECS recap: Producer Extends, Consumer Super");
}
}よくある質問
「PECS 原則」レッスンは無料ですか?
はい。「PECS 原則」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Java Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Java Academyコースには全4レッスンが含まれています。
「PECS 原則」で何を学びますか?
Producer Extends、Consumer Super です。 ブラウザで直接実行するハンズオンコードでJava Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
Java Academyを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのJava Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン3/4です。
「PECS 原則」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このJava Academyレッスンでコードを書いて実行できますか?
はい。すべてのJava Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。