---
title: なぜJavaのジェネリクスには 「PECS原則」が必要なのか？~Scala/Kotlinと⽐較して理解するワイルドカードの設計思想~
tags:  #java #jjug_ccc #ジェネリクス  
author: [シンプレクス株式会社](https://docswell.com/user/Simplex)
site: [Docswell](https://www.docswell.com/)
thumbnail: https://bcdn.docswell.com/page/LJ1YD8NXEG.jpg?width=480
description: JJUG CCC 2026 Springのセッション『なぜJavaのジェネリクスには 「PECS原則」が必要なのか？~Scala/Kotlinと⽐較して理解するワイルドカードの設計思想~』の公開資料です。
published: May 29, 26
canonical: https://docswell.com/s/Simplex/ZJW8RE-simplex_yamada01
---
# Page. 1

![Page Image](https://bcdn.docswell.com/page/LJ1YD8NXEG.jpg)

なぜJavaのジェネリクスには
「PECS原則」が必要なのか？
Scala/Kotlinと比較して理解するワイルドカードの設計思想
シンプレクス株式会社
山田 耀平
© 2026 Simplex Inc.
1


# Page. 2

![Page Image](https://bcdn.docswell.com/page/GJWGYZ4K72.jpg)

自己紹介
山田 耀平
2023年シンプレクス入社
主にFX取引システムのバックエンド開発に関わる
言語やフレームワークの仕様・特徴を調べることが好き
© 2026 Simplex Inc.
2


# Page. 3

![Page Image](https://bcdn.docswell.com/page/4EZLX12N73.jpg)

はじめに
生成AIやコーディングエージェントによって、開発にとどまらずレビューすら自動化されつつある中
ジェネリクスの読み方・書き方 を知ることがどう役に立つのか
個人的な考え
「曖昧さの少なさ」では、形式言語の方が自然言語より依然として優れている
型やメソッドシグネチャは、設計意図や役割を簡潔に伝えられる
人間とAIが設計意図を共有するうえでの共通言語としての役割
© 2026 Simplex Inc.
3


# Page. 4

![Page Image](https://bcdn.docswell.com/page/Y76W4LV97V.jpg)

シグネチャから意図を読む
void copy(List&lt;? super T&gt; dest, List&lt;? extends T&gt; src)
dest は T を受け取れる変数
src は T を取り出せる変数
T が src から dest へ流れる
今日のゴール
このシグネチャの ? super T と ? extends T が理解できるようになる
Kotlin/Scalaでは同じことを表現するとどうなるのかを見る
なぜPECSという原則が必要になるのか考える
© 2026 Simplex Inc.
4


# Page. 5

![Page Image](https://bcdn.docswell.com/page/G75MQ1DD74.jpg)

Javaのジェネリクス: 基本おさらい
型引数を使って、型をパラメータ化する
コンパイル時に型チェックできる
利用側のキャストを減らせる
List&lt;String&gt; names = new ArrayList&lt;&gt;();
names.add(&quot;Alice&quot;);
String name = names.get(0); // キャスト不要
© 2026 Simplex Inc.
5


# Page. 6

![Page Image](https://bcdn.docswell.com/page/9J29P9MMER.jpg)

型引数は「実行時の型」ではない
Javaのジェネリクスは erasure により実装されている
型引数は主にコンパイル時の検査に使われ、実行時には除去（erase）される
実行時に List&lt;String&gt; と List&lt;Integer&gt; が同じクラスとして扱われる
List&lt;String&gt; strings = new ArrayList&lt;&gt;();
List&lt;Integer&gt; integers = new ArrayList&lt;&gt;();
System.out.println(strings.getClass() == integers.getClass()); // true
© 2026 Simplex Inc.
6


# Page. 7

![Page Image](https://bcdn.docswell.com/page/DEY454XPJM.jpg)

重要: List&lt;String&gt; は List&lt;Object&gt; ではない
String string = &quot;Bob&quot;;
Object object = string; // エラーにはならない
List&lt;String&gt; strings = new ArrayList&lt;&gt;();
List&lt;Object&gt; objects = strings; // コンパイルエラー
String は Object のサブタイプ
しかし List&lt;String&gt; は List&lt;Object&gt; のサブタイプではない
© 2026 Simplex Inc.
7


# Page. 8

![Page Image](https://bcdn.docswell.com/page/VJNYNY1M78.jpg)

もし List&lt;String&gt; を List&lt;Object&gt; として扱えたら
List&lt;String&gt; strings = new ArrayList&lt;&gt;();
List&lt;Object&gt; objects = strings; // もしOKだったら
objects.add(123);
String s = strings.get(0); // Stringとして読めなくなる
List&lt;Object&gt; は Object であれば何でも（当然 Integer も）追加できる
List&lt;String&gt; には Integer は追加できない
→ List&lt;String&gt; は List&lt;Object&gt; として扱うことができない
© 2026 Simplex Inc.
8


# Page. 9

![Page Image](https://bcdn.docswell.com/page/YE9PRP5WJ3.jpg)

Javaで型の幅を広げたい場面
double sum(List&lt;Number&gt; numbers) {
double total = 0;
for (Number n : numbers) {
total += n.doubleValue();
}
return total;
}
List&lt;Integer&gt; ints = List.of(1, 2, 3);
sum(ints); // コンパイルエラー
sum はリストから値を読むだけ
Integer は Number として扱える
しかし List&lt;Integer&gt; は List&lt;Number&gt; として扱うことができない
© 2026 Simplex Inc.
9


# Page. 10

![Page Image](https://bcdn.docswell.com/page/GE8DWDYRED.jpg)

ワイルドカードを用いて扱う型の幅を広げる
double sum(List&lt;? extends Number&gt; numbers) {
double total = 0;
for (Number n : numbers) {
total += n.doubleValue();
}
return total;
}
List&lt;Integer&gt; ints = List.of(1, 2, 3);
sum(ints); // OK
? extends Number
「要素型は Number のサブタイプの何か」
取り出した値は Number として扱える
© 2026 Simplex Inc.
10


# Page. 11

![Page Image](https://bcdn.docswell.com/page/LELMNM427R.jpg)

? extends T の読み方
List&lt;? extends Number&gt; xs;
List&lt;Integer&gt; かもしれない
List&lt;Double&gt; かもしれない
List&lt;BigDecimal&gt; かもしれない
少なくとも、取り出した値は Number として扱える
上限境界: unknown type extends Number
© 2026 Simplex Inc.
11


# Page. 12

![Page Image](https://bcdn.docswell.com/page/4JMYXYK9JW.jpg)

? extends T はProducer
List&lt;? extends Number&gt; xs = new ArrayList&lt;Integer&gt;(List.of(1, 2, 3));
Number n = xs.get(0); // OK
xs.add(1);
// NG
xs は Number を生産する
つまり、取り出す側から見るとProducer
ただし、実体は List&lt;Double&gt; かもしれない
具体的な要素型は不明なので追加できない
© 2026 Simplex Inc.
12


# Page. 13

![Page Image](https://bcdn.docswell.com/page/PJR9N9X979.jpg)

書き込み先の型の幅も広げたい
void addSamples(List&lt;Integer&gt; dest) {
dest.add(1);
dest.add(2);
}
List&lt;Number&gt; numbers = new ArrayList&lt;&gt;();
addSamples(numbers); // コンパイルエラー
List&lt;Number&gt; には Integer を追加できる
しかし List&lt;Integer&gt; としては受け取れない
© 2026 Simplex Inc.
13


# Page. 14

![Page Image](https://bcdn.docswell.com/page/PEXQNQ63JX.jpg)

そこでワイルドカード
void addSamples(List&lt;? super Integer&gt; dest) {
dest.add(1);
dest.add(2);
}
List&lt;Number&gt; numbers = new ArrayList&lt;&gt;();
addSamples(numbers); // OK
? super Integer
「要素型は Integer のスーパータイプの何か」
Integer は安全に書き込める
© 2026 Simplex Inc.
14


# Page. 15

![Page Image](https://bcdn.docswell.com/page/3EK9N91NED.jpg)

? super T の読み方
List&lt;? super Integer&gt; xs;
List&lt;Integer&gt; かもしれない
List&lt;Number&gt; かもしれない
List&lt;Object&gt; かもしれない
少なくとも、 Integer は安全に追加できる
下限境界: unknown type super Integer
© 2026 Simplex Inc.
15


# Page. 16

![Page Image](https://bcdn.docswell.com/page/L73WVWQZ75.jpg)

? super T はConsumer
List&lt;? super Integer&gt; xs = new ArrayList&lt;Number&gt;();
xs.add(1);
// OK
Object value = xs.get(0); // OK
Integer i = xs.get(0);
// NG
xs は Integer を消費できる
つまり、入れる側から見るとConsumer
実体は List&lt;Object&gt; かもしれない
具体的な要素型は不明なので、読むときは Object まで広がる
© 2026 Simplex Inc.
16


# Page. 17

![Page Image](https://bcdn.docswell.com/page/87DK8K94JG.jpg)

PECS原則
Producer Extends, Consumer Super
役割
使う型
できること
できないこと
Producer
? extends T
T として読む
T を書く
Consumer
? super T
T を書く
T として読む
読み書き両方なら、ワイルドカードではなく T
© 2026 Simplex Inc.
17


# Page. 18

![Page Image](https://bcdn.docswell.com/page/VJPK8KZVE8.jpg)

標準API: Collections.copy
public static &lt;T&gt; void copy(
List&lt;? super T&gt; dest,
List&lt;? extends T&gt; src) {
...
}
List&lt;? extends T&gt; src は値を取り出すProducer
List&lt;? super T&gt; dest は値を受け取るConsumer
© 2026 Simplex Inc.
18


# Page. 19

![Page Image](https://bcdn.docswell.com/page/2EVVNVWREQ.jpg)

Kotlin/Scalaとの比較の前に
Javaではワイルドカードを使って受け入れ可能な型の幅を広げることができる
背景にある考え方が variance（変性）
A &lt;: B （例えば Integer &lt;: Number ）のとき:
用語
関係
Javaで対応する考え方（※）
共変
F&lt;A&gt; &lt;: F&lt;B&gt;
List&lt;Integer&gt; &lt;: List&lt;? extends Number&gt;
反変
F&lt;B&gt; &lt;: F&lt;A&gt;
List&lt;Number&gt; &lt;: List&lt;? super Integer&gt;
不変
どちらでもない
List&lt;Integer&gt; と List&lt;Number&gt; は別物
※Javaの List&lt;T&gt; 自体が共変/反変になるわけではないので、あくまでイメージ
© 2026 Simplex Inc.
19


# Page. 20

![Page Image](https://bcdn.docswell.com/page/57GLKLD6EL.jpg)

Kotlinとの比較
Kotlinでは、型パラメータのvarianceを 型の宣言側 で指定できる
この方式を declaration-site variance と呼ぶ
class Box&lt;out T&gt;(val value: T)
interface Serializer&lt;in T&gt; {
fun serialize(value: T): String
}
out T : 共変
in T : 反変
何も付けない: 不変
© 2026 Simplex Inc.
20


# Page. 21

![Page Image](https://bcdn.docswell.com/page/4EQYNY12JP.jpg)

Kotlin: out / in の例
val strings: Box&lt;String&gt; = Box(&quot;hello&quot;)
val chars: Box&lt;CharSequence&gt; = strings // OK
val anySerializer: Serializer&lt;Any&gt; = serializerForAny()
val stringSerializer: Serializer&lt;String&gt; = anySerializer // OK
Box&lt;String&gt; は Box&lt;CharSequence&gt; として扱える
Serializer&lt;Any&gt; は Serializer&lt;String&gt; として扱える
Javaの ? extends T / ? super T に近い
© 2026 Simplex Inc.
21


# Page. 22

![Page Image](https://bcdn.docswell.com/page/KJ4WGWRP71.jpg)

Kotlin: 位置制約
out T は、引数位置にそのまま置けない
interface BadBox&lt;out T&gt; {
fun get(): T
fun put(value: T) // コンパイルエラー
}
out T はProducerとして使う型
put(value: T) はConsumerとして使っているためコンパイルエラー
in T では逆に、戻り値位置にそのまま置けない
PECS原則を言語仕様として自然にサポート
© 2026 Simplex Inc.
22


# Page. 23

![Page Image](https://bcdn.docswell.com/page/LE1YDYQX7G.jpg)

Kotlin標準ライブラリで見る
interface List&lt;out E&gt; : Collection&lt;E&gt;
interface MutableList&lt;E&gt; : List&lt;E&gt;, MutableCollection&lt;E&gt;
Kotlinの List は読み取り専用ビューであり、共変
追加・更新できる MutableList は不変
© 2026 Simplex Inc.
23


# Page. 24

![Page Image](https://bcdn.docswell.com/page/GEWGYGMKJ2.jpg)

Kotlin: Type projection
Kotlinでは、Javaと同じようにジェネリクスの 使用時に out/in を指定 することもできる
この方式を use-site variance と呼ぶ
fun total(xs: MutableList&lt;out Number&gt;): Double {
return xs.sumOf { it.toDouble() }
}
fun addSamples(dest: MutableList&lt;in Int&gt;) {
dest.add(1)
dest.add(2)
}
out Number : Number として読み出せる
in Int : Int を書き込める
© 2026 Simplex Inc.
24


# Page. 25

![Page Image](https://bcdn.docswell.com/page/47ZLXLQNJ3.jpg)

Scalaとの比較
Scalaでも、宣言側でvarianceを指定できる（declaration-site variance）
final class Box[+A](val value: A)
trait Serializer[-A] {
def serialize(value: A): String
}
+A : 共変
-A : 反変
何も付けない: 不変
© 2026 Simplex Inc.
25


# Page. 26

![Page Image](https://bcdn.docswell.com/page/YJ6W4W69JV.jpg)

Scala: 共変と反変の例
val strings: Box[String] = new Box(&quot;hello&quot;)
val chars: Box[CharSequence] = strings // OK
val anySerializer: Serializer[Any] = ???
val stringSerializer: Serializer[String] = anySerializer // OK
Box[String] は Box[CharSequence] として扱える
Serializer[Any] は Serializer[String] として扱える
© 2026 Simplex Inc.
26


# Page. 27

![Page Image](https://bcdn.docswell.com/page/GJ5MQM6DJ4.jpg)

Scala: 型境界
Scalaにも、Javaの ? extends / ? super に相当する表現がある
? &lt;: T // 上限境界
? &gt;: T // 下限境界
? &lt;: T : 上限境界で、Javaの ? extends T に近い
? &gt;: T : 下限境界で、Javaの ? super T に近い
※ Scala 3のワイルドカード記法で、Scala 2では _ &lt;: T / _ &gt;: T
© 2026 Simplex Inc.
27


# Page. 28

![Page Image](https://bcdn.docswell.com/page/9E29P9NM7R.jpg)

PECSはどこに現れるか
言語
主な表現場所
補助的な表現
Java
利用箇所の ? extends / ? super
-
Kotlin
宣言側の out T / in T
利用箇所の out T / in T
Scala
宣言側の +T / -T
利用箇所の ? &lt;: T / ? &gt;: T
Kotlin/Scalaでは、PECS的な制約が型宣言に組み込まれやすい
Javaでは、API設計者が利用箇所のワイルドカードとして表現する
どちらも同じ型安全性の問題を、違う場所で表現している
© 2026 Simplex Inc.
28


# Page. 29

![Page Image](https://bcdn.docswell.com/page/D7Y454PPEM.jpg)

なぜJavaはuse-site varianceだけなのか
Java Genericsは、既存の非ジェネリックなCollections APIとの互換性を保って導入された
既存のコレクションAPIを壊さずにジェネリクス化する必要があった
List&lt;E&gt; は読み書きの両方を持つため、型そのものは不変にする
Producer/Consumerの役割は、メソッドごとのワイルドカードで表す
PECSは、API設計者がその役割を選ぶための指針
© 2026 Simplex Inc.
29


# Page. 30

![Page Image](https://bcdn.docswell.com/page/VENYNY5MJ8.jpg)

クロージング
Javaのジェネリクスは不変が基本
? extends はProducerを柔軟に受け取るための仕組み
? super はConsumerを柔軟に受け取るための仕組み
PECSはJavaのuse-site varianceを使いこなすための読み方
Kotlin/Scalaと比べると、Javaは「役割を利用箇所に書く」設計
© 2026 Simplex Inc.
30


# Page. 31

![Page Image](https://bcdn.docswell.com/page/Y79PRPYWE3.jpg)

アンケート
セッションアンケート
© 2026 Simplex Inc.
全体アンケート
31


