1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Java Gold

Last updated at Posted at 2024-06-23

enum P9

enum
特定の値を列挙するオブジェクトのこと

→ 列挙型の実体 = 定数、メソッドを持つクラス

注意点メモ

・明示的なインスタンス化❌
・アクセス:列挙型.クラス定数
・コンストラクタ、メソッド、変数定義可
・抽象メソッド、インタフェースのオーバーライド、実装可
・定数は書いた順番に管理

enum.java
enum Card {
    SPADES,    //Card.SPADES.ordinary(); -> 列挙型の位置を返す(0から始まる)
    CLUBS,
    DIAMONDS,
    HEARTS
}
 コンパイルすると...
final class Card extends java.lang.Enum<Card> {
    public static final Card SPADES = new Card();   //列挙した値を名前とするクラス定数に、インスタンス化した列挙型オブジェクトを格納する
    public static final Card CLUBS = new Card();
    public static final Card DIAMONDS = new Card();
    public static final Card HEARTS = new Card();

    public static Card[] values() {・・・・・}                //列挙した値すべて返す
    public static Card valueOf(java.lang.String) {・・・・・} //引数で指定した値を返す
}

Card.SPADES.ordinal()・・・列挙宣言時の位置を返す。0から始まる

抽象メソッドの利用.java
enum Unknown {
    // A, B  //コンパイルエラー(抽象メソッドをオーバーライドしていないから)
    A{ String method() { return "A"; } },
    B{ String method() { return "B"; } };
    abstract String method();
}

Java.lang.Object P17

ハッシュコード:Java実行環境がオブジェクトを識別する時に使う整数値

  • equals(), hashCode() をオーバーライドする時のルール
要件 説明
object.hashCode() == object.hashCode(); 同じオブジェクトに使っても、同じ整数値返る
object1.equals(object2) == true

object1.hashCode() == object2.hashCode()
equals()trueなら、hashCode()もtrue
object1.equals(object2) == false

object1.hashCode() == object2.hashCode() true,falseどっちでも
falseの方がパフォーマンス向上するかも
object.hashCode() == object.hashCode(); false
object1.equals(object2) == false

インタフェース P19

インタフェース
必要な操作をまとめたクラスの仕様書(取り決め)

  • 定数(public static final)宣言可
    →宣言時に初期化しないとコンパイルエラー
  • 抽象メソッド宣言=public abstract 自動付与(publicのみ)
  • static / default + 具象メソッド
    → 自動的にpublic、protectd❌
    → static private⭕️ / default private ❌
  • defaultメソッド
    → toString(), equals(), hashCode() 定義❌
    → 実装クラスでオーバーライド可能
  • 親インタフェースへアクセス
    → <親インタフェース名> . super . <メソッド名>
  • メンバへのアクセス
    → クラスが優先される
インタフェース内での定義.java
interface Foo {
    private static void p1() {
        System.out.println("Foo : p1()");
    }
    private void p2() {
        System.out.println("Foo : p2()");
    }
    static void methodA() {
        System.out.println("Foo : methodA()");
        p1();
        p2();   //コンパイルエラー
    }
    default void methodB() {
        System.out.println("Foo : methodB()");
        p1();
        p2();
    }
}

publc class MyClass implements Foo {}

public class Main {
    public static void main(String[] args) {
        MyClass obj = new MyClass();
        obj.methodA();    //コンパイルエラー 参照変数を使って、staticメソッドは呼び出せない
    }
}
interfaceの実装.java
interface A {
    void method(); //抽象メソッド
    void priority(); //抽象メソッド
}
interface X extends A { //サブインタフェース
    @Override 
    default void method() { System.out.println("X"); }
    @Override 
    default void priority() { System.out.println("X"); }
}
class Y impletents A { //実装クラス
    @Override 
    public void priority() { System.out.println("Y"); }
}

class Main extends Y implements X {
    @Override 
    public void method() { X.super.method(); }   //親インタフェース.super.メソッド
    Main main = new Main();
    main.priority(); //コンパイルエラーにはならない、Javaではクラスが優先される→Yクラスのpriority()が呼ばれる
}

ネストクラス P30

クラスの中に定義したクラス

  • 存在を外部から隠したいクラスをネスト
  • 外側のクラスのメンバの一つ
  • インタフェース、抽象クラスを定義できる
ネストクラス.java
    class Outer {
        class A {} //非staticクラス(インナークラス)→ ローカルクラス、匿名クラス
        static class B {} //staticクラス
    }

ネストクラスのルール

クラス ルール
staic / 非static 共通 ・外側のクラスと同名❌
・アクセス修飾子使える
・abstruct, final 使える
staticのみ ・(非)staticメンバもてる
・外側クラスのインスタンスメンバへアクセス❌
非staticのみ ・staticメンバもてない
・外側クラスのインスタンス / static変数へアクセス⭕️

ネストクラスへのアクセス

1. 別のクラスからアクセス

Main.java
class Outer {
    private int val1 = 100;
    private static int val2 = 200;
    class A {
        void method1() {
            System.out.println(val1);
            System.out.println(val2);
        }
        static void method2(){} //コンパイルエラー
    }
    static class B {
        void method1() {
            System.out.println(val1); //コンパイルエラー
            System.out.println(val2);
        }
        static void method2() {
            System.out.println(val1); //コンパイルエラー
            System.out.println(val2);
        }
    }
}
//外部クラス
public class Main {
    public staic void main(String[] args) {
        Outer.A a = new Outer().new A(); //非staticクラスのインスタンス化
        Outer.B b = new Outer.B(); //staticクラスのインスタンス化
    }
}

2. 外側のクラスからアクセス

Main.java
public class Main {
    class A {
        void methodA() {}
    }
    static class B {
        static void methodB() {}
    }
    public static void main(String[] args) {
        new Main().new A().methodA();
        new Main.B().methodB(); //new B().methodB() ⭕️
        Main.B.methodB(); //B.methodB() ⭕️
    }
}

ローカルクラスのルール

クラスのメソッド内に定義したクラス

  • アクセス修飾子 使えない
  • static 使えない
  • 外側のクラスのメンバにアクセスできる
  • 外側クラスのメソッドの引数、ローカル変数にアクセス→各変数はfinal
Main.java
class Outer {
    private static int a = 1;
    private int b = 2;
    void methodOuter(final int c, int d) {
        final int e = 5; int f = 6;
        class A {
            void method() {
                System.out.println(a);
                d = 100; //暗黙的に「final」付与のためコンパイルエラー
                f = 100; //暗黙的に「final」付与のためコンパイルエラー
            }
        }
    }
}

匿名クラスのルール

匿名クラス

  • クラス名を指定しない、定義&インスタンス化を一緒にする
  • サブクラスとして or インタフェース実装クラスとして定義
  • static, abstract, final 使えない
  • 外側クラスのメンバにアクセスできる
  • 外側クラスのメソッドの引数、ローカル変数にアクセス→各変数はfinal
  • コンストラクタ 定義できない
Main.java
interface MyInter { void method(); }
class Outer {
    void method() {
        new MyInter() {       //new スーパークラス・インタフェース() {};
            public void method() {}
        }.method(); //匿名クラスのメソッドの呼び出し
    }
}

アノテーション(注釈)

目的
コードを解釈するコンパイラ、実行するJVMに付加情報を伝える

主なアノテーション

アノテーション 付加情報
@Override スーパークラスのメソッドをオーバーライドする
@Functionallnterface 関数型インタフェースである
@Deprecated 非推奨の要素である
@SuppressWarnings コンパイラの警告を無効にする
@SafeVarargs 安全でない可変長引数の警告を無効にする

@Functionallnterface

関数型インタフェースに付与(任意)
関数型インタフェースの要件を満たしてるかチェック

  • 要件
interface.java
@Functionallnterface
initerface Function<T> {
    void foo(T t);       //抽象メソッドを持つ
    String toString();   //Objectクラスのpublicメソッド=抽象メソッドとして宣言できる
    static void X() {};  //staticメソッド,defaultメソッド宣言できる
}

@Deprecated

プログラマが使うのを薦ないことを示す

  • @Deprecatedの補足情報
    • since = "" (default)
      • 非推奨になったバージョンを示す
    • forRemoval = false (default)
      • true: 将来のバージョンで注釈のついたプログラムを削除することを伝える
      • false: 注釈のついたプログラムが非推奨であることを伝える

@SuppressWarnings( 引数 )

引数に指定したコンパイラの警告を無効にする
引数が必須

指定できる主な警告

警告 内容
unchecked 型を使う時などの警告
deprecation @Deprecatedがつけられた要素の警告
Main.java
    import java.util.*;

    public class Main {
        @SuppressWarnings(value = {"unchecked", "deprecation"}) // メンバ(value)1つのみ→ 「value=」省略可 
        public static void main(String[] args)  {
            Date date = new Date("2024/06/25"); //@Deprecated 付与されたコンストラクタを使用
            List list = new ArrayList(); //<型>が未定義
        }
    }

@SafeVarargs

@SafeVarargs をつけられるメソッドは以下3つ
→ final、static、private

カスタムアノテーション P65

独自のアノテーションを作成する
@interface -> アノテーション型となる(java.lang.annotation.Annotationインタフェースを継承したインタフェースになる)

  • アノテーションは任意でメンバをもてる
メンバ.java
import java.lang.annotation.*;

public @interface MyAnnot {
    public enum RANK {A, B, C, D};
    RANK rank();   //暗黙で「public abstract」、列挙値を返すメンバ
    String item(); //暗黙で「public abstract」
    int num();     //暗黙で「public abstract」

    //デフォルト値の指定する場合
    String str1() default new String("");    //コンパイルエラー
    String str2() default "";
    String str3() default null;              //コンパイルエラー
}

 //独自アノテーションを利用するクラス
class Foo {
    @MyAnnot(rank=MyAnnot.RANK.C, item="itemX", num=100)   //独自アノを利用する→メンバ値を指定する
    public int method(int a) {
        return a;
    }
}
  • 独自アノテーションに適応させるアノテーション(メタアノテーションについて)
メタアノテーション 内容
@Documented APIドキュメント作成時、アノテーション情報を含める
@Target アノテーションの適用場所を指定
@Rentention アノテーションをどこまで存在させるか指定
@Inherited スーパークラス→サブクラス へアノテーションを継承させる(クラスアノテーションのみ)
@Repeatable 同じ場所に複数回、アノテーションを使う時
  • アノテーション:任意でメンバを持てる
    • メソッド:暗黙的に「public abstract」
    • 戻り値は以下のいずれか
      • 基本データ型
      • String 型
      • Class 型
      • 列挙型
      • アノテーション型
      • 左記の型の一次元配列
  • メンバにデフォルト値を設定できる

