Slide 1

Slide 1 text

Java ジェネリクス⼊⾨ 2024 株式会社クレディセゾン テクノロジーセンター なぎせゆうき @nagise

Slide 2

Slide 2 text

2 Good morning ! #jjug_ccc_a 2024/10/27 10:00 〜 10:45

Slide 3

Slide 3 text

富⼭県出⾝ 富⼭市在住 株式会社 クレディセゾン 株式会社 凪瀬アーキテクツ 主なジャンル ●Java⾔語仕様 ○ https://docs.oracle.com/javase/specs/index.html ●書籍 Effective Java の解説ポッドキャスト Tsundokanai Radio ○ https://podcasters.spotify.com/pod/show/dokanai/ ●Javaのジェネリクス ●⽇本の旧暦のライブラリ(開発中) ●Blog : プログラマーの脳みそ ●Burikaigi (富⼭で1⽉末~2⽉頭で⾏われるカンファレンス) ⾃⼰紹介 なぎせゆうき X (Twitter) @nagise

Slide 4

Slide 4 text

⽬標 ● Stream API のドキュメントの意味が雰囲気で分かるぐらい ● 主にAPIを利⽤するポジションの⼈を想定 範囲外 ● ジェネリクスを⽤いたクラス設計・メソッド設計には踏み込まない 今⽇の⽬標

Slide 5

Slide 5 text

● 1973年 ML⾔語 によってジェネリックプログラミングが開拓 ● 1977年 Ada に搭載 ● 1993年 C++ の STL (Standard Template Library) がANSI/ISO委員会に提出 ● 1995年 Java 登場 ○ 当初から検討はされていたがリリースに間に合わず⾒送り ● 2002年 五⼗嵐淳 On Variance-Based Subtyping for Parametric Types ● 2003年 Scala 登場 ● 2004年 Java 5 にてジェネリクスが搭載 ● 2005年 C# バージョン 2.0 でジェネリクス搭載 ● 2011年 Kotlin 登場 ● 2011年 Java 7 ダイヤモンド演算⼦が追加 ● 2014年 Java 8 ラムダ式が追加 ● 2018年 Java 10 varによる変数宣⾔ ● 20XX年 Project Valhalla ? ジェネリクス関連の歴史トピックス

Slide 6

Slide 6 text

List list = new ArrayList(); list.add("甲"); list.add("⼄"); String s0 = list.get(0); Listのメソッドの定義 boolean add(E e) E get(int index) ジェネリクスの使⽤例

Slide 7

Slide 7 text

List list = new ArrayList(); list.add("⼦"); list.add("丑"); Integer i = (Integer) list.get(0); //キャスト必須 実⾏時にClassCastException コンパイル時点でヒューマンエラー を検出できない ジェネリクスは単なるキャストのシンタックスシュガーではない 古代のJavaのコード(〜2004年 Java 5 より前)

Slide 8

Slide 8 text

Java 5 より前の時代のソースコードもコンパイルできるように するため List list = new ArrayList(); ただし、現代のIDEだとおおよそ警告が出るハズ 基本的に避けるべき §4.8. Raw Types raw型

Slide 9

Slide 9 text

型変数 (§4.4. Type Variables) 型そのものを変数に⼊れて扱う ListのjavadocではList 型変数名は慣習として アルファベット1⽂字が多い あるいは⼤⽂字スネークケース 標準APIでも java.util にPrimitiveIterator といった利⽤例もある。⾔語仕様的には⽇本語名でも可(§3.8. Identifiers) 型変数

Slide 10

Slide 10 text

● ある範囲で区切ってIN/OUTの型の 関係性を型変数で抽象化して表す ● 「型が違うけど同じような処理」を 共通化することができる 簡単なメソッドスコープのジェネリクス から解説をします 超概論ジェネリクス

Slide 11

Slide 11 text

java.util.Collections クラスより リスト内に出現する指定された値を すべてほかの値に置き換えます。 public static boolean replaceAll (List list, T oldVal, T newVal) 3つの引数がList型、 T型、 T型という関係性 メソッドスコープのジェネリクス

Slide 12

Slide 12 text

java.util.Collections クラスより 指定されたオブジェクトだけを格納している 不変のセットを返します public static Set singleton(T o) 引数がT型で、戻り値がSet型という関係性 メソッドスコープのジェネリクス

