enum P9
enum
特定の値を列挙するオブジェクトのこと
→ 列挙型の実体 = 定数、メソッドを持つクラス
注意点メモ
・明示的なインスタンス化❌
・アクセス:列挙型.クラス定数
・コンストラクタ、メソッド、変数定義可
・抽象メソッド、インタフェースのオーバーライド、実装可
・定数は書いた順番に管理
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) {・・・・・} //引数で指定した値を返す
}
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 . <メソッド名> - メンバへのアクセス
→ クラスが優先される
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 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
クラスの中に定義したクラス
- 存在を外部から隠したいクラスをネスト
- 外側のクラスのメンバの一つ
- インタフェース、抽象クラスを定義できる
class Outer {
class A {} //非staticクラス(インナークラス)→ ローカルクラス、匿名クラス
static class B {} //staticクラス
}
ネストクラスのルール
クラス | ルール |
---|---|
staic / 非static 共通 | ・外側のクラスと同名❌ ・アクセス修飾子使える ・abstruct, final 使える |
staticのみ | ・(非)staticメンバもてる ・外側クラスのインスタンスメンバへアクセス❌ |
非staticのみ | ・staticメンバもてない ・外側クラスのインスタンス / static変数へアクセス⭕️ |
ネストクラスへのアクセス
1. 別のクラスからアクセス
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. 外側のクラスからアクセス
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
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
- コンストラクタ 定義できない
interface MyInter { void method(); }
class Outer {
void method() {
new MyInter() { //new スーパークラス・インタフェース() {};
public void method() {}
}.method(); //匿名クラスのメソッドの呼び出し
}
}
アノテーション(注釈)
目的
コードを解釈するコンパイラ、実行するJVMに付加情報を伝える
主なアノテーション
アノテーション | 付加情報 |
---|---|
@Override | スーパークラスのメソッドをオーバーライドする |
@Functionallnterface | 関数型インタフェースである |
@Deprecated | 非推奨の要素である |
@SuppressWarnings | コンパイラの警告を無効にする |
@SafeVarargs | 安全でない可変長引数の警告を無効にする |
@Functionallnterface
関数型インタフェースに付与(任意)
関数型インタフェースの要件を満たしてるかチェック
- 要件
@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: 注釈のついたプログラムが非推奨であることを伝える
- since = "" (default)
@SuppressWarnings( 引数 )
引数に指定したコンパイラの警告を無効にする
引数が必須
指定できる主な警告
警告 | 内容 |
---|---|
unchecked | 型を使う時などの警告 |
deprecation | @Deprecatedがつけられた要素の警告 |
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
-
@SuppressWarnings( 引数 ) と @SafeVarargs の違い
-
@SafeVarargs
- 付与した場所の呼び出し元の警告まで無効
-
@SuppressWarnings( 引数 )
- 付与した場所のみ無効
-
@SafeVarargs
カスタムアノテーション P65
独自のアノテーションを作成する
@interface -> アノテーション型となる(java.lang.annotation.Annotationインタフェースを継承したインタフェースになる)
- 独自アノテーションに適応させるアノテーション(メタアノテーションについて)
メタアノテーション | 内容 |
---|---|
@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種類のカスタムアノテーションを定義する必要あり
import java.lang.annotation.*;
@Repeatable(MyAnnotContainer.class) //複数回使うアノテーションに@Repeatable 付与
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnot {
public String value();
}
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()
- エラーメッセージを取得
- void printStackTrace()
catch ブロックの注意点 P93
複数のcatchブロック
} catch (例外クラス 変数名) {
} catch (例外クラス 変数名) {
} //例外クラスに継承関係がある場合、サブクラスから定義する。スーパーから定義するとコンパイルエラー
マルチキャッチ
} catch (NumberFormatException | ArithmeticException e) {
//継承関係のあるクラスを列挙できない
//e は final
}
throws してるメソッド
オーバーライドする時の注意点
- 例外クラス:同じ or サブクラス
- RuntimeException, サブクラス:スーパークラスの例外に関係なくスローできる
- スーパーでthrowsされてても、サブでthrowsしなくてもいい
try-with-resources
暗黙的にリソースを解放してくれる
try-with-resource のとき:tryブロックのみ⭕️
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ブロック");
}
}
}
tryブロック内の実装
close():obj2
close():obj1 //複数のリソースを取得した場合:後に取得したclose()から実行
catchブロック:SQLException
finallyブロック
例外の抑制 P107
参考:https://relearn-java.com/exception/
- try-catch(-finally):複数の例外が発生した場合、伝わる例外が置き換えられちゃう問題
- なぜ置き換えられるか → 例外は一つしか伝播できない!
- try-resource-with:最初に発生した例外を伝え、その他はメソッドで取得できるように抑制
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 =アサーション無効
private void check(int point) {
assert ( point > 0 ) : "point = " + point;
//checkメソッドの処理↓
}
コレクション 過去問外しすぎもう一回やる
ラッパークラス
基本データ型の値 → 参照型として扱う専用のクラス
基本データ型との違い・・・値を操作するメソッドがある
コレクション
複数のオブジェクトをまとめて扱うやつ
インタフェース名 | データ(要素)の管理方法 |
---|---|
List(≒ サイズ変更できる配列) | ・順序づけて管理 ・重複 ⚪︎ |
Set(≒ 袋の中に要素を入れる) | ・順不同で管理 ・重複 × |
Queue | ・FIFO(First In First Out)で管理 |
Map | ・「キー」+「値」で管理 ・キーの重複 × |
イテレータ
コレクションの要素へ順番にアクセスするオブジェクト(≒ 要素を指すカーソル)
TreeSet<String> set = new TreeSet<String>();
set.add("C"); set.add("A"); set.add("B");
Iterator<String> iter = set.iterator(); //Iterator iterator():Collectionインタフェースで宣言,各オブジェクトで実装。要素を指すポインタを返す
while (iter.hasNext()) { //hasNext():次の要素がある場合、true
System.out.println(iter.next() + " "); //next():指してる要素を返す
}
A B C
Queue インタフェース
FIFO(First In First Out)でデータ管理
- Queue インタフェースの主なメソッド
操作 | メソッド名 | 内容 |
---|---|---|
挿入 | boolean add(E e) boolean offer(E e) |
要素追加=true、容量制限で追加ムリ=IllegalStateException 要素追加=true、それ以外=false |
削除 | E remove() E poll() |
先頭の取得・削除、空の場合=NoSuchElementException 先頭の取得・削除、空の場合=null |
検査 | E element() E peek() |
先頭の取得、空の場合=NoSuchElementException 先頭の取得・削除、空の場合=null |
ジェネリクス
各コレクションのクラス:<⚪︎>に扱う型を指定
- ジェネリクス という機能
- <> =「型パラメータリスト」、扱う型を指定するために使う
ArrayList<String> list = new ArrayList<String>();
ArrayLisy<Object> list2 = new ArrayList<String>(); //型パラメータリストが異なるのでコンパイルエラー
ダイヤモンド演算子
ArrayList<String> list = new ArrayList<>(); //左辺の型パラメータリストから類推できるので、右辺は省略。 <>=ダイヤモンド演算子
ジェネリクスを使ってクラス定義
メリット
- 独自クラスを定義 → クラス生成時に扱う型を決められる!
class Gen<T> { //クラス名の後に型パラメータリストを指定(仮の型: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);
}
}
ジェネリクスを使ってメソッド定義
型パラメータリストの有効範囲=メソッド内だけ
class Gen {
public <T> T methodA(T value) { return value; } //戻り値・引数=型パラメータ使用
}
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>なしでも⚪︎
}
}
ジェネリクスを使ってインタフェース定義
インタフェースはインスタンス化できない
→ 実装するクラス側で型を指定する
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);
}
}
継承を使ったジェネリクス
型パラメータリスト=どんな型でも指定できる!
→ 指定できる型を限定したい時に継承を使う
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のサブクラスでないため、コンパイルエラー
}
}
ワイルドカードを使ったジェネリクス
? を使って型パラメータを指定 → 型は実行時でないとわからない
:
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インタフェースを実装
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インタフェースを実装
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)
- 引数の配列からリストを作成
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ダメ
関数型インタフェース 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の拡張 |
- ラムダ式の書き方
Function<String, String> f1 = (String str) -> { //引数が一つの場合=()省略可、インタフェースの宣言で、引数の型は分かるので省略可
return "Hello " + str; //一行の場合={}省略可、{}省略した場合、return; 省略可
}
String str1 = f1.apply("HY");
Function<String, String> f1 = str -> "Hello " + str
String str1 = f1.apply("HY");
- ラムダ式が外側の変数を使うとき→その変数は final でないといけない
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;
}
}
}
public static void main(String[] args) {
int i = 25;
Supplier<Integer> s = () -> i; //アクセスは問題ない
i++; //i は final なのでコンパイルエラー
System.out.println(s.get());
}
メソッド参照
インタフェース実装時、抽象メソッドの引数(個数・型)= 処理内のメソッドの引数(個数・型)→ メソッド参照で書ける
- staticメソッド参照
- インスタンスメソッド参照
- コンストラクタ参照
staticメソッド参照( 処理内のメソッド:staticメソッド)→ クラス名 :: メソッド名
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);
インスタンスメソッド参照( 処理内のメソッド:インスタンスメソッド)→ インスタンス変数名 :: メソッド名
List<Integer> list = List.of(3, 1, 2);
//ラムダ式
list.forEach( a -> System.out.print(a + " ") ); //Iterableインタフェースから継承したforEach()メソッド=コレクションの各要素に処理をする Comsumer インタフェースのaccept()が実装されている。
//インスタンスメソッド参照
list.forEach(System.out::print + " ") //コンパイルエラー= +演算子などで処理はできない!
//ラムダ式
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メソッド参照みたい...)
//ラムダ式
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"));
//IntegerクラスのhashCode() → static・インスタンスメソッドどちらもある
//ラムダ式
Function<Integer, Integer> obj = i -> i.hashCode(); //hashCode()=インスタンスメソッド
//staticメソッド参照
Function<Integer, Integer> obj = Integer::hashCode(); //コンパイルエラー=どちらか分からない
// → こういう場合、ラムダ式で実装すること!
コンストラクタ参照 クラス名::new
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; }
}
//Function<Integer, String[]> array = i -> new String[i];
Function<Integer, String[]> array = String[]::new;
System.out.println(array.apply(10).length);
基本データ型に特化した関数型インタフェース
- 引数、戻り値に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) |
//IntFunction<String[]> array = index -> new String[index];
IntFunction<String[]> array = String[]::new;
String[] obj1 = array.apply(5);
IntSupplier obj2 = "Java"::length;
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) |
ObjectIntConsumer | void accept(T t, int value) |
IntToDoubleFunction f1 = (int i) -> { return i * 3.14 }
IntToDoubleFunction f2 = (Integer i) -> { return i * 3.14 } //コンパイルエラー=引数は自動変換されない
Double ans1 = f1.applyAsDouble(100); // OK=戻り値は自動変換される
関数型インタフェースの合成
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"));
}
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
- 複数の処理をつなぐ仕組み(=パイプライン処理)をもってる
- パイプライン処理には、データソースが必要(処理のもとになるから)
- データソースとなるクラスは、ストリーム生成するメソッド持ってる
<処理の流れ>
- データソース
- ストリームオブジェクト生成
- 処理(パイプラインの途中) → 中間操作
- 処理(最後) → 終端操作
ストリームの種類
インタフェース名 | 説明 |
---|---|
Stream<T> | 汎用的なストリーム |
IntStream | int型に対応したストリーム |
LongStream | Long型に対応したストリーム |
DoubleStream | Double型に対応したストリーム |
終端操作を行うメソッド
//引数=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回だけ!!
long result = Stream.of("a", "b", "c").count(); //要素の数を返す
Stream<String> stream1 = Stream.of("a", "b", "c");
stream1.forEach(System.out::print); //引数で指定されたアクションを各要素に行う、引数=Consumerインタフェース
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
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);
//ストリームオブジェクトを配列に変換
int[] ary1 = IntStream.range(1, 10).toArray(); //IntStreamインタフェースで提供されるtoArray()メソッド、戻り値=int[]
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 というオブジェクト
Optional<Integer> op = Optional.of(10); // Optional.of() -> 引数で指定された値を持つOptionalを返す
System.out.println(op.get()); // T get() -> ある:値、ない:NoSuchElementException
op.isPresent(); // boolean isPresent() -> ある:true、ない:false
戻り値が Optional の終端操作
// Optional<T> max(Comparator com) -> comparatorに従って最大要素を取り出す
List<String> data = Arrays.asList("aaa", "bb", "c");
Optional<String> max = data.stream().max(Comparator.naturalOrder());
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));
- Optional.get()のNoSuchElementException を自身で指定したい(終端操作ではなく、Optionalクラスのメソッド)
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(): 重複を除く要素のストリームを返す
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個分の要素を持つストリームを返す
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(): 引数の処理をしたストリームを返す
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要素=多結果
- → 入れ子になっているストリームを平坦化する
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 + " "));
- sorted(): 自然順序に並べ替え
- sorted(Comparator.〇〇()): 明示的に並べ替え順序を指定
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インタフェース(戻り値なし)→ デバック機能として使う
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>
IntStream stream = Stream.of(1, 2, 3);
Stream<Integer> stream = stream.boxed();
ストリームインタフェースの暗黙の型変換をするメソッド
- IntStream、LongStreamインタフェースで提供
インタフェース名 | メソッド |
---|---|
IntStream | LongStream asLongStream() |
↑ | DoubleStream asDoubleStream() |
LongStream | DoubleStream asDoubleStream() |
IntStream streami = Stream.of(2, 3, 5);
DoubleStream streamd = streami.asDoubleStream();
終端操作 ~ collect()メソッド ~ P246
ストリームの要素 → まとめて1つのオブジェクトを取得
collect()メソッド: 引数にCollectorクラスのメソッドを指定
Collectorsクラスのメソッド
- toList()、joining()、summingInt()、averagingInt()
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
- toSet()、toMap()メソッド: Set、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)
Stream<String> stream = Stream.of("tatsumi", "igarashi", "kojima");
Map<Integer, String> map = stream.collect(Collectors.toMap(
s -> s.length(),
s -> s,
(s1, s2) -> s1 + ":" + s2)); //第三引数=キーが同じ場合の処理
- toMap(Function key, Function value, BinaryOperator function, Supplier mapSuppiler)
Stream<String> stream = Stream.of("tatsumi", "igarashi", "kojima");
Map<Integer, String> map = stream.collect(Collectors.toMap(
s -> s.length(),
s -> s,
(s1, s2) -> s1 + ":" + s2),
TreeMap::new); // 第4引数=格納するMapを指定
- Collectors <T,?,Map <K,List<T>>> groupingBy(Function<T> function) → 引数が一つの場合
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) → 引数が二つの場合
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())); //第3引数=グループ化した要素同士に行いたい処理
//結果: {a=akkoami, b=bellebob, n=nao}
- Collectors <T,?,Map <K,List<T>>> groupingBy(Function<T> function, Suppiler suppiler,Collector collect) → 引数が3つの場合
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()));
- partitioningBy(Predicate predicate)
→ グルーピング処理をPredicateでやる
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)
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(): ストリームの各要素に行いたい処理を指定
Stream<String> stream = Stream.of("akira", "rimi", "sonoko");
String result = stream.collect(Collectors.maping(
s -> s.toUpperCase(), //第1引数=各要素に行いたい処理
Collectors.joining(":"))); //第2引数=マップ後に行いたい処理
//結果: AKIRA:RIMI:SONOKO
- maxBy(Comparator comparator)、minBy(Comparator comparator):最大値、最小値を取得
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 |
今の実行環境で参照できるモジュール一覧を表示
% java --list-modules
java.base@11
java.compiler@11
モジュール定義
module foo {
exports xlib; //exports:どのパッケージを公開するか指定
requires java.base; //requires:依存するモジュール指定、java.base:全てのモジュールへ暗黙的に含まれるから、省略可能
}
モジュールのコンパイル
- ライブラリを提供する側
javac -d out/foo src/foo/ylib/XTest.java // javac -d <クラスファイルの生成場所> <コンパイルするソースファイル場所>
javac -d out/foo src/foo/module-info.java
- ライブラリを利用する側
javac -d out/client --module-path out/foo src/client/app/Main.java src/foo/module-info.java // --module-path <依存するモジュール場所(クラスファイル)>
モジュールの実行(クラスファイルで実行)
java --module-path out/foo;out/client --module client/app.Main
// --module <モジュール名+main()含むクラス名>
間接エクスポート
名前付きモジュール、その他のモジュール
- 名前付きモジュール:module-info.class ある
- その他モジュール:module-info.class ない
- 自動モジュール:モジュールパス上にある
- 無名モジュール:クラスパス上にある
- → 全てのパッケージをexports
- → モジュールパス上全てのモジュールをrequires
- → 参照が違う
モジュールの依存性確認
- オプションで確認
--describe-module //モジュールの説明出力
--show-module-resolution //モジュールの解決(モジュールの呼び出し)を出力
- コマンドで確認
jdeps -summary (-s) //依存関係のサマリーだけ出力
jdeps -jdkinternals //JDKのクラス・レベルの依存関係を検索
jdeps -dotoutput //DOTファイルの出力先を指定
jlink コマンド
JREのカスタマイズツール
オプション | 説明 |
---|---|
--add-modules | イメージに追加するモジュールを指定 |
--module-path (-p) | モジュールパスを指定 |
--compress (-c) | リソースの圧縮を有効化 |
--output | 出力ディレクトリ |
ServiceLoader
インタフェースの実装クラスを動的にロードする仕組み
- インタフェース、実装クラス提供側
module foo {
exports xlib;
provides xlib.MyInter with xlib.XTest; //provides <インタフェース> with <実装クラス>
}
- 実装クラスを利用する側
module client {
requires foo;
uses xlib.MyInter; // use <インタフェース>
}
ボトムアップ / トップダウン移行
Javaのアプリケーションをバージョンアップする
→ アプリをモジュール化する必要あり!
↓ 3つのモジュールを使用
ボトムアップ移行
最下位のモジュールから移行
- c.jar モジュール化 → 名前付きモジュールへ
- モジュールパス上へ配置
- 公開すべきパッケージを exports
- b.java → a.jar の順にモジュール化
トップダウン移行
最上位のモジュールから移行
- 全てのモジュールをモジュールパス上へ配置 → 自動モジュールへ
- a.jar からモジュール化 → 名前付きモジュール
- a.jarで必要なモジュールをrequires
並列処理
スレッド
プログラム実行 → Javaの実行環境がスレッド作成(処理の最小単位)→ 処理開始
→ 複数のスレッドに分けて実行= マルチスレッド
スレッド作成&開始
- Thread クラスを継承
- Runnable インタフェースを実装
public class Main {
public static void main(String[] args) {
//①Thread クラスを継承した時のインスタンス化
ThreadA theadA = new TheadA();
//②Runnable インタフェースを継承した時のインスタンス化
Thread threadA = new Thread(new ThreadA());
//②Runableインタフェースをラムダ式で記述したパターン
Thread threadA = new Thread(() -> {
System.out.println("①Threadクラスを継承");
});
//(共通)スレッド実行開始 -> 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()できない)
スレッドの優先度
優先度の高いスレッドから実行される
メソッド名 | 説明 |
---|---|
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を受け取る ③処理を再開 |
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
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
資源の競合
- デットロック:ロックが一生解けなくなること
- ライブロック:デットロックはされてないが、進まない処理を続けてる状態
- スレッドスタベーション:スレッドが共有オブジェクトの使用を待ち続けている状態
並列コレクション
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 インタフェースのメソッド
メソッド名 | 説明 |
---|---|
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<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 を使うと解消する!!
ThreadA : A
main: add()
main: remove()
ThreadA : B
ThreadA : C
ThreadA : D
【 変更が反映されない理由 】
→ CopyOnWriteArrary:イテレーターを生成した時点の状態を参照する → イテレータ生成後のリストへ追加・削除しても反映されない
Executor フレームワーク
スレッドコードを簡単に書ける!
-
Executorオブジェクト(インタフェース)
- スレッド生成&指定したRunnable タスク(1つの処理)を実行するするオブジェクト
-
Executors クラス(実装クラス)
- Executorオブジェクトを取得するためのメソッド持つ
Executor executor = //Executor オブジェクト
executor.execute(new RunnableTask()); // = new Thread(new(RunnableTask())).start()
- ExecutorService インタフェース(Executorのサブインタフェース)
- スレッドの終了を管理するメソッドあり
- Executor:スレッドを実行するメソッドだけ
static ExecutorService newSingleThreadExecutor()
一つのスレッドでタスク処理する(Executorsクラスのメソッド)
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(() -> {
System.out.println("thread task");
for(int a = 0; a < 5; a++) {
try {
Thread.sleep(500);
System.out.prinln("*");
} 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<?> submit(Runnable task) =ExecutorServiceインタフェースのメソッド
Runnableタスク送信 → タスク結果を表すFuture返す
ExecutorService service = null;
try {
service = Executors.newSingleThreadEcecutor();
Future<?> result1 = service.submit(() -> System.out.println("hello"));
System.out.println(result1.get()); // V get() -> タスク結果を取得(Futureインタフェースのメソッド)
Future<Boolean> result2 = service.submit(() -> System.out.println("hello"), true); // <T> Future<T> submit(Runnable task, T result) -> 第一引数:Runnableタスク、第二引数:正常にタスクが終了した時に返す値
System.out.println(result2.get());
} catch (InterruptException | ExecutionException e) {
e.printStackTrace();
} finally {
if(service != null) service.shutdown();
}
//結果
hello
null -> 正常にタスクが完了=null
hello
true
java.util.concurrent.Callableインタフェース
スレッド開始元に処理結果(オブジェクト)を返す
- 関数型インタフェース
- V call() throws Exception : タスク実行・結果を返す
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();
}
ScheduledExecutorService インタフェース(ExecutorService継承)
タスクのスケジュールができる
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(task1, 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)
ScheduledExecutorService service = null;
try {
service = Executors.newSingleThreadScheduledExecutor();
Runnable task = () -> System.out.println(new Date());
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
スレッドプール
複数のスレッドを用意
ExecutorService service = null;
service = Executors.newCachedThreadPool(); //新規スレッドを作成
// service = Executors.newFixedThreadPool(2);
Runnable task = () -> {
String name = Thread.currentTread().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 クラス
複数スレッドで処理 → バリアポイント(待機する箇所)設定
- public CyclicBarrier (int num)
- 引数分のスレッドが待機状態になると、バリアポイントを通過
- public CyclicBarrier (int num, Runnable bariierAction)
- バリアポイントを通る時、bariierActionを実行
- public int await() throws InterruptedException, BrokenBarrierException
- 指定されたスレッド数がくるまで待機(バリアポイント)
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 | 参照型を使ってアトミック操作する |
//わからなすぎて飛ばす
パラレルストリーム
並行処理を行うストリーム
- パラレルストリームを生成するメソッド
メソッド名 | 説明 |
---|---|
default Stream parallelStream() | Collectionインタフェースで提供 コレクションを元に生成 |
S parallel() | BaseStream インタフェースで提供 ストリームを元に生成 |
boolean isParallel() | BaseStream インタフェースで提供 パラレルストリームならtrue |
S sequential() | BaseStream インタフェースで提供 ストリームを元にシーケンシャルストリーム返す |
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
※ ForkJoinPoolのくだりは分からない
- パラレル処理をするパイプライン