@Target

  • カスタムアノテーションの適用場所 = ElementType列挙型で用意
ElementType列挙型 適用場所
TYPE クラス、インタフェース、enum
FIELD フィールド
METHOD メソッド
PARAMETER メソッドパラメータ
CONSTRUCTOR コンストラクタ
LOCAL_VARIABLE ローカル変数
ANNOTATION_TYPE メタアノテーション
PACKAGE パッケージ
TYPEPARAMETER 型パラメータ
TYPE_USE
MODULE モジュール

@Rentention

定数 存在範囲
SOURCE ソースコードのみ存在させる(コンパイラが破棄)
CLASS クラスファイルまで存在させる(実行時VMは無視)
RUNTIME 実行まで存在

@Repeatable

2種類のカスタムアノテーションを定義する必要あり

①複数使えるアノテーション自体の定義.java
import java.lang.annotation.*;
@Repeatable(MyAnnotContainer.class) //複数回使うアノテーションに@Repeatable 付与
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnot {
    public String value();
}
②MyAnootが複数回使われた時、データを保持する専用のアノテーション(コンテナアノテーション).java
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotContainer {
    public MyAnnot[] value();     //戻り値:複数回使うアノテーション型の配列
}

例外・例外処理 P88

例外:プログラム実行時に発生したエラー
→ スローされた例外をキャッチしないと、プログラムは強制終了。。。
→ 強制終了せず実行を続けるためには、例外処理が必要!
→ RuntimeException 以外の Exceptionのサブクラスが check例外

カテゴリ クラス名 エラー発生条件
Errorのサブクラス
(unchecked例外)
AssertionError
StackOverflowError
NoClassDefFoundError
assert文でfalseになる
アプリの再帰が多すぎる
クラスファイルが見つからない
RuntimeException サブクラス
(unchecked例外)
ArrayIndexOutOfBounds
ArrayStoreException
ClassCastException
IllegalStateException
DateTimeException
MissingResourceException
ArithmeticException
NumberFormatException
存在しない要素にアクセス
不正な型を配列に格納
間違ったキャストをした
間違ったメソッドの呼び方
日付/時間に間違った処理
リソースが見つからない
ゼロ除算
整数でない文字列を変換
RuntimeException以外の
Exceptionサブクラス
(checked例外)
IOException
FileNotFoundException
ParseException
SQLException
入出力のとき
ファイル入出力で目的ファイルない時
解析中のエラー
DBにエラー 
  • Throwableクラスのメソッド
    • void printStackTrace()
      • エラートレース(エラーの発生場所を特定)出力
    • String getMessage()
      • エラーメッセージを取得

catch ブロックの注意点 P93

複数のcatchブロック

catch.java
} catch (例外クラス 変数名) {

} catch (例外クラス 変数名) {

} //例外クラスに継承関係がある場合、サブクラスから定義する。スーパーから定義するとコンパイルエラー

マルチキャッチ

catch.java
} catch (NumberFormatException | ArithmeticException e) {
    //継承関係のあるクラスを列挙できない
    //e は final
}

throws してるメソッド

オーバーライドする時の注意点

  • 例外クラス:同じ or サブクラス
  • RuntimeException, サブクラス:スーパークラスの例外に関係なくスローできる
  • スーパーでthrowsされてても、サブでthrowsしなくてもいい

try-with-resources

暗黙的にリソースを解放してくれる
try-with-resource のとき:tryブロックのみ⭕️

Main.java
import java.sql.*;

class MyResource implements AutoCloseable {  //AutoCloseable=close()のみ宣言、実装時にオーバーライドが必要 
    private String msg;
    public MyResource(String msg) { this.msg = msg; }
    public void close() throws Exception
        System.out.println("close():" + msg);
    }
}
public class Main {
    public static void main(String[] args) {
        try (MyResource obj1 = new MyResource("obj1");  //クローズ対象のリソースを生成(var ⚪︎)、final 扱い
             MyResource obj2 = new MyResource("obj2"); ) { //try(**AutoClosable,Closeableインタフェースの実装クラス**)
            System.out.println("tryブロック内の実装");
            throw new SQLException();
        } catch () {  //try{}終了 → 暗黙的にclose()実行 → リソース解放
            System.out.println("catchブロック:SQLException");
        } finally {
            System.out.println("finallyブロック");
        }
    }
}
実行結果.txt
tryブロック内の実装
close():obj2
close():obj1     //複数のリソースを取得した場合:後に取得したclose()から実行
catchブロック:SQLException
finallyブロック

例外の抑制  P107

参考:https://relearn-java.com/exception/

  • try-catch(-finally):複数の例外が発生した場合、伝わる例外が置き換えられちゃう問題
    • なぜ置き換えられるか → 例外は一つしか伝播できない!
  • try-resource-with:最初に発生した例外を伝え、その他はメソッドで取得できるように抑制
Main.java
import java.sql.*;

class MyResource implements AutoCloseable {  //AutoCloseable=close()のみ宣言、実装時にオーバーライドが必要 
    private String msg;
    public MyResource(String msg) { this.msg = msg; }
    public void method() throws Exception
        throw new SQLException("method()でのエラー:");
    }
    public void close() throws Exception
        System.out.println("close():" + msg);
        throw new SQLException("close()でのエラー:" + msg);
    }
}
public class Main {
    public static void main(String[] args) {
        try (MyResource obj1 = new MyResource("obj1")) {
            obj1.method();
        } catch (SQLException e) { //try{}抜 → close()のSQLExceptionは、obj1.method() の付加情報として伝播(=例外の抑制)
            System.out.println("e.getMessage():" + e.getMessage()); //e = obj1.method()のSQLExceptionを参照
            System.out.println("e.getSuppressed()で取り出した情報");
            Throwable[] errAry = e.getSuppressed(); //obj1.method()のSQLException以外のobj1.close()のSQLExceptionを取得(=抑制された例外を配列で取得)
            System.out.println("抑制例外数" + errAry.length());
            for(Throwable ex : errAry) {
                System.out.println(ex.getMessage());
            }
        } finally {
            System.out.println("finallyブロック");
        }
    }
}

アサーション機能

目的:バグを残さない

  • true であるべき箇所に assert(条件式) : "メッセージ(=falseの場合[AssertionError]のメッセージ)"
  • java -ea Main:-ea =アサーション有効
  • java -da Main:-da =アサーション無効
  • java Main : していない場合は、アサーション無効
Main.java
private void check(int point) {
    assert ( point > 0 ) : "point = " + point;
    //checkメソッドの処理↓
}

コレクション 過去問外しすぎもう一回やる

ラッパークラス

基本データ型の値 → 参照型として扱う専用のクラス
基本データ型との違い・・・値を操作するメソッドがある

コレクション

複数のオブジェクトをまとめて扱うオブジェクト

コレクション_楷書.jpg

インタフェース名 データ(要素)の管理方法 実装クラス
List(≒ サイズ変更できる配列) ・順序づけて管理
・重複 ⚪︎
ArrayList
LinkedList
Vector
Set(≒ 袋の中に要素を入れる) ・順不同で管理
・重複 ×
HashSet
TreeSet
LinkedHashSet
Queue ・FIFO(First In First Out)で管理 ArrayDeque
Map ・「キー」+「値」で管理
・キーの重複 ×
HashMap
TreeMap
LinkedHashMap

各インタフェースの実装

List
  • 実装クラス
クラス名 特徴 使用
ArrayList 要素にインデックスを付与 ランダムアクセス→高速
挿入・削除→低速
同期性→サポートしてない
LinkedList 要素に次の要素へのポインタも付与 挿入・削除→ArrayListより高速
Vetcor ArrayListと同じ 同期性→サポートしてる(マルチスレッド環境で使用)
Set
  • 実装クラス
クラス名 特徴
HashSet アクセス→高速
順序づけ→してない
同期性→サポートしてない
TreeSet アクセス→低速
順序づけ→ソート
同期性→サポートしてない
ceiling(): 引数で指定した要素と等しいか、それよりも大きい要素の中で最小のもの
LinkedHashSet アクセス→高速
順序づけ→挿入順
同期性→サポートしてない

イテレータ

コレクションの要素へ順番にアクセスするオブジェクト(≒ 要素を指すカーソル)
→ 異なるコレクションに対する共通の操作方法

イテレータ.jpg

Main.java
TreeSet<String> set = new TreeSet<String>();
set.add("C"); set.add("A"); set.add("B");
Iterator<String> iter = set.iterator();  //Iterator iterator():各実装クラスで実装。イテレータを取得する
while (iter.hasNext()) {  //hasNext():次の要素がある場合、true
    System.out.println(iter.next() + " "); //next():指してる要素を返す
}
console.txt
A B C

Queue インタフェース

FIFO(First In First Out)でデータ管理

キュー.jpg

  • Queue インタフェースの主なメソッド
操作 メソッド名 内容
挿入 boolean add(E e)
boolean offer(E e)
要素追加=true、容量制限で追加ムリ=IllegalStateException
要素追加=true、それ以外=false
削除 E remove()
boolean remove(Object o)
E poll()
先頭の取得・削除、空の場合=NoSuchElementException
Collectionインタフェースのメソッド
先頭の取得・削除、空の場合=null
検査 E element()
E peek()
先頭の取得、空の場合=NoSuchElementException
先頭の取得・削除、空の場合=null

Map

  • 実装クラス
クラス名 特徴
HashMap 順序づけ→順不同
キー・要素→null⚪︎
同期性→サポートしてない
TreeMap 順序づけ→キー元にソート
同期性→サポートしてない
LinkedHashMap 順序づけ→維持される必要がある場合使う
同期性→サポートしてない
TreeMap.java
NavigableMap<String, String> map = new TreeMap<String, String>();   //NavigableMap:指定されたキーに一番近い要素を返すメソッド持つ
map.put("1111", "ItemA");
map.put("2222", "ItemB");
map.put("3333", "ItemC");
map.put("4444", "ItemD");
String key = "2000";
System.out.println(map);
if(map.containsKey(key)) {
    System.out.println("get() : " + map.get(key));
}else {
    System.out.println("higherKey() : " + map.higherKey(key));    //指定されたキーに一番近い下の値を返す
    System.out.println("lowerKey() : " + map.lowerKey(key));      //指定されたキーに一番近い上の値を返す
}
NavigableMap<String, String> sub = map.subMap("2000", true, "3500", true);   //キー範囲を指定して要素を取り出す
System.out.println("2000 - 5000 : " + sub);

ジェネリクス

各コレクションのクラス:<⚪︎>に扱う型を指定

  • クラス定義時: <T> として汎用的に型を指定できるようにする
  • クラス利用時: <String> として扱う型を指定する
    • ジェネリクス という機能
    • <> :「型パラメータリスト」、扱う型を指定する