Slide 13

Slide 13 text

java.util.Collections クラスより デフォルトの乱数発生の元を使用して、 指定されたリストの順序を無作為に入れ替えます。 public static void shuffle(List> list) 引数1つだけなので関連性を示す必要がない メソッドスコープのジェネリクス

Slide 14

Slide 14 text

ジェネリックメソッド ● メソッドの引数と戻り値の型の 関係を表す ● メソッドの複数の引数の型の関 係を表す ● このスコープの型変数はメソッ ド内だけで有効 メソッドスコープのジェネリクス

Slide 15

Slide 15 text

● 複数のメソッド間の引数・戻り値の 型の関係性 ● 公開されているフィールド ● 内部クラス new するタイミングで型変数の具体的 な型を指定する(型変数のバインド) インスタンス単位で別のバインド new ArrayList(); new ArrayList(); インスタンススコープのジェネリクス

Slide 16

Slide 16 text

java.util.ArrayListの例 public boolean add(E e) public E get(int index) 複数のメソッド間で型の関連性を表現している ジェネリックなインスタンスの例

Slide 17

Slide 17 text

ArrayList の iterator() はエンクロージング内部クラス Itr を返す public Iterator iterator() { return new Itr(); } 内部クラス Itr の宣⾔はこんな感じ。ArrayListの を Iterator としてバインドして使っている public class ArrayList /** いろいろ略 */ { private class Itr implements Iterator { … } } インスタンスに紐づく内部クラスへの波及 ArrayList Itr implements 発展的内容

Slide 18

Slide 18 text

public class Syntax implements Iterable { public void hoge(X x) { List list = new ArrayList(); list.add(x); } @Override public Iterator iterator() { return null; } } 似て非なる<>を色分けしてみました ⽂法の話

Slide 19

Slide 19 text

型変数の宣⾔ class Hoge{} // インスタンススコープ public void hoge() {} // メソッドスコープ 型変数のバインド new ArrayList(); // new でのバインド class StringList extends ArrayList {} // 継承でのバインド Collections. replaceAll(list, "hoge", "piyo"); // メソッドでのバインド パラメータ化された型 (parameterized type) List list; 3種の<>

Slide 20

Slide 20 text

ジェネリッククラスの例 java.util.ArrayList より 宣⾔側 public class ArrayList /*略*/ {} 利⽤側でバインド List list = new ArrayList(); 推論 List list = new ArrayList<>(); 型変数の宣⾔とバインド

Slide 21

Slide 21 text

継承によるバインド class StringList extends ArrayList 継承の際に型変数をバインドすることもできる class StringKeyMap extends HashMap 型変数の宣⾔とバインド

Slide 22

Slide 22 text

ジェネリックメソッドの例 java.util.Collections より 宣⾔側 public static boolean replaceAll (List list, T oldVal, T newVal) 利⽤側 Collections.replaceAll(list, "hoge", "piyo"); 推論 Collections.replaceAll(list, "hoge", "piyo"); 型変数の宣⾔とバインド

Slide 23

Slide 23 text

ジェネリックメソッド、複数型変数の例 java.util.Collections より 宣⾔側 public static Map singletonMap(K key, V value) 利⽤側 Collections.singletonMap("hoge", 123); 推論 Collections.singletonMap("hoge", 123); 型変数の宣⾔とバインド

Slide 24

Slide 24 text

インスタンススコープとメソッドスコープ併⽤の例 java.util.Optional 宣⾔側 public Optional map(Function super T, ? extends U> mapper) 利⽤側 Optional optI = Optional.of(123); Optional optS = optI.map( (Integer i) -> i.toString()); 推論 var optI = Optional.of(123); var optS = optI.map( (i) -> i.toString()); 型変数の宣⾔とバインド 発展的内容

Slide 25

Slide 25 text

「パラメータ化された型」§4.5. Parameterized Types 変数宣⾔時の型で出てくる<> List stringList = List.of("甲","⼄","丙"); パラメタライズドタイプではワイルドカードが使える List> someList; List extends Hoge> extendsList; List super Hoge> superList; 詳細は後ほど パラメタライズドタイプ

Slide 26

Slide 26 text

