$30 off During Our Annual Pro Sale. View Details »

Java ジェネリクス入門 2024

nagise
October 27, 2024

Java ジェネリクス入門 2024

JJUG CCC 2024 Fall で発表したジェネリクス入門セッションの資料です。
ジェネリクスの基本的な構文と、変性について解説し、Stream API のjavadocが雰囲気で理解できるようになるぐらいを目標にしています。

nagise

October 27, 2024
Tweet

More Decks by nagise

Other Decks in Programming

Transcript

  1. 富⼭県出⾝ 富⼭市在住 株式会社 クレディセゾン 株式会社 凪瀬アーキテクツ 主なジャンル •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
  2. • 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 ? ジェネリクス関連の歴史トピックス
  3. List<String> list = new ArrayList<String>(); list.add("甲"); list.add("⼄"); String s0 =

    list.get(0); Listのメソッドの定義 boolean add(E e) E get(int index) ジェネリクスの使⽤例
  4. List list = new ArrayList(); list.add("⼦"); list.add("丑"); Integer i =

    (Integer) list.get(0); //キャスト必須 実⾏時にClassCastException コンパイル時点でヒューマンエラー を検出できない ジェネリクスは単なるキャストのシンタックスシュガーではない 古代のJavaのコード(〜2004年 Java 5 より前)
  5. java.util.Collections クラスより リスト内に出現する指定された値を すべてほかの値に置き換えます。 public static <T> boolean replaceAll (List<T>

    list, T oldVal, T newVal) 3つの引数がList<T>型、 T型、 T型という関係性 メソッドスコープのジェネリクス
  6. ArrayList の iterator() はエンクロージング内部クラス Itr を返す public Iterator<E> iterator() {

    return new Itr(); } 内部クラス Itr の宣⾔はこんな感じ。ArrayListの <E> を Iterator<E> としてバインドして使っている public class ArrayList<E> /** いろいろ略 */ { private class Itr implements Iterator<E> { … } } インスタンスに紐づく内部クラスへの波及 ArrayList<String> Itr implements <String> 発展的内容
  7. public class Syntax<T> implements Iterable<String> { public <X> void hoge(X

    x) { List<X> list = new ArrayList<X>(); list.add(x); } @Override public Iterator<String> iterator() { return null; } } 似て非なる<>を色分けしてみました ⽂法の話
  8. 型変数の宣⾔ class Hoge<T>{} // インスタンススコープ public <T> void hoge() {}

    // メソッドスコープ 型変数のバインド new ArrayList<String>(); // new でのバインド class StringList extends ArrayList<String> {} // 継承でのバインド Collections.<String> replaceAll(list, "hoge", "piyo"); // メソッドでのバインド パラメータ化された型 (parameterized type) List<String> list; 3種の<>
  9. ジェネリッククラスの例 java.util.ArrayList より 宣⾔側 public class ArrayList<E> /*略*/ {} 利⽤側でバインド

    List<String> list = new ArrayList<String>(); 推論 List<String> list = new ArrayList<>(); 型変数の宣⾔とバインド
  10. ジェネリックメソッドの例 java.util.Collections より 宣⾔側 public static <T> boolean replaceAll (List<T>

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

    singletonMap(K key, V value) 利⽤側 Collections.<String, Integer>singletonMap("hoge", 123); 推論 Collections.singletonMap("hoge", 123); 型変数の宣⾔とバインド
  12. インスタンススコープとメソッドスコープ併⽤の例 java.util.Optional<T> 宣⾔側 public <U> Optional<T> map(Function<? super T, ?

    extends U> mapper) 利⽤側 Optional<Integer> optI = Optional.<Integer>of(123); Optional<String> optS = optI.<String>map( (Integer i) -> i.toString()); 推論 var optI = Optional.of(123); var optS = optI.map( (i) -> i.toString()); 型変数の宣⾔とバインド 発展的内容
  13. T 型のオブジェクト x に関して真となる属性を q(x) とする。 このとき S が T

    の派⽣型であれば、 S 型のオブジェクト y について q(y) が真となる。 端的に⾔えば、親クラスは⼦クラスで代替できる。 ⼦クラスは親クラスの機能を全て代替できなけれ ばならない。 リスコフの置換原則 計算機科学博⼠ バーバラ・リスコフ 1939年〜
  14. Javaは変数の代⼊は共変(covariant)なので Integer integer = Integer.valueOf(123456); Number number = integer; といったように、親の型の変数に⼦の型の変数を代⼊することができる

    逆は無条件ではできなくて明⽰的なキャストが必要、 かつ実⾏時例外も出る Number number = Integer.valueOf(123456); Integer integer = (Integer) number;// 常に成功するとは⾔えない Javaの変数は基本的に共変
  15. このパターンはOK ArrayList<String> arrayList = new ArrayList<>(); List<String> list = arrayList;

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

    arrayList; このパターンはNG 型変数部分は⾮変(invariant) List<Integer> integerList = List.of(123); List<Number> numberList = integerList; // NG §4.10. Subtyping パラメタライズドタイプと共変・⾮変
  17. Javaの配列は共変 粉ミルク[] 粉ミルクストック = new 粉ミルク[] {”はいはい”, ”つよいこ”} 飲料[] 飲料ストック

    = 粉ミルクストック; // コンパイルが通る 飲料ストック[0] = ”⼭崎”; コンパイルエラーは出ないが、実⾏時にはArrayStoreException 実⾏時に型を確認するアプローチ 配列の共変
  18. public void のどを潤す(Supplier<飲料> ⾃動販売機, Consumer<飲料> ⼈) { 飲料 drink =

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

    // NG パラメタライズドタイプで共変を使いたいケース Supplier<飲料> Supplier<コーラ> コーラだけの⾃販 機でも別にいいん だけどなあ……
  20. 普通の⾃動販売機A 飲料 get() void 補充(飲料) コーラ専⽤の⾃動販売機B コーラ get() void 補充(コーラ)

    共変にできない理由を振り返る リスコフの置 換原則違反! でもそこ使わ ないじゃん B extends A
  21. <? extends 飲料> を表す型 コンパイル時の型の推論にだけ現れ、単体で宣⾔することはできない §5.1.10. Capture Conversion Capture というトリック

    Capture <? extends 飲料> キャスト可 飲料 キャプチャ変換 <飲料> <コーラ> ※いろいろと⽅便です
  22. List<? extends 飲料> 飲み物リスト 飲料 x = 飲み物リスト.getFirst(); // OK

    飲み物リスト.add(コーラ); // NG! Capture というトリック 出⼒(戻り値)にだけ使えて ⼊⼒(引数)には使えない Capture <? extends 飲料> キャスト可 飲料 キャスト不可 飲料
  23. public void のどを潤す(Supplier<? extends 飲料> ⾃動販売機, Consumer<飲料> ⼈) { 飲料

    drink = ⾃動販売機.get(); // Capture<? extends 飲料> から 飲料 へのキャスト ⼈.accept (drink); } Captureで共変 Consumer<飲料> Supplier<飲料> Supplier<コーラ> どっちも OK!
  24. List<? super 飲料> 飲み物リスト 飲料 x = 飲み物リスト.getFirst(); // NG!

    飲み物リスト.add(コーラ); // OK! 反変のCapture ⼊⼒(引数)にだけ使えて 出⼒(戻り値)には使えない 何に使うの? Capture <? super 飲料> キャストはObjectにだけ Object <飲料> <Object> キャプチャ変換
  25. public void 消費する(Supplier<飲料> ⾃動販売機, Consumer<? super 飲料> 消費者) { 飲み物

    drink = ⾃動販売機.get(); 消費者.accept (drink); } 反変で消費する Supplier<飲料> Consumer<飲料> 飲み物が消費できればなんでも可 Consumer<Object>
  26. • Java 5 : ジェネリックメソッドの型推論 • Java 7 : ダイヤモンド演算⼦

    • Java 8 : ラムダ式の引数の型推論 • Java 10 : ローカル変数宣⾔の型推論 var Javaの⾔語仕様的には型推論が⽣じるところが複数ある • Java21 : Record Pattern の型推論 new! (JEP 440) Javaの型推論四天王
  27. public class Hoge<T extends X> 型変数を宣⾔する際に型変数Tの上限境界を指定することが出来る。 ここでは extends は使えるが super

    は使えない。 パラメタライズドタイプのワイルドカードと紛らわしい public class Hoge<T extends X & Cloneable> & を⽤いて interface を implements していることを表すこともできる 型変数の境界
  28. ⼤雑把には • 同じクラス • 同じ名前 • 引数のイレイジャが同じ オーバーライド等価という概念 override-equivalent (§8.4.2.

    Method Signature) void hoge(List<String> list) void hoge(List<Integer> list) はイレイジャが同じ |List| になるので共存できない Javaのメソッド シグネチャ
  29. イレイジャの誤解で実⾏時にはなんでも消えるイメージがあるがそうで もない public static <T extends Number> void hoge(T t)

    メソッドをコンパイルしてclassファイルを作り、 classpathにこのclassを置いて、hogeメソッドを利⽤しようとすれば TがNumberでなければコンパイルエラーになる ジェネリクスとリフレクション classファイル <T extends Number> <T extends Number>
  30. ジェネリクス関連の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) 未来のジェネリクス
  31. Java / Java VM のリファクタリングをする巨⼤プロジェクト Value Type を導⼊してVMの効率をアップする⽬論み 既存のものも含め Value

    Type / 参照型 に整理 そのどちらでも同様にジェネリクスを適⽤ Codes like a class, works like an int. Project Valhalla
  32. Valhalla 関連 JEP Javaの参照型を強化し、nullがある型かどうか表現できるように する。 型変数にもnull許容 / null制限 を表現できるように拡張する •

    T ! - null 制限型 • T ? - null 許容型 • Box<String!> - null制限型のパラメタライズドタイプ • Box<String?> - null許容型のパラメタライズドタイプ JEP draft: Null-Restricted and Nullable Types (Preview)
  33. Radu Grigore (2016). Java Generics are Turing Complete 絶対にIDEに貼り付けてはいけない。 絶対にバージョン管理システムに

    コミットしてはいけない。 絶対にCIを回してはいけない。 Javaのジェネリクスはチューリング完全? コードなコピペそ
  34. 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