ジェネリクス.java
ArrayList<String> list = new ArrayList<String>();
ArrayLisy<Object> list2 = new ArrayList<String>();  //型パラメータリストが異なるのでコンパイルエラー

ダイヤモンド演算子

ダイヤモンド演算子.java
ArrayList<String> list = new ArrayList<>(); //左辺の型パラメータリストから類推できるので、右辺は省略。 <>=ダイヤモンド演算子

コンパイル時にデータ型が明確なら使える(明確でない場合、コンパイルエラー)
左辺の型が宣言されてる時、右辺で使用できる¥

ジェネリクスを使ってクラス定義

メリット

  • 独自クラスを定義 → クラス生成時に扱う型を決められる!
Main.java
class Gen<T> {  //クラス名の後に型パラメータリストを指定
    private T var1; //適応させたい場所に型パラメータ(仮の型)を指定する
    //private static T var2;  staticメンバには指定できない
    public Gen(T var1) {this.var1 = var1;}
    public T getVar1() {return var1;}
    public void setVar1(T var1) {this.var1 = var1;}
}

public class Main {
    public static void main(String[] args) {
        Gen<String> g1 = new Gen<>("ABC");  //型パラメータリストの型を指定->g1オブジェクト内で扱う型が決まる(T -> String)
        Gen<Integer> g2 = new Gen<>(2);
        Gen<Boolean> g3 = new Gen(true);   //<>なくても実行できる
    }
}

型パラメータ・・・基本データ型は使えない

ジェネリクスを使ってメソッド定義

型パラメータリストの有効範囲=メソッド内だけ
修飾子 <T> 戻り値

Main.java
class Gen {
    public <T> T methodA(T value) { return value; }  //戻り値・引数=型パラメータ使用
    public static <T> methodB(List<T> list) { System.out.println(list); }  //メソッドの型パラメータ -> staticで使える
}

public class Main {
    pubcli static void main(String[] args) {
        Gen g = new Gen();
        String stg = g.<String>methodA("abc");
        // String stg = g.methodA("abc"); -> 引数で類推できるので<String>なしでも⚪︎
    }
}

ジェネリクスを使ってインタフェース定義

インタフェースはインスタンス化できない
→ 実装するクラス側で型を指定する

Main.java
interface MyIn<T> { void method(T t); } //インタフェース名の後に型パラメータリスト指定、引数に適用

class Foo implements MyIn<String> { //実装クラスで型を指定
    public void method(String s) { System.out.println(S); }
}

public class Main {
    public static void main(String[] args) {
        new Foo.method("ABC");  //実装を利用する側では型の指定はしない
        new MyIn<Integer>() {  //匿名クラスでジェネリクスを使用する例
            public void method(Integer i) { System.out.println(i); }
        }.method(100);
    }
}

継承を使ったジェネリクス

型パラメータリスト=どんな型でも指定できる!
→ 指定できる型を限定したい時に継承を使う

Main.java
class Gen<T extends Number> {  //Number or そのサブクラスを型に指定できる
    private T var;
    public Gen(T var) { this.var = var; }
}

public class Main {
    public static void main(String[] args) {
        Gen<Integer> g1 = new Gen<>(100);  //Integer型を扱う
        Gen<Double> g2 = new Gen<>(3.14);  //Double型を扱う
        Gen<String> g3 = new Gen<>("abc")  //Numberのサブクラスでないため、コンパイルエラー
    }
}

ワイルドカードを使ったジェネリクス

? を使って型パラメータを指定 → 型は実行時でないとわからない
変数宣言時に使用できる。 

:

Main.java
class X {
    public String toString() { return "X"; }
}

class Y extends X {
    public String toString() { return "Y"; }
}

public class Main {
    public static void method1(List<? extends X> list) {  //listの要素は、Xかそのサブクラスのみ
        //メソッドの引数=実行時までメソッドの引数の型は分からない  → add()でなんらかのオブジェクトを格納するとエラー
        list.add(new X());
        list.add(new Y());
    }
    public static void method2(List<? super Y> list) {  //listの要素は、Yかそのスーパークラス
        list.add(new X());  //エラー
        list.add(new Y());  //<? super type> のみtypeと同じ型のオブジェクトは追加 ○
    }
    public static void main(String[] args) {
        List<X> l1 = new Arraylist<>(); l1.add(new X());
        List<Y> l2 = new Arraylist<>(); l2.add(new Y());
    }
}

オブジェクトの順序づけ P158

ソート順序を独自に決める

二つのインタフェースを実装する!

  • java.lang.Comparable
    • 自然順序づけを提供するインタフェース
    • public int compareTo(T o) のみ宣言 : T=比較対象のデータ型
      • → 実装クラスでオーバーライドして、並び順を決める
      • → 比較対象のオブジェクト自身に compareTo() を実装
  • java.util.Comparator
    • 比較対象のオブジェクトから独立した、比較ルールクラスを定義
    • public int compare(T o1, T o2) 宣言
      • → 実装クラスでオーバーライドして、並び順を決める
      • → Comparableの compare() と同様処理
  • IntegerクラスでComparableインタフェースを実装
Comparable.java
public final class Integer extends Number implements Comparable<Integer> {  //Comparableインタフェースで扱うクラスを指定。Integerクラスで実装するから<Integer>
    public int compareTo(Integer anotherInteger) {
        return compare(this.value, anotherInteger.value);
    }

    public static int compare(int x, int y) {
        return (x < y) ? -1 : ((x == y) ? 0 : 1);  //自オブジェクト・引数のオブジェクトの比較
    }
}
  • 独自クラスでComparatorインタフェースを実装
Comparable.java
class Employee {
    private Integer id;
    public Integer getId() { return id; }
}
class MyRule implements Comparator<Employee> {
    public int compare(Employee e1, Employee e2) {
        return e1.getId().compareTo(e2.getId());  //e1.getId()とe2.getId()のIntegerオブジェクトをIntegerクラスのcompareTo()で比較 → 自然順序でソート
    }
}

コレクションが提供する便利なメソッド

Collections クラス

  • コレクションを操作するクラス
メソッド 説明
static void sort(List list) 自然順序づけに従って、昇順にソート
static void sort(List list, Comparator c) コンパレータが示す順序に従って、ソート
static void reverse(List< ? > list) 要素の順番を逆にする

Arrays クラス

  • 配列を操作するクラス
メソッド 説明
static void sort(Object[] a) 配列の要素を昇順にソート
static List asList(T... a) 配列からリストを作成
static void sort(T[] a, Comparator c) コンパレーターが示す順序に従って、配列の要素をソート
array.java
Object[] array = { "aa", 10 };
Arrays.sort(array);  //違う型を持つ配列をソート → ClassCastException

String[] args = {"A", "B", "C"};
List<String> list = Arrays.asList(args);
list.add("D");  //Arrays.asList() -> 固定サイズのlistになる。追加すると、UnsupportedOperationException

of( ) メソッド

  • List, Set, Map を作成
  • いかなる変更もダメ
  • nullダメ
of().java
List<Integer> list = List.of(1, 2, 1);
Set<Integer> set1 = Set.of(1, 2, 3);
Set<Integer> set2 = Set.of(1, 2, 1);   //実行時エラー(重複要素は入れられない)
Map<Integer, String> map = map.of(a, "a", 2, "b", 3, "c");

関数型インタフェース P182

定義されている抽象メソッドが1つだけのインタフェース

  • ラムダ式、メソッド参照で使うために導入された
  • java.util.function パッケージで提供

var (ローカル変数の型推論)

var 使える箇所

  • ローカル変数の初期化時
  • for 文のインデックス
  • 拡張 for のローカル変数
  • try-with-resourceのローカル変数
    使えない箇所
  • メソッドの引数、戻り値
  • コンストラクタの引数
  • フィールド
  • catchブロック

ラムダ式

インタフェースの実装をシンプルにできる方法!
サブクラスの実装+メソッドのオーバーライド

  • 主な関数型インタフェース
インタフェース名 抽象メソッド
Function<T, R> R apply(T t)
BiFunction<T, U, R> R apply(T t, U u)
Consumer<T> void accept(T t)
BiConsumer<T, U> void accept(T t, U u)
Predicate<T> boolean test(T t)
BiPredicate<T, U> boolean test(T t, U u)
Supplier<T> T get()
UnaryOperator<T> T apply(T t) ※Functionの拡張
BinaryOperator<T> T apply(T t1, T t2) ※BiFunctionの拡張
  • ラムダ式の書き方
省略しない書き方.java
Function<String, String> f1 = (String str) -> {  //引数が一つの場合=()省略可、インタフェースの宣言で、引数の型は分かるので省略可 
    return "Hello " + str;  //一行の場合={}省略可、{}省略した場合、return; 省略可
}
String str1 = f1.apply("HY");
省略した書き方.java
Function<String, String> f1 = str -> "Hello " + str
String str1 = f1.apply("HY");
  • ラムダ式が外側の変数を使うとき→その変数は final でないといけない
実質的 final.java
public class Main {
    int a = 10;
    public void method() {
        final int b = 20;
        int c = 30;  //実質的final
        int d = 40;
        d = 50;      //※再代入すると、実質的finalにはならない!!
        int e = 60;  //実質的final
        Function<String, String> f1 = (String str) -> {
            System.out.println(a);
            System.out.println(b);
            System.out.println(c);
            System.out.println(d);  //コンパイルエラー
            System.out.println(e);
            e = 100;  //コンパイルエラー
            return "Hello" + str;
        } 
    }
}
実質的 final.java
public static void main(String[] args) {
    int i = 25;
    Supplier<Integer> s = () -> i;  //アクセスは問題ない
    i++;  //i は final なのでコンパイルエラー
    System.out.println(s.get());
}

メソッド参照

インタフェース実装時、抽象メソッドの引数(個数・型)= 抽象メソッド内のソッドの引数(個数・型)
→ メソッド参照で書ける
→ 抽象メソッドの引数 & 抽象メソッド内のメソッドの引数 省略できる

  • staticメソッド参照
  • インスタンスメソッド参照
  • コンストラクタ参照

staticメソッド参照( 処理内のメソッド:staticメソッド)→ クラス名 :: メソッド名

staticメソッド参照.java
List<Integer> list = Arrays.asList(3, 1, 2);

//ラムダ式
Consumer<List<Integer>> con1 = list -> Collection.sort(list);

//static メソッド参照
Consumer<List<Integer>> con1 = Collection::sort;

con1.accept(list);

インスタンスメソッド参照( 処理内のメソッド:インスタンスメソッド)→ インスタンス変数名 :: メソッド名