T 型のオブジェクト x に関して真となる属性を q(x) とする。 このとき S が T の派⽣型であれば、 S 型のオブジェクト y について q(y) が真となる。 端的に⾔えば、親クラスは⼦クラスで代替できる。 ⼦クラスは親クラスの機能を全て代替できなけれ ばならない。 リスコフの置換原則 計算機科学博⼠ バーバラ・リスコフ 1939年〜

Slide 27

Slide 27 text

Javaは変数の代⼊は共変(covariant)なので Integer integer = Integer.valueOf(123456); Number number = integer; といったように、親の型の変数に⼦の型の変数を代⼊することができる 逆は無条件ではできなくて明⽰的なキャストが必要、 かつ実⾏時例外も出る Number number = Integer.valueOf(123456); Integer integer = (Integer) number;// 常に成功するとは⾔えない Javaの変数は基本的に共変

Slide 28

Slide 28 text

このパターンはOK ArrayList arrayList = new ArrayList<>(); List list = arrayList; このパターンはNG List integerList = List.of(123); List numberList = integerList; // NG パラメタライズドタイプと共変・⾮変 なんでだろう? 型の不⼀致: List から List には変換できません

Slide 29

Slide 29 text

パラメタライズドタイプの型変数部分が共変だとダメな例 飲⽤ボトル<粉ミルク> 哺乳瓶 = new 飲⽤ボトル<粉ミルク>(); 飲⽤ボトル<飲料> ボトル = 哺乳瓶; // 実際はNG ボトル.add (new ウイスキー()); パラメタライズドタイプと共変・⾮変

Slide 30

Slide 30 text

このパターンはOK 型識別⼦部分は共変(covariant) ArrayList arrayList = new ArrayList<>(); List list = arrayList; このパターンはNG 型変数部分は⾮変(invariant) List integerList = List.of(123); List numberList = integerList; // NG §4.10. Subtyping パラメタライズドタイプと共変・⾮変

Slide 31

Slide 31 text

Javaの配列は共変 粉ミルク[] 粉ミルクストック = new 粉ミルク[] {”はいはい”, ”つよいこ”} 飲料[] 飲料ストック = 粉ミルクストック; // コンパイルが通る 飲料ストック[0] = ”⼭崎”; コンパイルエラーは出ないが、実⾏時にはArrayStoreException 実⾏時に型を確認するアプローチ 配列の共変

Slide 32

Slide 32 text

public void のどを潤す(Supplier<飲料> ⾃動販売機, Consumer<飲料> ⼈) { 飲料 drink = ⾃動販売機.get(); ⼈.accept (drink); } パラメタライズドタイプで共変を使いたいケース Supplier<飲料> Consumer<飲料>

Slide 33

Slide 33 text

Supplier<飲料> ⾃動販売機A; Supplier<コーラ> ⾃動販売機B; のどを潤す(⾃動販売機A, ⼈); // OK のどを潤す(⾃動販売機B, ⼈); // NG パラメタライズドタイプで共変を使いたいケース Supplier<飲料> Supplier<コーラ> コーラだけの⾃販 機でも別にいいん だけどなあ……

Slide 34

Slide 34 text

普通の⾃動販売機A 飲料 get() void 補充(飲料) コーラ専⽤の⾃動販売機B コーラ get() void 補充(コーラ) 共変にできない理由を振り返る リスコフの置 換原則違反! でもそこ使わ ないじゃん B extends A

Slide 35

Slide 35 text

extends 飲料> を表す型 コンパイル時の型の推論にだけ現れ、単体で宣⾔することはできない §5.1.10. Capture Conversion Capture というトリック Capture extends 飲料> キャスト可 飲料 キャプチャ変換 <飲料> <コーラ> ※いろいろと⽅便です

Slide 36

Slide 36 text

List extends 飲料> 飲み物リスト 飲料 x = 飲み物リスト.getFirst(); // OK 飲み物リスト.add(コーラ); // NG! Capture というトリック 出⼒(戻り値)にだけ使えて ⼊⼒(引数)には使えない Capture extends 飲料> キャスト可 飲料 キャスト不可 飲料

Slide 37

Slide 37 text

