Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Java ジェネリクス入門 2024

nagise
October 27, 2024

Java ジェネリクス入門 2024

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

nagise

October 27, 2024

Transcript

60 slides
  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

More Decks by nagise

Other Decks in Programming