forEach()の例.java
List<Integer> list = List.of(3, 1, 2);

//ラムダ式
list.forEach( a -> System.out.print(a + " ") ); //Iterableインタフェースから継承したforEach()メソッド=コレクションの各要素に処理をする Comsumer インタフェースのaccept()が実装されている。

//インスタンスメソッド参照
list.forEach(System.out::print + " ") //コンパイルエラー= +演算子などで処理はできない!
インスタンス変数を宣言していない時.java
//ラムダ式
UnaryOperator<String> obj = s -> s.toUpperCase();

//インスタンスメソッド参照
UnaryOperator<String> obj = s ::toUpperCase(); //コンパイルエラー=s変数を宣言なしで使っているから

//省略なしで記述
class Uo implements UnaryOperator<String> {
    @Override 
    public String apply() {
        return s.toUpperCase();  //コンパイラ「このS何者??」
    }
}

//インスタンスメソッドを呼びだすインスタンス変数が指定できない時の書き方
UnaryOperator<String> obj = String ::toUpperCase(); //クラス名で指定する(staticメソッド参照みたい...)
インスタンスメソッド参照に引数がある場合.java
//ラムダ式
BiFunction<String, Integer, Character> bf1 = (s, i) -> s.charAt(i);
System.out.println(bf1.apply("Java", 2));

//インスタンスメソッド参照 OKパターン
BiFunction<String, Integer, Character> bf1 = String ::charAt;
System.out.println(bf1.apply("Java", 2));

//インスタンスメソッド参照 NGパターン
BiFunction<Integer, String, Character> bf1 = String ::charAt; //「第一引数=メソッド実行する対象オブジェクト、第二引数=メソッドの引数」で渡すことが決まってるから!
System.out.println(bf1.apply(2, "Java"));
static(インスタンス)メソッドの区別がつかない.java
//IntegerクラスのhashCode() → static・インスタンスメソッドどちらもある

//ラムダ式
Function<Integer, Integer> obj = i -> i.hashCode(); //hashCode()=インスタンスメソッド

//staticメソッド参照
Function<Integer, Integer> obj = Integer::hashCode(); //コンパイルエラー=どちらか分からない
// → こういう場合、ラムダ式で実装すること!

コンストラクタ参照 クラス名::new

Main.java
public class Main {
    public static void main(String[] args) {
        //Supplier<Foo> obj1 = () -> new Foo();
        Supplier<Foo> obj1 = Foo ::new
        System.out.println(obj1.get().a);
        /* class test implements Supplier<Foo> {
            public Foo get() {
                return Foo obj1 = new Foo();
            }
        }*/

        //Function<Integer, Foo> obj2 = i -> new Foo(i);
        Function<Integer, Foo> obj2 = Foo::new;
        System.out.println(obj2.apply(10).a);
    }
}

class Foo {
    int a = 0;
    Foo() { }
    Foo(int a) { this.a = a; }
}
配列を生成するパターン.java
//Function<Integer, String[]> array = i -> new String[i];
Function<Integer, String[]> array = String[]::new;
System.out.println(array.apply(10).length);

基本データ型(int, long, double)に特化した関数型インタフェース

  • 引数にint型を使う関数型インタフェース
インタフェース名 抽象メソッド
IntFunction<R> R apply(int value)
IntConsumer void accept(int value)
IntPredicate boolean test(int value)
IntSupplier int getAsInt()
IntUnaryOperator int applyAsInt(int value)
IntBinaryOperator int applyAsInt(int value1, int value2)

Supplierのみ boolean型に対応したインタフェイスある
→ BooleanSupplier : boolean getAsBoolean()

基本データ型特化インタフェース.java
//IntFunction<String[]> array = index -> new String[index];
IntFunction<String[]> array = String[]::new;
String[] obj1 = array.apply(5);

IntSupplier obj2 = "Java"::length;    //戻り値は int 型に決定しているので、変数宣言時の<>は省略
int num2 = obj2.getAsInt();

様々なパターンの関数型インタフェース 命名規則が決まっている

  • 戻り値に基本データ型を指定するとき
インタフェース名 抽象メソッド
ToIntFunction<T> int applyAsInt(T t)
ToIntBiFunction<T, U> int applyAsInt(T t, U u)
IntToDoubleFunction double applyAsDouble(int value)
IntToLongFunction long applyAsLong(int value)
ObjIntConsumer<T> void accept(T t, int value)
ラムダ式の型の自動変換.java
IntToDoubleFunction f1 = (int i) -> { return i * 3.14 }
IntToDoubleFunction f2 = (Integer i) -> { return i * 3.14 }  //コンパイルエラー=引数は自動変換されない
Double ans1 = f1.applyAsDouble(100);  // OK=戻り値は自動変換される

関数型インタフェースの合成

andThen(),compose().java
Function<String, Character> f1 = s -> s.charAt(0);
Function<Character, Boolean> f2 = c -> Character.isUpperCase(c);

// 自身.andThen(after) ①自身②after->結果返す
Function<String, Boolean> f3 = f1.andThen(f2);
System.out.println(f3.apply("Java"));

/*class F3 implements Function<String, Boolean> {
    public boolean apply(String str) {
        Character c = f1.apply("Java");
        Boolean b = f2.apply(c); 
        return b;
    }*/

//自身.compose(before) ①before②自身→結果返す
Function<String, Boolean> f4 = f2.compose(f1);
System.out.println(f4.apply("Java"));

}
and(),or(),negate().java
LongPredicate p1 = n -> (n % 3) == 0;
LongPredicate p2 = n -> (n % 5) == 0;

//一方 true -> true返る
LongPredicate p3 = p1.or(p2);
System.out.println(p3.test(15));

//両方 true -> true返る
LongPredicate p4 = p1.and(p2);

//反対の結果が返る
LongPredicate p5 = p4.negate();

ストリームAPI

データ(コレクション、配列...)を元に集計処理をするAPI

  • 複数の処理をつなぐ仕組み(=パイプライン処理)をもってる
  • パイプライン処理には、データソースが必要(処理のもとになるから)
  • データソースとなるクラスは、ストリーム生成するメソッド持ってる
    <処理の流れ>
  1. データソース
  2. ストリームオブジェクト生成
  3. 処理(パイプラインの途中) → 中間操作
  4. 処理(最後) → 終端操作
    1. 要素ごとに行いたい処理を書くが、反復処理ではなく、ラムダ式を一行書くのみ
      1. → ストリームが内部でイテレータを持っているため

ストリームAPI.jpg

基本データ型に対応したストリームオブジェクト

インタフェース名 説明
Stream<T> 汎用的なストリーム
IntStream int型に対応したストリーム
LongStream Long型に対応したストリーム
DoubleStream Double型に対応したストリーム

終端操作を行うメソッド

allMatch(), anyMatch(), noneMatch().java
//引数=Predicateインタフェース
List<String> data = Arrays.asList("yuga", "ren", "shou");
boolean result1 = data.stream().allMatch(s -> s.length() >= 3);  //指定された条件に全ての要素が一致するか
boolean result2 = data.stream().anyMatch(s -> s.length() >= 3);  //指定された条件にいずれかの要素が一致するか
boolean result3 = data.stream().noneMatch(s -> s.length() >= 3);  //指定された条件に全ての要素が一致しないか

Stream<String> stream1 = data.stream();
boolean result4 = stream1.allMatch(s -> s.length() >= 3);
boolean result5 = stream1.anyMatch(s -> s.length() >= 3);  //コンパイルエラー ※1streamオブジェクトに対して、終端操作は1回だけ!!
count(), forEach().java
long result = Stream.of("a", "b", "c").count();  //要素の数を返す

Stream<String> stream1 = Stream.of("a", "b", "c");
stream1.forEach(System.out::print);  //引数で指定されたアクションを各要素に行う、引数=Consumerインタフェース
reduce()メソッド.java
Stream<Integer> stream = Stream.of(10, 20, 30);
int result = stream.reduce(0, (a, b) -> a + b);  // T reduce(T value, BinaryOperator<T> bo) value:初期値
System.out.println(result);

// 結果:60

IntStream、LongStream、DoubleStream : reduce()メソッド

  • int reduce(int value, IntBinaryOperator ibo)
  • long reduce(long value, LongBinaryOperator lbo)
  • double reduce(double value DoubleBinaryOperator dbo)
reduce()メソッド.java
inaryOperator<Integer> operator = (a, b) -> a + b;
Stream<Integer> stream2 = Stream.of(40, 50, 60); 
Optional result2 = stream2.reduce(operator);  // Optional<T> reduce(BinaryOperator bo)
System.out.println(result2);
toArray()メソッド.java
//ストリームオブジェクトを配列に変換
int[] ary1 = IntStream.range(1, 10).toArray();  //IntStreamインタフェースで提供されるtoArray()メソッド、戻り値=int[](DoubleStream、LongStreamも同じ理屈)
int[] ary2 = IntStream.rangeClosed(1, 10).toArray();  // int[] toArray();

Object[] ary3 = Stream.of("a", "b").toArray();  //Streamオブジェクトで提供されるtoArray()メソッド、戻り値=Object[]

String[] ary4 = Stream.of().toArray(String[] :: new);  // A[] toArray(IntFunction<A[]> if)

Optional クラス  P228

一つの値を保持しているクラス

  • 値を保持しているかどうかで処理が違うメソッドを持つ
  • 値が存在しない場合 = empty というオブジェクト
get()、isPresent()メソッド.java
Optional<Integer> op = Optional.of(10);  // Optional.of() -> 引数で指定された値を持つOptionalを返す
System.out.println(op.get());  // T get() -> ある:値、ない:NoSuchElementException
op.isPresent();  // boolean isPresent() -> ある:true、ない:false

戻り値が Optional の終端操作

max()メソッド.java
// Optional<T> max(Comparator com) -> comparatorに従って最大要素を取り出す
List<String> data = Arrays.asList("aaa", "bb", "c");
Optional<String> max = data.stream().max(Comparator.naturalOrder());
findFirst()、findAny()メソッド.java
List<String> data = Arrays.asList("あ", "い", "う", "え", "お");
Optional<String> first = data.stream().findFirst();  //Optional<T> findFirst() -> 最初の要素を返す
Optional<String> any = data.stream().findAny();  //Optional<T> findAny() -> いずれかの要素を返す
Stream<String> stream = Stream.empty();
Optional<String> empty = stream.findFirst();

System.out.println(first.get());
any.ifPresent(System.out::println);  // void ifPresent(Consumer<T> con) -> 値が存在=Consumerのacccept()を呼び出す。終端操作ではない。Optionalのメソッドの一つ
empty.ifPresent(r -> System.out.println("result:" + r));

※ 基本データ型のStreamオブジェクトでも提供(例:IntStream)

