Java SE 11 Programmer II 備忘録
Java SE 11 Programmer II で学習したことをまとめる
◆インナークラス
インナークラスの種類
- 非静的インナークラス(Member Inner Class)
- 静的ネストクラス(Static Nested Class)
- ローカルクラス(Local Class)
- 匿名クラス(Anonymous Class)
インナークラスの種類と特徴
種類 | 特徴 | アクセス修飾子の制限 |
---|---|---|
非静的インナークラス | - 外部クラスのメンバーとして定義されるインナークラス - 外部クラスのインスタンスが必要 - 外部クラスのメンバーに直接アクセス可能 |
- public , protected , private , デフォルト可 |
静的ネストクラス | - static キーワードで修飾されたインナークラス- 外部クラスのインスタンス不要 - 外部クラスの静的メンバーにのみアクセス可能 |
- public , protected , private , デフォルト可 |
ローカルクラス | - メソッド内やブロック内に定義されるクラス - メソッドのスコープ内でのみ有効 - 外部クラスのメンバーにアクセス可能( final ローカル変数も可) |
- アクセス修飾子は使用不可 |
匿名クラス | - 名前を持たないクラス - 1回限りのインスタンス化に使用 - インターフェースの実装またはクラスの拡張として記述 |
- アクセス修飾子は使用不可 |
- 非静的インナークラス
class OuterClass {
private String outerField = "Outer Field";
// 非静的インナークラス
class InnerClass {
void display() {
System.out.println("Accessing: " + outerField);
}
}
public void createInner() {
InnerClass inner = new InnerClass();
inner.display();
}
}
public class Main {
public static void main(String[] args) {
OuterClass outer = new OuterClass();
OuterClass.InnerClass inner = outer.new InnerClass();
inner.display();
}
}
- 静的ネストクラス
class OuterClass {
private static String staticField = "Static Field";
// 静的ネストクラス
static class StaticNestedClass {
void display() {
System.out.println("Accessing: " + staticField);
}
}
}
public class Main {
public static void main(String[] args) {
OuterClass.StaticNestedClass nested = new OuterClass.StaticNestedClass();
nested.display();
}
}
- ローカルクラス
class OuterClass {
void demonstrateLocalClass() {
String localVariable = "Local Variable";
// ローカルクラス
class LocalClass {
void display() {
System.out.println("Accessing: " + localVariable);
}
}
LocalClass local = new LocalClass();
local.display();
}
}
public class Main {
public static void main(String[] args) {
OuterClass outer = new OuterClass();
outer.demonstrateLocalClass();
}
}
- 匿名クラス
interface Greeting {
void sayHello();
}
public class Main {
public static void main(String[] args) {
// 匿名クラスでインターフェースを実装
Greeting greeting = new Greeting() {
@Override
public void sayHello() {
System.out.println("Hello from Anonymous Class");
}
};
greeting.sayHello();
// 匿名クラスでRunnableを実装
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Running in Anonymous Class Thread");
}
});
thread.start();
}
}
- 外部クラス内では、インナークラスのprivateフィールドにもアクセス可能
- 匿名クラスではコンストラクタの定義はできない(初期化子を利用)
- 匿名クラスでは独自メソッドの定義ができるが、varで定義する必要がある
◆インターフェース
インターフェースのstaticメソッドの特徴
1. クラスのstaticメソッドと同じ扱い
インターフェースのstaticメソッドはそのインターフェースに直接属するメソッド
実装クラスではなく、インターフェース名を使って呼び出す
2. 実装クラスには継承されない
staticメソッドはインターフェースに属するため、実装クラスから直接呼び出すことはできない
3. オーバーライドはできない
4. インターフェースのデフォルトメソッドとの混同に注意
インターフェースのデフォルトメソッド(default)は、実装クラスに継承されるインスタンスメソッドだがstaticメソッドは継承されない
5. インターフェースのstaticメソッドは多重継承には影響しない
複数のインターフェースに同名のstaticメソッドが存在しても、コンフリクト(競合)は発生しない。どのstaticメソッドを呼び出すかはインターフェース名で明確に指定
interface InterfaceA {
static void staticMethod() {
System.out.println("Static method in InterfaceA");
}
}
interface InterfaceB {
static void staticMethod() {
System.out.println("Static method in InterfaceB");
}
}
public class Main {
public static void main(String[] args) {
InterfaceA.staticMethod(); // Static method in InterfaceA
InterfaceB.staticMethod(); // Static method in InterfaceB
}
}
まとめ
ポイント | 注意点 |
---|---|
呼び出し方法 | インターフェース名を使って呼び出す(実装クラスからは呼び出せない)。 |
継承について | 実装クラスに継承されないため、実装クラス内でそのまま使用することはできない。 |
目的 | ユーティリティ的なメソッドや補助的な処理に限定して使用する。 |
デフォルトメソッドとの違い | デフォルトメソッドはインスタンスメソッドとして動作し、実装クラスに継承されるが、static メソッドはされない。 |
多重継承 | 複数のインターフェースに同名のstatic メソッドがあっても競合しない(インターフェース名で区別)。 |
◆ジェネリクス
ジェネリクスの目的
型安全性: コンパイル時に型チェックを行い、実行時のClassCastExceptionを防ぐ。
コードの再利用: 型をパラメータ化することで、さまざまな型に対応する汎用コードを記述可能。
ジェネリッククラス
class Pair<K, V> {
private K key;
private V value;
public Pair(K key, V value) {
this.key = key;
this.value = value;
}
public K getKey() {
return key;
}
public V getValue() {
return value;
}
}
ジェネリックメソッド
public class Utility {
public static <T> void printArray(T[] array) {
for (T element : array) {
System.out.print(element + " ");
}
}
}
ワイルドカード(?)と境界
ワイルドカード | 意味 | 使用例 |
---|---|---|
? |
任意の型。どんな型でも許容される。 | List<?> |
? extends T |
TまたはTのサブクラスを許容(上限境界)。主に読み取り専用として使用される。 | List<? extends Number> |
? super T |
TまたはTのスーパークラスを許容(下限境界)。主に書き込み専用として使用される。 | List<? super Integer> |
public static void printNumbers(List<? extends Number> numbers) {
for (Number num : numbers) {
System.out.println(num);
}
}
public static void addNumbers(List<? super Integer> list) {
list.add(10); // OK: IntegerはIntegerまたはそのスーパークラスに適合
}
制限事項
-
プリミティブ型は使えない
ジェネリクスではintやdoubleなどのプリミティブ型は直接使用できない。ラッパークラスを利用する。 -
ジェネリクスと配列の制限
ジェネリクス型の配列は直接作成できない。これは型消去による安全性の制約
◆関数型インターフェース
インターフェース | 概要 | メソッド |
---|---|---|
Function<T, R> |
1つの引数を受け取り、結果を返す関数を表す。 |
apply , andThen , compose
|
BiFunction<T, U, R> |
2つの引数を受け取り、結果を返す関数を表す。 |
apply , andThen
|
UnaryOperator<T> |
入力と出力の型が同じFunction<T, T> の特化型。 |
apply , andThen , compose
|
BinaryOperator<T> |
2つの引数を受け取り、結果を返すBiFunction<T, T, T> の特化型。 |
apply , andThen
|
Predicate<T> |
1つの引数を受け取り、真偽値(boolean )を返す関数を表す。 |
test , and , or , negate
|
BiPredicate<T, U> |
2つの引数を受け取り、真偽値(boolean )を返す関数を表す。 |
test , and , or
|
Consumer<T> |
1つの引数を受け取り、値を消費する(結果を返さない)。 |
accept , andThen
|
BiConsumer<T, U> |
2つの引数を受け取り、値を消費する(結果を返さない)。 |
accept , andThen
|
Supplier<T> |
引数を受け取らずに結果を生成して返す。 | get |
Functionインターフェース
1つの引数を受け取り、それに基づいて値を返す関数
@FunctionalInterface
public interface Function<T, R> {
R apply(T t); // 引数Tを受け取り、結果Rを返す
}
- apply
定義: R apply(T t)
概要: 入力を処理し、結果を返す。
Function<String, Integer> stringLength = str -> str.length();
System.out.println(stringLength.apply("Java")); // 出力: 4
- andThen
定義: Function andThen(Function super R, ? extends V> after)
概要: この関数の結果を別の関数に渡してさらに処理を行う。
Function<Integer, Integer> multiplyBy2 = x -> x * 2;
Function<Integer, String> toString = x -> "Result: " + x;
Function<Integer, String> combined = multiplyBy2.andThen(toString);
System.out.println(combined.apply(5)); // 出力: Result: 10
- compose
定義: Function compose(Function super V, ? extends T> before)
概要: この関数を適用する前に、別の関数を適用する。
Function<String, Integer> stringLength = str -> str.length();
Function<Integer, String> toString = x -> "Value: " + x;
Function<Integer, Integer> composed = stringLength.compose(toString);
System.out.println(composed.apply(12345)); // 出力: 12 (文字列 "Value: 12345" の長さ)
Predicateインターフェース
1つの引数を受け取り、真偽値(boolean) を返す関数型インターフェース
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t); // 引数Tを評価して、trueまたはfalseを返す
}
- test
定義: boolean test(T t)
概要: 引数を評価して、true または false を返す。
Predicate<Integer> isPositive = x -> x > 0;
System.out.println(isPositive.test(5)); // true
System.out.println(isPositive.test(-1)); // false
- and
定義: default Predicate and(Predicate super T> other)
概要: このPredicateと、別のPredicateをAND条件で結合する。
Predicate<Integer> isEven = x -> x % 2 == 0;
Predicate<Integer> isPositiveAndEven = isPositive.and(isEven);
System.out.println(isPositiveAndEven.test(4)); // true (正の偶数)
System.out.println(isPositiveAndEven.test(3)); // false (正の奇数)
System.out.println(isPositiveAndEven.test(-2)); // false (負の偶数)
- or
定義: default Predicate or(Predicate super T> other)
概要: このPredicateと、別のPredicateをOR条件で結合する。
Predicate<Integer> isPositive = x -> x > 0;
Predicate<Integer> isEven = x -> x % 2 == 0;
Predicate<Integer> isPositiveOrEven = isPositive.or(isEven);
System.out.println(isPositiveOrEven.test(4)); // true (正の偶数)
System.out.println(isPositiveOrEven.test(3)); // true (正の奇数)
System.out.println(isPositiveOrEven.test(-2)); // true (負の偶数)
System.out.println(isPositiveOrEven.test(-3)); // false (負の奇数)
- negate
定義: default Predicate negate()
概要: このPredicateの評価結果を反転させる。
Predicate<Integer> isPositive = x -> x > 0;
Predicate<Integer> isNegative = isPositive.negate();
System.out.println(isNegative.test(5)); // false (正の整数)
System.out.println(isNegative.test(-1)); // true (負の整数)
- isEqual
定義: static Predicate isEqual(Object targetRef)
概要: 引数が指定された値と等しい場合にtrueを返す。
Predicate<String> isEqualToJava = Predicate.isEqual("Java");
System.out.println(isEqualToJava.test("Java")); // true
System.out.println(isEqualToJava.test("Python")); // false
Consumerインターフェース
1つの引数を受け取り、結果を返さず処理を行う関数型インターフェース
@FunctionalInterface
public interface Consumer<T> {
void accept(T t); // 引数Tを処理する
}
- accept
定義: void accept(T t)
概要: 引数を消費し、処理を行う(結果は返さない)。
Consumer<String> print = s -> System.out.println(s);
print.accept("Hello, Java!"); // 出力: Hello, Java!
- andThen
定義: default Consumer andThen(Consumer super T> after)
概要: このConsumerを実行した後に、指定したConsumerを実行する。
Supplierインターフェース
引数を受け取らずに値を生成して返す関数型インターフェース
@FunctionalInterface
public interface Supplier<T> {
T get(); // 値を生成して返す
}
- get
定義: T get()
概要: 値を生成して返す
import java.time.LocalDateTime;
import java.util.function.Supplier;
public class Main {
public static void main(String[] args) {
Supplier<LocalDateTime> currentTimeSupplier = () -> LocalDateTime.now();
System.out.println(currentTimeSupplier.get()); // 現在時刻を出力
}
}
import java.util.Random;
import java.util.function.Supplier;
public class Main {
public static void main(String[] args) {
Supplier<Integer> randomSupplier = () -> new Random().nextInt(100); // 0~99のランダムな整数
System.out.println(randomSupplier.get());
}
}
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.function.Supplier;
public class Main {
public static void main(String[] args) {
Supplier<Integer> randomSupplier = () -> new Random().nextInt(100);
List<Integer> randomNumbers = new ArrayList<>();
for (int i = 0; i < 5; i++) {
randomNumbers.add(randomSupplier.get());
}
System.out.println(randomNumbers); // ランダムな数値リストを出力
}
}
UnaryOperatorインターフェース
Function の特化型で、引数と戻り値の型が同じ関数
@FunctionalInterface
public interface UnaryOperator<T> extends Function<T, T> {
// Function<T, T>をそのまま継承している
}
主な用途
引数を受け取り、同じ型の結果を返す処理を行いたいときに使用します。
例: 値の変換、データの加工、同じ型を保持した操作。
◆並列処理
avaでの並列処理は、複数のスレッドを利用して同時に複数の処理を実行する仕組みを指し、並列処理を活用することで、計算の効率化やリソースの有効活用を実現できる
手法 | 概要 | 主な用途 |
---|---|---|
スレッド (Thread) | スレッドを手動で作成し、管理する低レベルの手法。 | カスタムな並列処理が必要な場合や軽量なタスクの実行。 |
Executor フレームワーク |
スレッドプールを利用して、タスクの管理を効率化する高レベルのAPI。 | スレッドの作成や終了を自動管理する場面。 |
Fork/Join フレームワーク |
大きなタスクを小さなタスクに分割して並列処理を行う(分割統治アルゴリズム)。 | 再帰的なタスク分割が可能な処理。 |
Parallel Streams |
ストリームAPIを利用した並列データ処理。 | データを並列に処理する際の簡潔な実装。 |
CompletableFuture |
非同期タスクを実行し、結果を非同期で取得する仕組み。 | タスクの非同期実行や非同期処理のパイプライン構築。 |
Threadクラスを使ったスレッド作成
Threadクラスを継承するか、Runnableインターフェースを実装する
class MyTask implements Runnable {
@Override
public void run() {
System.out.println("Task executed by: " + Thread.currentThread().getName());
}
}
public class Main {
public static void main(String[] args) {
Thread thread = new Thread(new MyTask());
thread.start(); // スレッドを開始
}
}
Executorフレームワーク
Executorフレームワークは、スレッドプールを活用してタスクを効率的に実行します。手動でスレッドを管理する必要がなくなり、コードが簡潔になる
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Main {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(3);
for (int i = 0; i < 5; i++) {
executor.submit(() -> {
System.out.println("Task executed by: " + Thread.currentThread().getName());
});
}
executor.shutdown(); // スレッドプールのシャットダウン
}
}
Executors.newFixedThreadPool(int n)は固定数のスレッドプールを作成する
submit(Runnable task)でタスクをスレッドプールに送信できる
Fork/Joinフレームワーク
Fork/Joinフレームワークは、大きなタスクを小さなタスクに分割して並列実行します。ForkJoinPoolを利用してタスクを分割・統合
import java.util.concurrent.RecursiveTask;
class SumTask extends RecursiveTask<Integer> {
private final int[] array;
private final int start, end;
public SumTask(int[] array, int start, int end) {
this.array = array;
this.start = start;
this.end = end;
}
@Override
protected Integer compute() {
if (end - start <= 10) { // タスクが小さい場合直接計算
int sum = 0;
for (int i = start; i < end; i++) {
sum += array[i];
}
return sum;
} else { // タスクを分割
int mid = (start + end) / 2;
SumTask leftTask = new SumTask(array, start, mid);
SumTask rightTask = new SumTask(array, mid, end);
leftTask.fork(); // 非同期で実行
int rightResult = rightTask.compute();
int leftResult = leftTask.join();
return leftResult + rightResult;
}
}
}
public class Main {
public static void main(String[] args) {
int[] array = new int[100];
for (int i = 0; i < 100; i++) {
array[i] = i + 1;
}
SumTask task = new SumTask(array, 0, array.length);
int result = task.compute();
System.out.println("Total sum: " + result);
}
}
Parallel Streams
Stream API を並列化してデータを効率的に処理
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
int[] array = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int sum = Arrays.stream(array).parallel().sum();
System.out.println("Total sum: " + sum);
}
}
CompletableFuture
非同期タスクを簡潔に管理し、複数の非同期タスクを組み合わせられる
import java.util.concurrent.CompletableFuture;
public class Main {
public static void main(String[] args) {
CompletableFuture.supplyAsync(() -> {
System.out.println("Task executed by: " + Thread.currentThread().getName());
return "Result";
}).thenAccept(result -> {
System.out.println("Received: " + result);
});
}
}
supplyAsyncは非同期でタスクを実行
thenAcceptはタスクの結果を受け取り、処理を続行
Javaの並列処理の注意点
・スレッドの安全性
共有リソースへのアクセス時はスレッドセーフな設計が必要(例: synchronized、Lock)
・デッドロックの回避
スレッド間のロックの競合で発生するデッドロックに注意
・スレッドの数を制御
過剰なスレッド生成はパフォーマンスを低下させる
スレッドプールやForkJoinPoolでスレッド数を適切に管理
Executor
主なインターフェース
・Executor
タスクの非同期実行を提供する基本インターフェース。
・ExecutorService
タスクのライフサイクル管理(開始、終了、スレッドプールの制御)を提供する拡張インターフェース。
・ScheduledExecutorService
定期的なタスクや遅延タスクをスケジュール実行するためのインターフェース。
ScheduledExecutorService の主なメソッド
メソッド | 定義 | 説明 |
---|---|---|
ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) |
指定した遅延後にタスクを実行します。 | 一度だけ遅延実行が必要な場合に使用。 |
ScheduledFuture<?> schedule(Callable<V> callable, long delay, TimeUnit unit) |
指定した遅延後にタスクを実行し、結果を返します。 | 結果を伴う遅延タスクの実行に使用。 |
ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) |
初期遅延後に固定間隔でタスクを繰り返し実行します。 | 固定間隔で実行するタスク(例: ログ記録、周期的な監視など)。 |
ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) |
初期遅延後、前回の終了から一定時間後にタスクを実行します。 | 前回のタスク終了時間に依存したスケジュールで実行する場合に使用。 |
void shutdown() |
新しいタスクの受け入れを停止し、既存のタスクの完了を待つ。 | 正常な終了処理の開始。 |
List<Runnable> shutdownNow() |
実行中または待機中のタスクを停止し、リストで返します。 | 即座にすべてのタスクを停止する場合に使用。 |
boolean awaitTermination(long timeout, TimeUnit unit) |
スレッドプールのシャットダウンが完了するまで指定時間待機します。 | スレッドプールの正常終了を確認する場合に使用。 |
Executorsユーティリティクラスでのスレッドプール作成
メソッド | 概要 |
---|---|
Executors.newFixedThreadPool(int nThreads) |
固定サイズのスレッドプールを作成。指定された数のスレッドを維持し、キュー内のタスクを順次実行します。 |
Executors.newCachedThreadPool() |
必要に応じてスレッドを生成し、アイドル状態のスレッドを再利用するスレッドプールを作成。小規模タスクに適している。 |
Executors.newSingleThreadExecutor() |
シングルスレッドでタスクを順次実行するスレッドプールを作成。1つのスレッドで処理を直列化する場合に使用。 |
Executors.newScheduledThreadPool(int corePoolSize) |
指定された数のコアスレッドを持つスケジュールタスク用スレッドプールを作成。遅延タスクや定期実行タスクに利用可能。 |
Executors.newWorkStealingPool() |
タスクのロードバランスを自動調整するForkJoinPoolベースのスレッドプールを作成。多数の非同期タスクに適している。 |
ExecutorService インターフェース
ExecutorService は Executor インターフェースを拡張したインターフェースで、タスクのライフサイクル管理(開始、停止、完了の待機など)をサポートしています。
public interface ExecutorService extends Executor {
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);
// 他のメソッド...
}
項目 | submit | execute |
---|---|---|
戻り値 |
Future オブジェクトを返す。結果や状態を管理可能。 |
戻り値なし(void )。 |
サポートされるタスク |
Runnable と Callable の両方に対応。 |
Runnable のみ対応。 |
例外処理 | タスク内の例外は ExecutionException としてキャッチ可能。 |
未処理例外はスレッドのデフォルト例外ハンドラに渡される。 |
主な用途 | 非同期タスクの管理や結果取得が必要な場面。 | 非同期でタスクを実行するだけで十分な場面。 |
Future
Futureは、非同期処理の結果を表すオブジェクトを提供するインターフェースです。タスクを非同期で実行し、その結果や状態を操作できる仕組みを提供します。通常、ExecutorServiceやスレッドプールを利用してタスクを送信する際に、タスクの進捗や結果を管理するために使用
・Futureインターフェースの主な特徴
非同期タスクの結果を取得する。
非同期タスクの進行状態を確認する。
タスクのキャンセルを行う。
メソッド | 定義 | 説明 |
---|---|---|
boolean cancel(boolean mayInterruptIfRunning) |
タスクをキャンセルします。引数がtrue の場合、実行中のスレッドを中断することを試みます。 |
タスクを中止したい場合に使用。キャンセルが成功するとtrue を返します。 |
boolean isCancelled() |
boolean isCancelled() |
タスクがキャンセルされた場合にtrue を返します。 |
boolean isDone() |
boolean isDone() |
タスクが完了、キャンセル、または例外で終了した場合にtrue を返します。 |
V get() |
V get() throws InterruptedException, ExecutionException |
タスクが完了するまで待機し、結果を返します。例外が発生した場合、ExecutionException がスローされます。 |
V get(long timeout, TimeUnit unit) |
V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException |
指定した時間だけ待機し、結果を返します。指定時間内にタスクが完了しない場合、TimeoutException をスローします。 |
import java.util.concurrent.*;
public class Main {
public static void main(String[] args) {
ExecutorService executor = Executors.newSingleThreadExecutor();
Callable<Integer> task = () -> {
Thread.sleep(1000); // 擬似的に時間のかかる処理
return 42; // 計算結果
};
Future<Integer> future = executor.submit(task);
try {
System.out.println("Waiting for the result...");
Integer result = future.get(); // タスクの結果を取得
System.out.println("Result: " + result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
} finally {
executor.shutdown();
}
}
}
Futureの注意点
・ブロッキング
get()メソッドはタスクが完了するまで現在のスレッドをブロックします。
長時間の待機が問題になる場合、タイムアウト付きのgetを使用することを検討してください。
・例外処理
タスク内で例外が発生すると、ExecutionExceptionとしてラップされます。
ExecutionException.getCause()を使用して元の例外を確認することができます。
・キャンセルの限界
実行中のタスクを中断できるかどうかは、タスクの実装に依存します。
cancel(true)はスレッドを中断するリクエストを送りますが、必ず中断されるとは限りません。
・リソース管理
ExecutorServiceを使用する場合は、必ずshutdown()またはshutdownNow()を呼び出してリソースを解放してください。
RunnableとCallableの違い
RunnableとCallableは、Javaのマルチスレッドプログラミングで使用されるタスクを表現するインターフェースです。両者の主な違いは、戻り値があるかどうかと、例外処理の扱いにあります。
RunnableとCallableの比較
1. 定義と目的
インターフェース | 定義 | 目的 |
---|---|---|
Runnable |
void run() メソッドを持つ関数型インターフェース。 |
タスクを実行するが、戻り値や例外処理を必要としない場合に使用。 |
Callable |
T call() throws Exception メソッドを持つ関数型インターフェース。 |
タスクを実行し、結果(戻り値)や例外処理が必要な場合に使用。 |
2. 主な違い
特徴 | Runnable | Callable |
---|---|---|
戻り値 | 戻り値なし(void )。 |
戻り値あり(ジェネリクスT で型を指定)。 |
メソッド | void run() |
T call() throws Exception |
例外処理 | チェック例外(Exception )をスローできない。 |
チェック例外をスロー可能(例: IOException )。 |
スレッドプールとの統合 |
Executor.execute() またはsubmit() で使用。 |
Executor.submit() で使用し、Future で結果を取得。 |
3. 使用するタイミングの判断基準
シナリオ | 適切なインターフェース | 理由 |
---|---|---|
タスクを実行し、戻り値が不要な場合 | Runnable | 戻り値が必要ないため、シンプルなRunnable で十分。 |
タスクの実行結果が必要な場合 | Callable | 戻り値を取得し、処理結果を利用する必要がある。 |
スレッド内でチェック例外をスローしたい場合 | Callable |
call() はチェック例外をスロー可能であり、柔軟に例外処理が行える。 |
タスクの状態や結果を管理する必要がある場合(例: タスクが完了したか確認したい) | Callable |
Future と連携し、タスクの進行状況や結果を管理できる。 |
スレッドプールを使用し、タスクを効率的に管理したい場合 | どちらでも可 | 両方をサポートするが、戻り値や例外処理が必要ならCallable を選択。 |
java.util.concurrent
java.util.concurrent パッケージは、Javaでマルチスレッドプログラミングや並行処理を効率的に実現するためのクラスとインターフェースを提供します。このパッケージには、スレッドプール、タスクのスケジューリング、同期制御、並行データ構造、非同期処理などに関連する豊富なツールが含まれています。
1. 主なクラスとその概要
(1) スレッド管理
クラス/インターフェース | 説明 |
---|---|
Executor |
タスクの非同期実行を提供する基本インターフェース。 |
ExecutorService |
タスクのライフサイクル管理(開始、終了、進行確認)を提供する拡張インターフェース。 |
ScheduledExecutorService |
遅延タスクや定期タスクをスケジュール実行するためのインターフェース。 |
Executors |
各種スレッドプールやエグゼキュータを生成するユーティリティクラス。 |
ThreadPoolExecutor |
高度にカスタマイズ可能なスレッドプールエグゼキュータ。 |
ForkJoinPool |
タスク分割と統合に特化した並列処理用スレッドプール。 |
(2) 同期制御ツール
クラス/インターフェース | 説明 |
---|---|
Semaphore |
許可された数のスレッドのみがリソースを使用できるように制御する同期ツール。 |
CountDownLatch |
指定されたカウントがゼロになるまでスレッドを待機させる同期ツール。 |
CyclicBarrier |
複数のスレッドが集まり次第処理を進める同期ツール。 |
ReentrantLock |
再入可能なロック。synchronized の柔軟な代替として使用可能。 |
ReentrantReadWriteLock |
読み取りと書き込みのロックを分離するツール。複数のスレッドが同時に読み取り可能で、書き込みは1つに限定される。 |
StampedLock |
ReentrantReadWriteLock より高速で、読み取り重視の処理に最適化されたロック。 |
(3) 並行データ構造
クラス | 説明 |
---|---|
ConcurrentHashMap |
スレッドセーフなハッシュマップ。内部ロックにより高いパフォーマンスを提供。 |
ConcurrentLinkedQueue |
非ブロッキングのスレッドセーフなキュー(FIFO構造)。 |
ConcurrentSkipListMap |
スレッドセーフなナビゲーションマップ。キーの順序を保持する。 |
ConcurrentSkipListSet |
スレッドセーフなナビゲーションセット。順序付きのデータを保持。 |
CopyOnWriteArrayList |
読み取り操作が多い場面で効率的に使用できるスレッドセーフなリスト。書き込み時にコピーを作成。 |
CopyOnWriteArraySet |
スレッドセーフなセット。書き込み操作が少ない場合に適している。 |
LinkedBlockingQueue |
ブロッキングキューで、スレッド間のデータ受け渡しに使用される。 |
PriorityBlockingQueue |
優先順位付きのスレッドセーフなキュー。 |
(4) 非同期処理
クラス/インターフェース | 説明 |
---|---|
Future |
非同期タスクの結果を管理するインターフェース。 |
CompletableFuture |
非同期タスクのチェーン実行やエラーハンドリングを簡単に行えるクラス。 |
Callable |
戻り値のある非同期タスクを定義するインターフェース。 |
RunnableFuture |
Runnable とFuture の両方を実装したタスク。 |
(5) 原子操作ユーティリティ
クラス | 説明 |
---|---|
AtomicInteger |
スレッドセーフな整数操作をサポートするクラス。 |
AtomicLong |
スレッドセーフな長整数操作をサポートするクラス。 |
AtomicBoolean |
スレッドセーフな真偽値操作をサポートするクラス。 |
AtomicReference<T> |
オブジェクト参照の原子操作をサポートするクラス。 |
◆ストリームAPI
JavaのStream APIは、コレクションや配列などのデータソースに対して、効率的かつ簡潔にデータ操作を行うためのAPIです。java.util.streamパッケージに含まれ、Java 8以降で導入されました。
ストリームの特徴
非破壊的操作:
元のデータソースを変更せず、処理結果を新しいストリームとして返す。
遅延評価:
必要になるまで処理を実行しない(中間操作は遅延評価、終端操作で処理を実行)。
宣言的スタイル:
ループや条件を明示的に記述する従来の手続き型スタイルではなく、関数型の宣言的スタイルでデータ操作を記述。
パラレル処理のサポート:
並列ストリーム(parallelStream())を利用することで、マルチスレッドでデータ処理を簡単に行える。
(1) 中間操作
中間操作はストリームを変換するための操作で、遅延評価されます。連続してチェーン可能です。
(2) 終端操作
終端操作はストリームを消費して最終結果を生成します。これによりストリーム操作が実行されます。
操作の種類 | メソッド | 説明 |
---|---|---|
中間操作 | filter |
条件に一致する要素をフィルタリング。 |
map |
要素を別の形に変換。 | |
flatMap |
要素をストリームに変換して1つに統合。 | |
distinct |
重複を除外。 | |
sorted |
昇順またはカスタム順序で要素をソート。 | |
limit |
最初のN個の要素を取得。 | |
skip |
最初のN個の要素をスキップ。 | |
終端操作 | forEach |
各要素にアクションを実行。 |
collect |
要素をリストやセットなどのコレクションに変換。 | |
reduce |
要素を集約して1つの結果にまとめる。 | |
count |
要素の個数を取得。 | |
findFirst |
最初の要素を取得(Optional 型)。 |
|
findAny |
任意の要素を取得(並列処理で最初とは限らない)。 | |
allMatch |
すべての要素が条件を満たすか確認。 | |
anyMatch |
いずれかの要素が条件を満たすか確認。 | |
noneMatch |
すべての要素が条件を満たさないか確認。 |
◆Optional
Optionalは、Java 8で導入されたnullを安全に扱うためのコンテナクラスです。
これにより、nullチェックを明示的に行い、NullPointerExceptionのリスクを減らすことができます。
Optionalの特徴
nullの代替:
値が存在する場合はその値を保持し、存在しない場合は空の状態を持つオブジェクトです。
null安全:
値がnullかどうかを安全に確認するためのメソッドが提供されています。
関数型プログラミングスタイルに適合:
Optionalは、ストリームAPIと同様の関数型スタイルで操作可能です。
メソッド | 説明 |
---|---|
empty() |
空のOptional インスタンスを返します。 |
of(T value) |
指定された値を持つOptional インスタンスを返します。値がnull の場合はNullPointerException がスローされます。 |
ofNullable(T value) |
指定された値を持つOptional インスタンスを返します。値がnull の場合は空のOptional を返します。 |
isPresent() |
値が存在する場合にtrue を返します。 |
isEmpty() (Java 11以降) |
値が存在しない場合にtrue を返します。 |
get() |
値を返します。値が存在しない場合はNoSuchElementException がスローされます。 |
ifPresent(Consumer<? super T> action) |
値が存在する場合、指定されたアクションを実行します。 |
orElse(T other) |
値が存在する場合はその値を返し、存在しない場合は指定された値を返します。 |
orElseGet(Supplier<? extends T> other) |
値が存在する場合はその値を返し、存在しない場合は指定されたサプライヤーの結果を返します。 |
orElseThrow() |
値が存在する場合はその値を返し、存在しない場合はNoSuchElementException をスローします。 |
map(Function<? super T, ? extends U> mapper) |
値が存在する場合に関数を適用し、その結果のOptional を返します。値が存在しない場合は空のOptional を返します。 |
flatMap(Function<? super T, Optional<U>> mapper) |
値が存在する場合に関数を適用し、その結果を返します(関数が返すのはOptional 自体)。 |
filter(Predicate<? super T> predicate) |
値が存在し、条件を満たす場合にその値を含むOptional を返します。それ以外の場合は空のOptional を返します。 |
使用するメリット
nullチェックの簡略化:
明示的なnullチェックコードを減らし、可読性を向上させる。
関数型スタイルとの統合:
mapやflatMapを使用してチェーン処理を簡潔に記述可能。
エラーの防止:
nullを直接扱う必要がなくなるため、NullPointerExceptionのリスクを削減。