public void のどを潤す(Supplier extends 飲料> ⾃動販売機, Consumer<飲料> ⼈) { 飲料 drink = ⾃動販売機.get(); // Capture extends 飲料> から 飲料 へのキャスト ⼈.accept (drink); } Captureで共変 Consumer<飲料> Supplier<飲料> Supplier<コーラ> どっちも OK!

Slide 38

Slide 38 text

List super 飲料> 飲み物リスト 飲料 x = 飲み物リスト.getFirst(); // NG! 飲み物リスト.add(コーラ); // OK! 反変のCapture ⼊⼒(引数)にだけ使えて 出⼒(戻り値)には使えない 何に使うの? Capture super 飲料> キャストはObjectにだけ Object <飲料> キャプチャ変換

Slide 39

Slide 39 text

public void 消費する(Supplier<飲料> ⾃動販売機, Consumer super 飲料> 消費者) { 飲み物 drink = ⾃動販売機.get(); 消費者.accept (drink); } 反変で消費する Supplier<飲料> Consumer<飲料> 飲み物が消費できればなんでも可 Consumer

Slide 40

Slide 40 text

java.util.stream.Stream のメソッドには共変・反変のが多⽤さ れている mapに渡す関数は 「T を渡すと R を返す」 ことが出来る必要がある ⼊⼒はTより広くても良いし 出⼒はRより具体的でも良い Stream API のドキュメント

Slide 41

Slide 41 text

● Javaの型推論四天王 ● 型変数の境界 ● 再帰ジェネリクス ● イレイジャ ● ジェネリクスとリフレクション ● 未来のジェネリクス ● チューリング完全 おまけ

Slide 42

Slide 42 text

● Java 5 : ジェネリックメソッドの型推論 ● Java 7 : ダイヤモンド演算⼦ ● Java 8 : ラムダ式の引数の型推論 ● Java 10 : ローカル変数宣⾔の型推論 var Javaの⾔語仕様的には型推論が⽣じるところが複数ある ● Java21 : Record Pattern の型推論 new! (JEP 440) Javaの型推論四天王

Slide 43

Slide 43 text

public class Hoge 型変数を宣⾔する際に型変数Tの上限境界を指定することが出来る。 ここでは extends は使えるが super は使えない。 パラメタライズドタイプのワイルドカードと紛らわしい public class Hoge & を⽤いて interface を implements していることを表すこともできる 型変数の境界

Slide 44

Slide 44 text

enum のスーパークラス 趣旨としてはサブクラス⾃⾝の型 class Hoge extends Enum みたいな感じで継承でバインドされる ※実際には enum で宣⾔する 再帰ジェネリクス

Slide 45

Slide 45 text

Optional> desc = Hoge.ABC.describeConstable(); ポイントは親クラスEnumで定義されたメソッドなのに、 具象型Hogeが得られること GoFデザインパターンの Template Methodパターン などと相性がいい 再帰ジェネリクス

Slide 46

Slide 46 text

§4.6. Type Erasure イレイジャとは、型(パラメータ化された型や型変数を含む可能性があ る)から型(パラメータ化された型や型変数ではない)への写像である。 型Tのイレイジャを|T|と書く ⼤雑把に⾔えば List → |List| Map → |Map| イレイジャとは何か

Slide 47

Slide 47 text

⼤雑把には ● 同じクラス ● 同じ名前 ● 引数のイレイジャが同じ オーバーライド等価という概念 override-equivalent (§8.4.2. Method Signature) void hoge(List list) void hoge(List list) はイレイジャが同じ |List| になるので共存できない Javaのメソッド シグネチャ

Slide 48

Slide 48 text

イレイジャの誤解で実⾏時にはなんでも消えるイメージがあるがそうで もない public static void hoge(T t) メソッドをコンパイルしてclassファイルを作り、 classpathにこのclassを置いて、hogeメソッドを利⽤しようとすれば TがNumberでなければコンパイルエラーになる ジェネリクスとリフレクション classファイル

Slide 49

Slide 49 text

メソッドスコープの型変数の名前や上限境界を取得する例 Method[] methods = Sample.class.getMethods(); System.out.println(methods[0].getTypeParameters()[0].getName()); System.out.println(methods[0].getTypeParameters()[0].getBounds()[0]); 先の例だと “T” と class java.lang.Number が得られる ジェネリクスとリフレクション

Slide 50

Slide 50 text

java.lang.reflect.Method などでも、パラメタライズドタイプで 引数や戻り値の型を取得する機能がある。 classファイルにはメソッドやフィールドなどのシグネチャ部分 の型は パラメタライズドタイプで保存されている 実⾏時に取得することもできる ローカル変数はイレイジャ ジェネリクスとリフレクション

Slide 51

Slide 51 text

ジェネリクス関連のJEPはいくつか存在している ● JEP 218: Generics over Primitive Types (2014 / 2017 updated) ● JEP 300: Augment Use-Site Variance with Declaration-Site Defaults (2014 / 2016 updated) ● JEP 402: Enhanced Primitive Boxing (Preview) (2021 / 2024 updated) ● JEP draft 8261529: Universal Generics (Preview) (2021 / 2023 updated) ● JEP draft 8303099: Null-Restricted and Nullable Types (Preview) (2023 / 2024 updated) 未来のジェネリクス

Slide 52

Slide 52 text

2014年の古株のJEP Project Valhalla の管轄 プリミティブ型をジェネリクスで扱えるようにする 内容としては JEP 402 と被っていて 402 で進められていそう JEP 218: Generics over Primitive Types

Slide 53

Slide 53 text

更新が⽌まっているので活きているのかどうなのか Java の型変数は 利⽤側で変性を宣⾔する⽅式(use-site variance annotations) C#やKotlinは型変数の宣⾔側で変性を指定(declaration-site variance annotations) 宣⾔側でデフォルトの変性を指定できるようにしようという試み JEP 300: Augment Use-Site Variance with Declaration-Site Defaults

Slide 54

Slide 54 text

Java / Java VM のリファクタリングをする巨⼤プロジェクト Value Type を導⼊してVMの効率をアップする⽬論み 既存のものも含め Value Type / 参照型 に整理 そのどちらでも同様にジェネリクスを適⽤ Codes like a class, works like an int. Project Valhalla

Slide 55

Slide 55 text

Valhalla 関連 JEP Valhallaで導⼊予定のValue Typeもジェネリクスで⽤いること が出来るように。 Valhalla⾃体が検証中であるため、⽅針が⼆転三転。 最近、ステータスが Closed / Withdrawn になった。 後述のJEPに置き換わった模様 JEP draft: Universal Generics (Preview)

Slide 56

Slide 56 text

Valhalla 関連 JEP プリミティブ型を参照型っぽく使えるように。 この⼀環としてプリミティブ型を型変数に⽤いることができるよ うにする。 JEP 402: Enhanced Primitive Boxing (Preview) int i = 12; int iSize = i.SIZE; double iAsDouble = i.doubleValue(); Supplier iSupp = i::toString;

Slide 57

Slide 57 text

Valhalla 関連 JEP Javaの参照型を強化し、nullがある型かどうか表現できるように する。 型変数にもnull許容 / null制限 を表現できるように拡張する ● T ! - null 制限型 ● T ? - null 許容型 ● Box - null制限型のパラメタライズドタイプ ● Box - null許容型のパラメタライズドタイプ JEP draft: Null-Restricted and Nullable Types (Preview)

Slide 58

Slide 58 text

Radu Grigore (2016). Java Generics are Turing Complete 絶対にIDEに貼り付けてはいけない。 絶対にバージョン管理システムに コミットしてはいけない。 絶対にCIを回してはいけない。 Javaのジェネリクスはチューリング完全? コードなコピペそ

Slide 59

Slide 59 text

l Java⾔語仕様 l The Java® Language Specification Java SE 21 Edition https://docs.oracle.com/javase/specs/jls/se21/html/ l Java標準APIのjavadoc l Java® Platform, Standard Edition & Java Development Kit バージョン21 API仕様 https://docs.oracle.com/javase/jp/21/docs/api/ l JEP 各種 l https://openjdk.org/jeps/0 l Project Valhalla l https://openjdk.org/projects/valhalla/ l Radu Grigore (2016). Java Generics are Turing Complete l 五⼗嵐淳 (2002). On Variance-Based Subtyping for Parametric Types l @nagiseの過去の資料 l Project Valhalla 2023 https://nagise.hatenablog.jp/entry/2023/04/17/210648 l Java Generics Hell Advent Calendar 2017 https://adventar.org/calendars/2751 l OpenJDK の duke ギャラリー l https://wiki.openjdk.org/display/duke/Gallery l いらすとや l https://www.irasutoya.com/ 参考⽂献リスト Duke Duchess

Slide 60

Slide 60 text

Thank you !