インタフェース メソッド 戻り値
Stream max()、min()、findFirst()、findAny() Optional
IntStream max()、min()、findFirst()、findAny() OptionalInt (取得:getAsInt())
IntStream average() OptionalDouble
IntStream sum() int
  • Optional.get()のNoSuchElementException を自身で指定したい(終端操作ではなく、Optionalクラスのメソッド)
orElse()、orElseGet()、orElseThrow()メソッド.java
Stream<Double> stream = Stream.empty();
Optional<Double> result = stream.findFirst();

//値が存在=値を返す、値がない=返す値を指定
System.out.println(result.orElse(0.0));  // T orElse(T other) -> 戻り値:other
System.out.println(result.orElseGet(() -> Math.random()));  // T orElseGet(Supplier other) -> 戻り値:Suppliler の get()を呼び出す、戻り値の型は合わせないとコンパイルエラー
System.out.println(result.orElseThrow(IllegalArgumentException::new)); // T orElseThrow(Suppiler other) -> 戻り値:Supplierで生成した例外をスローする

中間操作

取得したストリーム ー(処理)→ 新しいストリーム生成

  • 中間操作をするメソッド群= java.util.stream.Streamインタフェースで宣言
  • filter(): trueで返る要素のストリームを返す
  • distinct(): 重複を除く要素のストリームを返す
filter()、distinct()メソッド.java
Stream<String> stream = Stream.of("akari", "masato", "asako", "");
stream.filter(s -> s.startWith("a"))  // Stream<T> filter(Predicate<T> predicate)
      .forEach(x -> System.out.print(x + "" ));
//結果: akari asako

Stream<String> stream2 = Stream.of("akari", "masato", "asako", "", "akari");
stream2.distinct()  // Stream<T> distinct()
      .forEach(x -> System.out.print(x + "" ));
//結果: masato asako
  • skip(): 先頭からn個の要素をスキップしたスト
  • limit(): n個分の要素を持つストリームを返す
limit()、skip()メソッド.java
IntStream.iterate(1, n -> n + 1)  //初期値:1、1ずつ加算した要素の無限ストリーム返す
         .skip(100L)  // Stream<T> skip(long n)
         .limit(5L)  // Stream<T> limit(log n)
         .forEach(x -> System.out.print(x + " "));
  • map(): 引数の処理をしたストリームを返す
map()メソッド.java
Stream<String> streama = Stream.of("sakura", "momo", "itsuki");
Stream<String> streamb = streama.map(s -> s.toUpperCase());  // Stream<T> map(Function<T> function)
streamb.forEach(x -> System.out.print(x + " "));
  • map(): 1要素=1結果
  • flatMap(): 1要素=多結果
    • → 入れ子になっているストリームを平坦化する
flatMap()メソッド.java
List<Integer> data1 = Arrays.asList(10);
List<Integer> data2 = Arrays.asList(20, 30);
List<Integer> data3 = Arrays.asList(40, 50, 60);
List<List<Integer>> dataList = Arrays.asList(data1, data2, data3);

//map()の場合
dataList.stream()
        .map(data -> data.stream())
        .forEach(l -> {
            l.forEach(x -> System.out.print(x + " "));
        });

//flatMap()の場合
dataList.stream()
        .flatMap(data -> data.stream())
        .forEach(x -> System.out.print(x + " "));

flatMap().jpg

  • sorted(): 自然順序に並べ替え
    • sorted(Comparator.〇〇()): 明示的に並べ替え順序を指定
sorted()メソッド.java
Stream.of("tatsumi", "igarashi", "kojima")
      .sorted()  // Stream<T> sorted()
      .forEach(System.out.print(x + " "));
Stream.of("tatsumi", "igarashi", "kojima")
      .sorted(Comparator.reverseOrder())  // Stream<T> sorted(Comparator comparator)、逆順
      .forEach(x -> System.out.print(x + " "));
  • peek(): 引数がConsumerインタフェース(戻り値なし)→ デバック機能として使う
peek()メソッド.java
List<String> list = Stream.of("one", "two", "three", "four", "four");
                    .filter(s -> s.length() > 3)
                    .peek(e -> System.out.println("filter後の確認" + e)) // Stream<T> peek(Consumer consumer)
                    .map(String::toUpperCase)
                    .forEach(System.out::println);

ストリームインタフェースの型変換するメソッド(戻り値省略)

インタフェース名 Stream 生成 DoubleStream 生成 IntStream 生成 LongStream 生成
Stream map() mapToDouble() mapToInt() mapToLong()
DoubleStream mapToObject() map() mapToInt() mapToLong()
IntStream mapToObject() mapToDouble() map() mapToLong()
LongStream mapToObject() mapToDouble() mapToInt() map()
  • ※ 基本データ型のストリーム → ラッパークラスのStreamへ変換する= boxed()メソッド
    • IntStream -> Stream <Integer>
    • DoubleStream -> Stream <Double>
    • LongStream -> Stream <Long>
boxed()メソッド.java
IntStream stream = Stream.of(1, 2, 3);
Stream<Integer> stream = stream.boxed();

ストリームインタフェースの型変換するメソッド(戻り値あり)

元の型 変換後の型(戻り値) メソッド
Stream <String> Stream<Integer> map(Function f)
Stream<String> IntStream mapToInt(ToIntFunction f)
Stream<Integer> IntStream mapToInt(ToIntFunction f)
Stream<Integer> Stream<String> map(Function f)
IntStream Stream<String> mapToObject(IntFunction f)
IntStream Stream<Integer> boxed()
型変換コード例.java
Stream<String> streamS = Stream.of("a", "b");
Stream<Integer> streamI = streamS.map(s -> s.length());

Stream<String> streamS2 = Stream.of("ayumi", "michiko", "sakiura");
IntStream Istream = streamS2.mapToInt(s -> s.length());

Stream<Integer> streamI2 = Stream.of(1, 2, 3);
IntStream Istream2 = streamI2.mapToInt(s -> s);

IntStream Istream3 = Stream.of(2,3,4);
Stream<String> streamS3 = Istream3.mapToObject(s -> s + "a");

IntStream Istream4 = Stream.of(3,4,5);
Stream<Integer> streamI3 = Istream4.boxed();

ストリームインタフェースの暗黙の型変換をするメソッド

  • IntStream、LongStreamインタフェースで提供
インタフェース名 メソッド
IntStream LongStream asLongStream()
DoubleStream asDoubleStream()
LongStream DoubleStream asDoubleStream()
暗黙の型変換.java
IntStream streami = Stream.of(2, 3, 5);
DoubleStream streamd = streami.asDoubleStream();

終端操作 ~ collect()メソッド ~ P246

ストリームの要素 → まとめて1つのオブジェクトを取得
collect()メソッド: 引数にCollectorクラスのメソッドを指定

Collectorsクラスのメソッド

  • toList()、joining()、summingInt()、averagingInt()
toList()、joining()、summingInt()、averagingInt().java
Stream<String> stream1 = Stream.of("tatsumi", "igarashi", "kojima");
List<String> result = stream1.collect(Collectors.toList());  //要素をListに蓄積する

Stream<String> stream2 = Stream.of("tatsumi", "igarashi", "kojima");
String result2 = stream2.collect(Collectors.joining(" | "));  //要素を連結 → 1つのString
//結果: tatsumi | igarashi | kojima

Stream<String> stream3 = Stream.of("tatsumi", "igarashi", "kojima");
Integer result3 = stream3.collect(Collectors.summingInt(t -> t.length()));  //結果の合計値 Collector<T,?,Integer> summingInt(ToIntFunction<T> function)
//結果: 21

Stream<String> stream4 = Stream.of("tatsumi", "igarashi", "kojima");
Double result4 = stream4.collect(Collectors.averagingInt(t -> t.length())); //結果の平均 Collector<T,?,Integer> .averagingInt(ToDoubleFunction<T> function)
//結果: 7

