0Pricing
Java Academy · レッスン

API でのワイルドカード

柔軟なジェネリックメソッドを設計します。

「API でのワイルドカード」はCoddyKit上の無料Java Academyレッスンです。 これはレッスン4/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはJava Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Java Academyコースには全4レッスンが含まれています。

柔軟なメソッドを設計する

ワイルドカードは、主にAPI の設計者のためのツールです。適切に配置したワイルドカードにより、呼び出し側はキャストなしでより多くの型を渡せます。

目標は、呼び出し側には最大限の柔軟性を提供し、内部では完全な型安全性を維持することです。

import java.util.List;

public class Main {
    static double total(List<? extends Number> items) {
        double sum = 0;
        for (Number n : items) sum += n.doubleValue();
        return sum;
    }
    public static void main(String[] args) {
        System.out.println(total(List.of(1, 2, 3)));
        System.out.println(total(List.of(1.5, 2.5)));
    }
}

非境界ワイルドカード

List<?> は非境界ワイルドカードで、未知の型のリストを意味します。

要素型をメソッドで考慮する必要がない場合に使用します。たとえば、要素数を数えたり、空かどうかを確認したりする場合です。

import java.util.List;

public class Main {
    static int size(List<?> any) {
        return any.size();
    }
    public static void main(String[] args) {
        System.out.println(size(List.of("a", "b")));
        System.out.println(size(List.of(1, 2, 3)));
    }
}

List<?> と List<Object> の違い

これらは異なるものです。List<Object> が受け入れるのは、Object として宣言されたリストだけです。一方、List<?> は任意の型のリストを受け入れます。

List<String> は List<?> パラメータには渡せますが、List<Object> パラメータには渡せません。

import java.util.List;

public class Main {
    static void printAll(List<?> list) {
        for (Object o : list) System.out.println(o);
    }
    public static void main(String[] args) {
        List<String> names = List.of("Ada", "Linus");
        printAll(names); // works; List<Object> would reject this
    }
}

型パラメータのほうが明確な場合

要素型を複数回参照する必要がある場合は、ワイルドカードよりも名前付き型パラメータのほうが読みやすくなります。

void swap(List<?> l, int i, int j)(非公開のヘルパーが必要)と、すっきりしたジェネリック版を比較してみてください。

import java.util.ArrayList;
import java.util.List;

public class Main {
    static <T> void swap(List<T> list, int i, int j) {
        T tmp = list.get(i);
        list.set(i, list.get(j));
        list.set(j, tmp);
    }
    public static void main(String[] args) {
        List<String> list = new ArrayList<>(List.of("a", "b", "c"));
        swap(list, 0, 2);
        System.out.println(list);
    }
}

ヘルパーによる型捕捉のテクニック

公開 API で List<?> を使う必要がある場合は、非公開のジェネリックヘルパーに処理を委譲して、ワイルドカードを捕捉できます。

ヘルパーで型に T という名前を付けることで、リストに書き戻せるようになります。

import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void reverse(List<?> list) { reverseHelper(list); }
    private static <T> void reverseHelper(List<T> list) {
        for (int i = 0, j = list.size() - 1; i < j; i++, j--) {
            T tmp = list.get(i);
            list.set(i, list.get(j));
            list.set(j, tmp);
        }
    }
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>(List.of(1, 2, 3, 4));
        reverse(list);
        System.out.println(list);
    }
}

実際の API でのプロデューサーパラメータ

互換性のある任意のソースから要素を取得する addAll を設計するには、ソースに上限を指定します。

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

public class Main {
    static <T> void addAll(List<T> target, Collection<? extends T> source) {
        for (T t : source) target.add(t);
    }
    public static void main(String[] args) {
        List<Number> nums = new ArrayList<>();
        addAll(nums, List.of(1, 2, 3));
        System.out.println(nums);
    }
}

実際の API でのコンシューマーパラメータ

結果を書き込むシンク API では、呼び出し側がより広い型のコンテナを渡せるように、宛先に下限を指定します。

import java.util.ArrayList;
import java.util.List;

