0Pricing
Java Academy · レッスン

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フィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. 上限付きワイルドカード
  2. 下限付きワイルドカード
  3. PECS 原則
  4. API でのワイルドカード
← Java Academyに戻る