※ summingInt だけじゃない

  • Long summingLong(ToLongFunctioin f);
  • Double summingDouble(ToDoubleFunction f);
    ※ averageInt だけじゃない(戻り値:Double
  • Double averagingLong(ToIntFunction f);
  • Double averagingDouble(ToIntFunction f);

  • toSet()、toMap()メソッド: Set、Mapへ変換
toSet()、toMap()メソッド.java
import java.util.stream.Stream;
import java.util.stream.Collector;   //終端操作:collect(Collector collector)=引数に使うオブジェクト
import java.util.stream.Collectors;  //Collectorオブジェクトを返すメソッドが用意されているクラス
import java.util.Map;

Stream<String> stream = Stream.of("tatsumi", "igarashi", "kojima");
Set<String> set = stream.collect(Collectors.toSet());

Stream<String> stream2 = Stream.of("tatsumi", "igarashi", "kojima");
Map<String, String> map = stream2.collect(Collectors.toMap(s -> s, String::toUpperCase));  //  toMap(Function<T> key, Function<T> value)
//結果: {tatsumi=TATSUMI, igarashi=IGARASHI, kojima=KOJIMA}
  • toMap(Function key, Function value, BinaryOperator function)
toMap().java
Stream<String> stream = Stream.of("nao", "akko", "ami");
Map<Integer, String> map = stream.collect(Collectors.toMap(
                                s -> s.length(),
                                s -> s,
                                (s1, s2) -> s1 + ":" + s2));  //第三引数=キーが同じ場合、要素をグループ化&値に対する処理
 // 結果
 {3=naoami, 4=akko}
 
  • toMap(Function key, Function value, BinaryOperator function, Supplier mapSuppiler)
toMap().java
Stream<String> stream = Stream.of("nao", "akko", "ami");
Map<Integer, String> map = stream.collect(Collectors.toMap(
                                s -> s.length(),
                                s -> s,
                                (s1, s2) -> s1 + ":" + s2),
                                TreeMap::new);  // 第4引数=格納するMapを指定 
//結果
{3=naoami, 4=akko}
class java.util.TreeMap
  • Collectors <T,?,Map <K,List<T>>> groupingBy(Function<T> function) → 引数が一つの場合
groupingBy()メソッド.java
Stream<String> stream = Stream.of("belle", "akko", "ami", "bob", "nao");
Map<String, List<String>> map =
            stream.collect(Collectors.groupingBy(s -> s.substring(0, 1)));  //①apply()に要素を渡す②Mapのキーを返す③同じキーを返す要素をListでグループ化
System.out.println(map);
//結果: {a=[akko, ami], b=[belle, bob], n=[nao]}
  • Collectors <T,?,Map <K,List<T>>> groupingBy(Function<T> function, Collector collect) → 引数が二つの場合
groupingBy().java
Stream<String> stream = Stream.of("belle", "akko", "ami", "bob", "nao");
Map<String, String> map = 
            stream.collect(Collectors.groupingBy(s -> s.substring(0, 1), Collectors.joining()));  //第2引数=グループ化した値に行う処理
//結果: {a=akkoami, b=bellebob, n=nao}
  • Collectors <T,?,Map <K,List<T>>> groupingBy(Function<T> function, Suppiler suppiler,Collector collect) → 引数が3つの場合
groupingBy().java
Stream<String> stream = Stream.of("belle", "akko", "ami", "bob", "nao");
Map<String, String> map = 
            stream.collect(Collectors.groupingBy(s -> s.substring(0, 1),
                           TreeMap::new,  //戻り値のMap型を指定する
                           Collectors.joining()));
System.out.println(map);
System.out.println(map.getClass());

//結果
{a=akkoami, b=bellebob, n=nao}
class java.util.TreeMap
  • partitioningBy(Predicate predicate)
    → グルーピング処理をPredicateでやる
partitioningBy().java
Stream<Integer> stream = Stream.of(3, 5, 7, 9);
Map<Boolean, List<Integer>> map = stream.collect(
                           Collectors.partitioningBy(s -> s > 5));
 // 結果: {false=[3, 5], true=[7, 9]}                          
  • partitioningBy(Predicate predicate, Collector collector)
partitioningBy().java
Stream<Integer> stream = Stream.of(3, 5, 7, 9);
Map<Boolean, Integer> map = stream.collect(
                           Collectors.partitioningBy(s -> s > 5),
                           Collectors.summingInt(n -> n));
 // 結果: {false=8, true=16}  
  • maping(): ストリームの各要素に行いたい処理を指定(中間操作のmap()と同様)
maping().java
Stream<String> stream = Stream.of("akira", "rimi", "sonoko");
String result = stream.collect(Collectors.maping(
                       s -> s.toUpperCase(),  //第1引数=各要素に行いたい処理 function<T>
                       Collectors.joining(":")));  //第2引数=マップ後に行いたい処理 Collector
//結果: AKIRA:RIMI:SONOKO
  • maxBy(Comparator comparator)、minBy(Comparator comparator):最大値、最小値を取得
maxBy()、minBy().java
Stream<String> stream = Stream.of("naoki", "akko", "ami")
Optional<String> result = stream.collect(Collectors.minBy(Comparator.naturalOrder()));
//結果: akko

モジュール・システム

モジュールとは
クラス < パッケージ < モジュール

  • クラス:プログラムの最小単位
  • パッケージ:クラスをグループ化
  • モジュール:パッケージをグループ化

モジュールのメリット

  • 信頼性の高い構成
    • モジュール間の依存性を明記、認識
  • 強力なカプセル化
    • エクスポート機能
  • スケーラブルなJavaプラットフォーム
    • 必要なモジュールのみ組み込める
  • プラットフォームの整合性向上
    • 利用されたくない機能は隠せる
  • パフォーマンスの向上
  • 主なモジュール
モジュール 説明 含まれるパッケージ
java.base Java SE Platform の基盤になるAPI java.lang, java.util, java.text
java.desktop GUIなどのAPI java.awt, javax.swing
java.sql JDBC API java.sql, javax.sql

今の実行環境で参照できるモジュール一覧を表示

terminal.java
% java --list-modules
java.base@11
java.compiler@11

モジュール定義

module-info.java
module foo {
    exports xlib;  //exports:どのパッケージを公開するか指定
    requires java.base;  //requires:依存するモジュール指定(このモジュールではどのモジュールが必要なのか・使っているのか)、java.base:全てのモジュールへ暗黙的に含まれるから、省略可能
}

モジュールのコンパイル

module-info.java -> module-info.class 生成
モジュールのルートディレクトリに配置すること!

  • ライブラリを提供する側(fooモジュール)
terminal.java
javac -d out/foo src/foo/ylib/XTest.java  // javac -d <クラスファイルの生成場所> <コンパイルするソースファイル場所>
javac -d out/foo src/foo/module-info.java
  • ライブラリを利用する側(clientモジュール)
    • → 依存するモジュールも指定 = --module-path
terminal.java
javac -d out/client --module-path out/foo src/client/app/Main.java src/foo/module-info.java  // --module-path <依存するモジュール場所(クラスファイル)>

モジュールの実行(クラスファイルで実行)

  • 使うモジュールを指定する・・・ --module-path (-p)
  • main()含むクラスを指定する・・・ --module (-m)
terminal.java
java  --module-path out/foo;out/client --module client/app.Main 
// --module <モジュール名+main()含むクラス名>

間接エクスポート

間接エクスポート.jpg

名前付きモジュール、その他のモジュール

  • 名前付きモジュール:module-info.class ある
  • その他モジュール:module-info.class ない(=モジュール名ない)
    • 自動モジュール:モジュールパス上にある
      • → 全てのパッケージをexports
      • → モジュールパス上の全てのモジュールをrequires
      • → 参照(下図)
    • 無名モジュール:クラスパス上にある
      • → 全てのパッケージをexports
      • → モジュールパス上の全てのモジュールをrequires
      • → 参照(下図)

※ モジュールパス・・・--module-path で指定したパス(モジュールのルートディレクトリ)

mumei .jpg
自動モジュール.jpg
※ 名前付きモジュールが自動モジュールをrequiresする時、自動モジュールの名前がない!!

  1. マニフェストファイルでモジュール名を定義する
  2. JARファイル名から取ってくる

モジュールの依存性確認

  • オプションで確認
terminal.java
--describe-module-d //モジュール記述子の情報出力(module-info.class)
--show-module-resolution //モジュールの解決(モジュールの呼び出し)を出力
  • コマンドで確認
jdeps.java
jdeps -summary (-s) //依存関係のサマリーだけ出力
jdeps -jdkinternals  //JDKのクラス・レベルの依存関係を検索
jdeps -dotoutput   //DOTファイルの出力先を指定

jlink コマンド

JREのカスタマイズツール

オプション 説明
--add-modules イメージに追加するモジュールを指定
--module-path (-p) モジュールパスを指定
--compress (-c) リソースの圧縮を有効化
--output 出力ディレクトリ

ServiceLoader

インタフェースの実装クラスを動的にロードする仕組み

  • インタフェース、実装クラス提供側
module-info.java
module foo {
    exports xlib;
    provides xlib.MyInter with xlib.XTest;  //provides <インタフェース> with <実装クラス>
}
  • 実装クラスを利用する側
module-info.java
module client {
    requires foo;
    uses xlib.MyInter;  // use <インタフェース>
}

ボトムアップ / トップダウン移行

Javaのアプリケーションをバージョンアップする
→ アプリをモジュール化する必要あり!
↓ 3つのモジュールを使用
システムの移行.jpg

ボトムアップ移行

最下位のモジュールから移行

  1. c.jar モジュール化 → 名前付きモジュールへ
    1. モジュールパス上へ配置
    2. 公開すべきパッケージを exports
  2. b.java → a.jar の順にモジュール化

ボトムアップ移行.jpg

トップダウン移行

最上位のモジュールから移行

  1. 全てのモジュールをモジュールパス上へ配置 → 自動モジュールへ
  2. a.jar からモジュール化 → 名前付きモジュール
    1. a.jarで必要なモジュールをrequires

トップダウン移行.jpg

並列処理

スレッド

プログラム実行 → Javaの実行環境がスレッド作成(処理の最小単位)→ 処理開始
→ 複数のスレッドに分けて実行= マルチスレッド

スレッド作成&開始する方法

  1. Thread クラスを継承
  2. Runnable インタフェースを実装
Main.java
public class Main {
    public static void main(String[] args) {
        //①Thread クラスを継承した時のインスタンス化
        ThreadA theadA = new TheadA();

        //②Runnable インタフェースを継承した時のインスタンス化 = Threadクラスのコンストラクタを呼ぶ
        Thread  threadA = new Thread(new ThreadA());

        //②Runableインタフェースをラムダ式で記述したパターン
        Thread  threadA = new Thread(() -> {
            System.out.println("①Threadクラスを継承");
        });

        //(共通)start() -> run()呼び出し -> スレッド開始
        threadA.start();

    }
}

//①Threadクラスを継承
class ThreadA extends Thread {
    public void run() {
        System.out.println("①Threadクラスを継承");
    }
}

//②Runnable インタフェースを実装
class ThreadA implements Runnable {
    public void run() {
        System.out.println("②Runnable インタフェースを実装");
    }
}

スレッドの状態

  • start() → 実行可能状態
  • run() → 実行状態
  • 終了したスレッド → 再実行✖️(2回 start()できない)

スレッドの状態.jpg

スレッドの優先度

実行可能状態のスレッドがいっぱい → 優先度の高いスレッドから実行

  • Threadクラスの優先度に関わるメソッド
メソッド名 説明
static Thread currentThread() いま実行中のスレッドを取得
final String getName() スレッド名を返す
final int getPriority() 優先度を返す
final void setPriority(int value) 優先度を変更、1~10、デフォルト=5

スレッドを制御

  • スレッド制御するThreadのメソッド
メソッド名 処理
static void sleep(long time) throws interruptedException スレッドをtimeミリ秒休止
final void join() throws interruptedException 実行中のスレッドが終わるまで待機
static void yield() 実行スレッドを休止→他スレッドに実行機会与える
void interrupt() ①休止中のスレッドに割り込む
②割り込まれたスレッド:interruptedExceptionを受け取る
③処理を再開
Main.java
class Main {
    public static void main(String[] args) {
        Thread  threadA = new Thread(() -> {
            System.out.println("theadA: sleep 開始");
            try {
                Thread.sleep(5000);  //sleep()を呼ぶと、このスレッドは(引数)ミリ秒休止、ThreadAのsleep()
            } catch (InterruptedException e) {
                System.out.println("threadA:割り込みをキャッチしました");
            }
            System.out.println("threadA:処理再開");
        });
        threadA.start();

        try {
            System.out.println("main: sleep 開始");
            Thread.sleep(2000); //mainのsleep()
            System.out.println("main: sleep 終了");
            threadA.interrupt();  //スレッドへ割り込み
        } catch (InterruptedException e) {
            System.out.println("main: 割り込みをキャッチしました");
        }
    }
}

//結果
main: sleep 開始
threadA: sleep 開始
main: sleep 終了
threadA: 割り込みをキャッチしました
threadA: 処理開始

排他制御、同期制御

複数のスレッド → 共有のオブジェクト使用 → スレッドを制御

  • 排他制御: オブジェクトを独占して実行する
  • 同期制御: スレッド同士で実行のタイミングを合わせる
  • Objectクラスの同期制御メソッド
メソッド名 処理
final void wait() throws InterruptedException 現在のスレッドを待機
final void wait(long time) throws InterruptedException time時間スレッドを待機
final void notify() オブジェクトの待機中スレッド1つ再開
final void notifyAll() オブジェクトの待機中スレッド全て再開

synchronizedがないものに使用すると、IllegalMonitorStateException

Main.java
public class Main {
    public static void main(String[] args) {
        Share share = new Share();
        ThreadA threadA = new ThreadA(share);
        ThreadB threadB = new ThreadB(share);
        threadA.start(); threadB.start();
    }
}
class ThreadA extends Thread {
    private Share share;
    public ThreadA(Share share) { this.share = share };
    public void run() {
        for(int i = 0; i < 5; i++){ share.set(); }
    }
}
class ThreadB extends Thread {
    private Share share;
    public ThreadA(Share share) { this.share = share };
    public void run() {
        for(int i = 0; i < 5; i++){ share.print(); }
    }
}
//共有して使うオブジェクト
class Share {
    private int a = 0;
    private String b;
    public synchronized void set() {  //このメソッド実行時、Shareオブジェクトはロック
        while(a != 0) {
            try {
                wait();
            } catch(InterruptedException e) { }
        }
        notify();
        a++; b = "data";
        System.out.println("set() a :" + a + " b : " + b);
    }
    public synchronized void print() {  ////このメソッド実行時、Shareオブジェクトはロック
        while(b == null) {
            try {
                wait();
            } catch(InterruptedException e) { }
        }
        notify();
        a--; b = null;
        System.out.println("  print() a :" + a + " b : " + b);
    }
}
//結果
set() a : 1 b: data
  print() a : 0 b: null
set() a : 1 b: data
  print() a : 0 b: null
set() a : 1 b: data
  print() a : 0 b: null
set() a : 1 b: data
  print() a : 0 b: null
set() a : 1 b: data
  print() a : 0 b: null

資源の競合

  • デットロック:ロックが一生解けなくなること
  • ライブロック:デットロックはされてないが、進まない処理を続けてる状態
  • スレッドスタベーション:スレッドが共有オブジェクトの使用を待ち続けている状態

並列コレクション

【拡張forでコレクションの繰り返し処理中の注意】

  • 繰り返し処理するために、ファストイテレータを使用
    • → ファストイテレータ=繰り返し処理中に変更の可能性があった場合
      • → 例外を発生して処理を中断する仕様
拡張for.java
Map<Integer, String> map = new Map<>();
map.put(1, "北海道"); map.put(2, "青森");
for(Integer key : map.keySet()) {
    map.remove(key);   //実行時エラー
}

java.util.concurrent パッケージ:並行処理機能のあるコレクションクラスを提供

Queue インタフェースを拡張したやつら

  • BlockingQueue インタフェースの実装クラス
インタフェース / クラス名 説明
BlockingQueue インタフェース 要素の追加・取得→キューの状態を見て待機する
SynchronousQueue クラス BlockingQueue を実装したキュー
LinkedBlockingQueue クラス リンクノードに基づいたキュー
ArrayBlockingQueue クラス 配列に基づいたキュー
  • BlockingQueue インタフェースのメソッド(4形式)
例外のスロー 特殊な値 ブロック タイムアウト
挿入 add(e) offer(e) put(e) offer(e, time, unit)
削除 remove() poll() take() poll(time, unit)
検査 element() peek() × ×
  • ブロック:操作が完了するまでスレッドをブロック

  • タイムアウト:処理ができなかったときの待機時間

    • e: 追加する要素
    • time: キューが空or一杯の場合→待機時間
    • unit: timeの単位
  • BlockingDeque インタフェースのメソッド

最初の要素
例外のスロー 特殊な値 ブロック タイムアウト
挿入 addFirst(e) offerFirst(e) putFirst(e) offerFirst(e, time, unit)
削除 removeFirst() pollFirst() takeFirst() pollFirst(time, unit)
検査 getFirst() peekFirst() × ×
最後の要素
例外のスロー 特殊な値 ブロック タイムアウト
挿入 addLast(e) offerLast(e) putLast(e) offerLast(e, time, unit)
削除 removeLast() pollLast() takeLast() pollLast(time, unit)
検査 getLast() peekLast() × ×

Map インタフェースを拡張したやつら

インタフェース / クラス名 説明
ConcurrentMap インタフェース
ConcurrentHashMap クラス ConcurrentMap を実装したクラス
  • ConcurrentMap インタフェースのメソッド
    • 1回の六で二つの処理を行う(↓ 例)
      • ①contanisKey()であるか確認
      • ②put()で格納する
メソッド名 説明
V putIfAbsent(K key, V value) 指定したキーにバリューがない場合、バリューをセット
boolean remove(Object key, Object value) キー&バリュー削除、キーない場合:何もしない
V replace(K key, V value) キー&バリューある場合、バリュー置換
boolean replace(K key, V oldValue, V newValue) 指定されたキー&バリューある場合、キーのバリュー置換
  • ArrayListクラス、Setインタフェースを拡張したやつら
クラス名 説明
CopyOnWriteArrayList 配列のコピーを作成
CopyOnWriteArraySet 内部でCopyOnWriteArrayListを使う
  • 並行処理機能を持ってないコレクションの例
ArrayList.java
arrayList<String> list = new ArrayList<String>(); // → List<String> list = new CopyOnWriteArrayList<String>();
list.add("A"); list.add("B"); list.add("C"); list.add("D");
new Thread( () -> {
    Iterator itr = list.iterator();
    while(itr.hasNext()) {
        System.out.println("ThreadA :" + itr.next());
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) { e.printStackTrace(); }
    }
} ).start();