public class Main {
    static <T> void repeat(T value, int times, List<? super T> sink) {
        for (int i = 0; i < times; i++) sink.add(value);
    }
    public static void main(String[] args) {
        List<Object> out = new ArrayList<>();
        repeat("hi", 3, out);
        System.out.println(out);
    }
}

ネストしたジェネリクスとワイルドカード

ネストしたジェネリクスでは、ワイルドカードが必要になることがよくあります。任意の種類のリストを要素として持つリストを受け取るメソッドでは、List<? extends List<?>> を使用します。

import java.util.List;

public class Main {
    static int countAll(List<? extends List<?>> lists) {
        int total = 0;
        for (List<?> inner : lists) total += inner.size();
        return total;
    }
    public static void main(String[] args) {
        System.out.println(countAll(List.of(List.of(1, 2), List.of("a", "b", "c"))));
    }
}

過剰なジェネリック化を避ける

すべてのメソッドにワイルドカードが必要なわけではありません。呼び出し側が常に1つの型だけを渡すのであれば、通常のパラメータのほうが明確です。

あらかじめ追加するのではなく、不変性によって実際の呼び出し側が妨げられていることを確認してからワイルドカードを追加してください。

import java.util.List;

public class Main {
    // Simple and clear; no wildcard needed for this internal use
    static String joinStrings(List<String> parts) {
        return String.join(", ", parts);
    }
    public static void main(String[] args) {
        System.out.println(joinStrings(List.of("a", "b", "c")));
    }
}

ワイルドカードの意図を文書化する

ワイルドカードは意図を示します。? extends T は「これから読み取るだけ」であることを読み手に伝え、? super T は「これに書き込むだけ」であることを示します。

適切なワイルドカードを選ぶことは、契約も強制できる自己文書化コードの一種です。

import java.util.ArrayList;
import java.util.List;

public class Main {
    // src: read-only producer; dest: write-only consumer
    static <T> void transfer(List<? extends T> src, List<? super T> dest) {
        for (T t : src) dest.add(t);
    }
    public static void main(String[] args) {
        List<Number> dest = new ArrayList<>();
        transfer(List.of(1, 2), dest);
        System.out.println(dest);
    }
}

ジェネリックなスタック API

Bloch の有名なスタックの例です。pushAll はプロデューサーから消費するため extends を使い、popAll はコンシューマーに生成するため super を使います。

import java.util.ArrayDeque;
import java.util.Collection;
import java.util.Deque;
import java.util.ArrayList;
import java.util.List;

public class Main {
    static class Stack<E> {
        private final Deque<E> items = new ArrayDeque<>();
        void pushAll(Collection<? extends E> src) { for (E e : src) items.push(e); }
        void popAll(Collection<? super E> dst) { while (!items.isEmpty()) dst.add(items.pop()); }
    }
    public static void main(String[] args) {
        Stack<Integer> s = new Stack<>();
        s.pushAll(List.of(1, 2, 3));
        List<Number> out = new ArrayList<>();
        s.popAll(out);
        System.out.println(out);
    }
}

理解度チェック

API 設計の感覚を試してみましょう。

まとめ

ワイルドカードを使った API の設計について学びました。

  • 型に依存しない読み取り専用メソッドには List<?> を使用します。
  • 柔軟性を高めるため、パラメータには PECS を適用します。
  • ワイルドカードを通じて書き込む必要がある場合は、非公開のヘルパーによる型捕捉のテクニックを使用します。
  • 戻り値の型ではワイルドカードを避け、過剰なジェネリック化も避けます。

ワイルドカードと PECS のコースはこれで完了です。

public class Main {
    public static void main(String[] args) {
        System.out.println("Wildcards in APIs course complete");
    }
}

よくある質問

「API でのワイルドカード」レッスンは無料ですか?

はい。「API でのワイルドカード」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Java Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Java Academyコースには全4レッスンが含まれています。

「API でのワイルドカード」で何を学びますか?

柔軟なジェネリックメソッドを設計します。 ブラウザで直接実行するハンズオンコードでJava Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

Java Academyを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのJava Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン4/4です。

「API でのワイルドカード」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このJava Academyレッスンでコードを書いて実行できますか?

はい。すべてのJava Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

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

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