try {
    Thread.sleep(1000);
} catch (InterruptedException e)  { e.printStackTrace(); }
list.add("E"); System.out.println("main: add()");
list.remove(2); System.out.println("main: remove()");  

//ConcurrentModificationException が発生する → 繰り返し処理中に要素の追加・削除を行なったため

→ CopyOnWriteArrayList を使うと解消する!!

CopyOnWriteArrayListを使った結果.txt
ThreadA : A
main: add()
main: remove()
ThreadA : B
ThreadA : C
ThreadA : D

【 変更が反映されない理由 】
→ CopyOnWriteArrary:イテレーターを生成した時点の状態を参照する → イテレータ生成後のリストへ追加・削除しても反映されない

Executor フレームワーク

スレッドコードを簡単に書ける!

  • Executorオブジェクト(インタフェース)
    • 指定したRunnable タスク(1つの処理)を実行するするオブジェクト
  • ExecutorService インタフェース
    • スレッドを終了するメソッド持つ
  • Executors クラス(実装クラス)
    • ExecutorServiceなどを取得するメソッド持つ
スレッド生成.java
Executor executor = //Executor オブジェクト
executor.execute(new RunnableTask());  // = new Thread(new(RunnableTask())).start()
  • ExecutorService インタフェース(Executorのサブインタフェース)
    • スレッドの終了を管理するメソッドあり
    • Executor:スレッドを実行するメソッドだけ

static ExecutorService newSingleThreadExecutor()

一つのスレッドでタスク処理する(Executorsクラスのメソッド)

newSingleThreadExecutor().java
ExecutorService service = null;
try {
    service = Executors.newSingleThreadExecutor();
    System.out.println("service.execute()");
    for (int i = 0; i < 3; i++) {  //1回目のタスク処理(execute())が終わらないと2回目が実行されない → newSingleThreadExecutorだから
        service.execute(() -> {      //execute(RunnableTask()) : RunnableTaskインタフェース内で実行する処理(タスク)を記述
            System.out.println("thread task");
            for(int a = 0; a < 5; a++) {
                try {
                    Thread.sleep(500);
                    System.out.print("*");
                } catch (InterruptedException e) { e.printStackTrace(); }
            }
            System.out.println();
        });
    } finally {
        service.shutdown();  // void shutdown() -> これ以降の新規タスクは受け付けない(待機中のタスクは実行)
        System.out.println("ex.shutdown()");
    }
}
// 結果
service.execute()
ex.shutdown()
thread task * * * * *
thread task * * * * *
thread task * * * * *

ExecutorServiceのメソッド

  • boolean isShutdown():Executorがシャットダウン→ true
  • boolean isTerminated():シャットダウン+タスク全て完了→ true
  • List<Runnable> shutdownNow():実行中のタスク停止→ 待機中タスクのリスト

Futureオブジェクトを返すメソッド

  • Future<?> submit(Runnable task)
  • Future<T> submit(Runnable task, T result)
    → Runnableタスク送信 → タスクが正常に完了したかのチェック
submit()メソッド.java
ExecutorService service = null;
try {
    service = Executors.newSingleThreadExecutor();
    Future<?> result1 = service.submit(() -> System.out.println("hello")); //タスクが正常に終了 → 戻り値:null
    System.out.println(result1.get());  // V get() -> タスク結果を取得(Futureインタフェースのメソッド)
    Future<Boolean> result2 = service.submit(() -> System.out.println("hello"), true); // 第一引数:Runnableタスク、第二引数:正常にタスクが終了した時に返す値
    System.out.println(result2.get()); 
} catch (InterruptedException | ExecutionException e) {
    e.printStackTrace();
} finally {
    if(service != null) service.shutdown();
}

//結果
hello
null  -> 正常にタスクが完了null
hello
true

java.util.concurrent.Callableインタフェース

処理結果をオブジェクトで返す(Runnableのrun()は戻り値void)

  • 関数型インタフェース
  • V call() throws Exception : タスク実行・結果を返す
  • Runnableインタフェースのrun()メソッド
    • void run()
Callableインタフェース.java
ExecutorService service = null;
try {
    service = Executors.newSingleThreadExecutor();
    Future<Date> result = service.submit(() -> new Date());  // V call() throws ... 実装
    System.out.println(result.get()): 
} catch(InterruptedException | ExecutionException e) {
    e.printStackTrace();
} finally {
    if(service != null) service.shutdown();
}

※ ExecutorService、Future、Threadの関係(イメージ図)・・・https://zenn.dev/yucatio/articles/14fe4507fde3d4

ScheduledExecutorService インタフェース(ExecutorService継承)

タスクのスケジュールができる

ScheduledExecutorService.java
ScheduledExecutorService service = null;
try {
    service = Executors.newSingleThreadScheduledExecutor();
    Runnable task1 = () -> System.out.println("task1");
    Callable<Date> task2 = () -> new Date();

    ScheduledFuture<?> result1 = service.schedule(task1, 3, TimeUnit.SECONDS); //指定された時間後、タスク実行
    ScheduledFuture<Date> result2 = service.schedule(task2, 3, TimeUnit.SECONDS);
    System.out.println(result2.get());
    
} finally {
    if(service != null) service.shutdown();
}

//結果
Fri Feb 21 13:48:29 JST 2020
task1
  • ScheduledFuture> scheduleAtFixedRate(Runnable command, long delay, long period, TimeUnit unit)
  • ScheduledFuture> scheduleWithFixedDelay(Runnable command, long delay, long period, TimeUnit unit)
scheduleWithFixedDelay().java
ScheduledExecutorService service = null;
try {
    service = Executors.newSingleThreadScheduledExecutor();
    Runnable task = () -> System.out.println(new Date());
    service.scheduleWithFixedDelay(task, 2, 2, TimeUnit.SECONDS);
    Thread.sleep(10000);  //mainスレッドを10秒間スリープして、その間にタスクを定期実行
} finally {
    service.shutdown();
}

//結果
Fri Feb 21 13:48:20 JST 2020
Fri Feb 21 13:48:22 JST 2020
Fri Feb 21 13:48:24 JST 2020
Fri Feb 21 13:48:26 JST 2020

スケジュール.jpg

スレッドプール

スレッドを複数用意 → 順次タスクを実行

  • ExcecutorService newCachedThreadPool()・・・新規スレッド作成、再利用できるスレッドは使う
  • ExcecutorService newFixedThreadPool(int nThreads)・・・固定数のスレッドを再利用(全てのスレッド使ってる場合、スレッドが開くまでタスクは待機)
  • ScheduledExcecutorService newScheduledThreadPool(int nThread)・・・newFixedThreadPoolと同様、+定期的なコマンド実行が可能
スレッドプール.java
ExecutorService service = null;
    service = Executors.newCachedThreadPool();  //新規スレッドを作成
    // service = Executors.newFixedThreadPool(2);  //2つのスレッドで実行、スレッドがあくまでタスクは待機
    Runnable task = () -> {
        String name = Thread.currentThread().getName();
        System.out.println(name + ": start");
        Thread.sleep(3000);
        System.out.println(name + ": end");
    };
    for(int i = 0; i < 5; i++){
        service.execute(task);
    }
    service.shutdown();

//結果(newCachedThreadPool()) 5つのスレッド生成 → 5回タスク実行
pool-1-thread-1 : start
pool-1-thread-4 : start
pool-1-thread-2 : start
pool-1-thread-5 : start
pool-1-thread-3 : start
pool-1-thread-5 : end
pool-1-thread-1 : end
pool-1-thread-2 : end
pool-1-thread-3 : end
pool-1-thread-4 : end

//結果(newFixedThreadPool(2))  2つのスレッドでタスクを実行
pool-1-thread-1 : start
pool-1-thread-2 : start
pool-1-thread-1 : end
pool-1-thread-2 : end

java.util.concurrent.CyclicBarrier クラス

複数スレッドで処理 → バリアポイント(待機する箇所)設定

バリアポイント.jpg

  • public CyclicBarrier (int num)
    • 引数分のスレッドが待機状態になると、バリアポイントを通過
  • public CyclicBarrier (int num, Runnable bariierAction)
    • バリアポイントを通る時、bariierActionを実行
  • public int await() throws InterruptedException, BrokenBarrierException
    • 指定されたスレッド数がくるまで待機(バリアポイント)
CyclicBarrier.java
public class Main {
    void exec (CyclicBarrier barrier) {
        System.out.println(Thread.currentThread().getName() + "start");
        Thread.sleep((int)(Math.random() * 3000));
        barrier.await();
        System.out.println(Thread.currentThread().getName() + "end");
    }
    public static void main(String[] args) {
        ExecutorService service = null;
        service = Executors.newFixedThreadPool(4);
        CyclicBarrier barrier = new CyclicBarrier(2, () -> System.out.println("task "));
        for (int i = 0; i < 4; i++) {
            service.execute(() -> new Main().exec(barrier));
        }
    }
    if(service != null) service.shutdown();
}
//結果
pool-1-thread-2start
pool-1-thread-3start
pool-1-thread-4start
pool-1-thread-1start
task
pool-1-thread-3end
pool-1-thread-1end
task
pool-1-thread-4end
pool-1-thread-2end

アトミック変数

アトミック:分割不可能な操作のこと

  • java.util.concurrent.atomicパッケージ:アトミック操作を簡単に実装できるクラスを提供
    → syhchronizedなど使ってロック制御しなくてもアトミック操作ができる!
  • java.util.concurrent.atomicパッケージの主なクラス
クラス名 説明
AtomicBoolean boolean型を使ってアトミック操作する
AtomicInteger int型を使ってアトミック操作する
AtomicLong long型を使ってアトミック操作する
AtomicReference 参照型を使ってアトミック操作する
AtomicInteger.java
//わからなすぎて飛ばす

パラレルストリーム

並行処理を行うストリーム

  • パラレルストリームを生成するメソッド
メソッド名 説明
default Stream parallelStream() Collectionインタフェースで提供
コレクションを元に生成
S parallel() BaseStream インタフェースで提供
ストリームを元に生成
boolean isParallel() BaseStream インタフェースで提供
パラレルストリームならtrue
S sequential() BaseStream インタフェースで提供
ストリームを元にシーケンシャルストリーム返す
パラレルストリーム生成.java
List<String> list = Arrays.asList("aaa", "bb", "c");
Stream<String> stream1 = list.parallelStream();
System.out.println("stream1:" + stream1.isParallel());
Stream<String> stream2 = list.stream();
System.out.println("stream2:" + stream2.isParallel());
Stream<String> stream3 = stream2.parallel();   //シーケンシャルストリーム→パラレルストリーム
System.out.println("stream3:" + stream3.isParallel());

//結果
stream1: true
stream2: false
stream3: true
パラレルストリーム処理.java
Arrays.asList("a", "b", "c", "d", "e")
      .stream()
      .forEach(System.out.print(s + " "));
System.out.println();
Arrays.asList("a", "b", "c", "d", "e")
      .parallelStream()
      .forEach(System.out.print(s + " "));

//結果
a b c d e   シーケンシャルストリーム
c e d a b   パラレルストリーム
ーーーーーーー
a b c d e  シーケンシャルストリーム
c d a b e  パラレルストリーム

※ パラレルストリーム・・・要素を並列に処理するので、実行ごとに処理順番はバラバラ

ForkJoinPool クラス(ExecutorServiceの実装クラス)

  • 重い計算を小さいタスクに分割 → 複数スレッドで実行 → 高速処理
ForkJoinPool.java
System.out.println("commonPool : " + ForkJoinPool.commonPool().getParallelism()); //デフォルトの並行して実行できる処理数

IntStream.range(0, 100)
         .forEach(i -> System.out.println(Thread.currentThread().getName() + " : " + i));

//結果
commonPool: 7
ForkJoinPool.commonPool-worker-9: 56    //スレッド名=ForkJoinPool.commonPool
main: 65
ForkJoinPool.commonPool-worker-15: 53   //パラレルストリーム=ForkJoinPoolを使う
・・・省略・・・

パラレル処理をするパイプライン

パラレルなパイプライン.java
Arrays.asList("sakura", "nagi", "michio")
      .parallelStream()
      .map( s -> {System.out.print(s + " "); return s.toUpperCase(); })
      .forEach(s -> System.out.print(s + " "));

//結果
nagi sakura SAKURA NAGI michio MICHIO

※ 並行処理なので、どの要素から実行されるかはわからない
※ map()、forEach()はそれぞれ独立した処理なので、どっちから実行されるかはわからない

パラレル処理と順序に依存するパイプライン

順序.java
List<Integer> data = Arrays.asList(1, 2, 3, 4);
Optional<Integer> result = data.parallelStream().findFirst();
System.out.println(result.get());

//結果
1

※ 並行処理の場合でも、最初の要素が返る。でも、どの要素から実行されるか不定なので、最初の要素が処理されるまで時間かかる。

パラレル処理と reduce()、collect() → どちらもパラレルストリームの時のみ第三引数が有効になる

  • U reduce(U value, BiFunction bf, BinaryOperator bo)・・・第三引数=途中の集約処理
reduce().java
Integer total = Arrays.asList(10, 20, 30, 40, 50)
                    .parallelStream()
                    .reduce(0,
                           (sum, a) -> {  //①
                                         System.out.println("sum:" + sum + " a:" + a);
                                         return sum += a;
                            },
                           (b, c) -> {   //②
                                        System.out.println("b:" + b + " c:" + c);
                                        return b + c;
                            });
System.out.println("total : " + total);

//結果
sum:0 a:10  //①
sum:0 a:20  //①
sum:0 a:50  //①
sum:0 a:40  //①
b:10 c:20   //②  
b:40 c:50   //②
sum:0 a:30  //①
b:30 c:90   //②
b:30 c:120  //②
total : 150
  • U collect(Suppiler su, BiConsumer bc, BiConsumer bc)・・・第三引数=途中の集約処理
collect().java
List<String> data = Arrays.asList("soccer", "baseball", "bastcketball");
List<String> list = data.parallelStream()
                        .collect(() -> new CopyOnWriteArrayList<>(),      //結果を格納するオブジェクト
                                 (plist, s) -> plist.add(s.toUpperCase()),//要素ごとに行う処理
                                 (alist, blist) -> alist.addAll(blist));  //集計結果のマージ処理
for(String s : list) { System.out.print(s + " "); }

//結果
SOCCER BASEBALL BASTCKETBALL

パラレルストリームで使うCollectors クラスのメソッド

  • groupingByConcurrent(Function f)
  • toConcurrentMap(Function f1, Function f2, BinaryOperator bo)
Collectors.java
//groupingByConcurrent(Function f)
Stream<String> stream1 = Stream.of("aiko", "saki", "kanae", "suzu", "ryo").parallel();
Map<String, List<String>> map1 = stream1.collect(Collectors.groupingByConcurrent(s -> s.substring(0, 1)));
System.out.println(map1);
System.out.println("map1のクラス名 :" + map1.getClass());

//toConcurrentMap(Function f1, Function f2, BinaryOperator bo)
Stream<String> stream2 = Stream.of("aiko", "saki", "kanae", "suzu", "ryo").parallel();
Map<Integer, String> map2 = stream2.collect(Collectors.toConcurrentMap(String::length,
                                                                       s -> s,
                                                                       (s1, s2) -> s1 + ":" + s2));
System.out.println(map2);
System.out.println("map2のクラス名 :" + map2.getClass());

//結果
{a=[aiko], r=[ryo], s=[saki, suzu], k=[kanae]}
map1のクラス名 class java.util.concurrent.ConcurrentHashMap
{3=ryo, 4=sakiaikosuzu, 5=kanae}
map2のクラス名 class java.util.concurrent.ConcurrentHashMap
1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?