「JAVA」カテゴリーアーカイブ

【Java入門シリーズ】第11章: Javaの入出力処理 〜ファイル操作とユーザー入力を扱う〜

Javaでは、ファイル操作やユーザー入力の処理が重要な役割を果たします。これらの処理は、プログラムが外部データを読み込んだり、データを保存するために不可欠です。Javaは、標準的な入出力(I/O)操作を行うために豊富なAPIを提供しており、これを利用してファイルの読み書きやコンソールからの入力を簡単に実装することができます。

この章では、Javaの入出力処理の基本から、ファイルの読み書き、ユーザー入力、バッファリングなど、さまざまなI/O操作について学びます。主に以下の内容をカバーします:

  • ファイルの入出力の基本
  • Fileクラスを使ったファイル操作
  • Scannerクラスを使ったユーザー入力の処理
  • FileReaderBufferedReaderによるファイルの読み込み
  • FileWriterBufferedWriterによるファイルへの書き込み
  • 例外処理を含むファイル操作

11.1 入出力の基本概念

入出力(I/O: Input/Output)は、プログラムが外部データとやり取りを行うための仕組みです。入力は、ユーザーや外部ファイルからデータを受け取ることを指し、出力は、ファイルにデータを書き込む、もしくは画面にデータを表示する操作を指します。

Javaでは、入出力処理を行うためにjava.iojava.nio.fileパッケージが提供されており、これらを使うことで簡単にI/O処理が実現できます。

11.1.1 入出力のストリーム

Javaの入出力処理は、ストリーム(Stream)という概念に基づいています。ストリームは、データの流れを表すものであり、入力ストリーム(データをプログラムに取り込む)と出力ストリーム(データをプログラムから外部に書き出す)の2種類があります。

  • 入力ストリーム: データを外部からプログラムに読み込むためのもの(例:ファイルやユーザー入力)。
  • 出力ストリーム: データをプログラムから外部に出力するためのもの(例:ファイルへの書き込みやコンソール出力)。

Javaでは、バイトストリーム(InputStreamOutputStream)と文字ストリーム(ReaderWriter)の2種類のストリームが提供されており、用途に応じて使い分けます。


11.2 Fileクラスを使ったファイル操作

Javaでファイルを扱うためには、java.io.Fileクラスを使用します。このクラスを使うことで、ファイルやディレクトリの存在確認、作成、削除、パスの取得など、基本的なファイル操作が行えます。

11.2.1 ファイルの存在確認

まず、特定のファイルが存在するかどうかを確認する方法です。Fileクラスのexists()メソッドを使って、ファイルが存在するか確認できます。

import java.io.File;

public class Main {
public static void main(String[] args) {
File file = new File("sample.txt");

if (file.exists()) {
System.out.println("ファイルは存在します。");
} else {
System.out.println("ファイルは存在しません。");
}
}
}

11.2.2 ファイルの作成

ファイルを作成するには、FileクラスのcreateNewFile()メソッドを使用します。

import java.io.File;
import java.io.IOException;

public class Main {
public static void main(String[] args) {
File file = new File("newfile.txt");

try {
if (file.createNewFile()) {
System.out.println("ファイルが作成されました。");
} else {
System.out.println("ファイルはすでに存在しています。");
}
} catch (IOException e) {
System.out.println("ファイル作成中にエラーが発生しました。");
}
}
}

11.2.3 ファイルの削除

ファイルを削除するには、delete()メソッドを使用します。

File file = new File("newfile.txt");
if (file.delete()) {
System.out.println("ファイルが削除されました。");
} else {
System.out.println("ファイルの削除に失敗しました。");
}

11.3 ユーザー入力の処理

Javaでは、ユーザーからの入力を受け取るために、java.util.Scannerクラスを使用します。このクラスは、標準入力(キーボード入力)やファイルからの入力を簡単に扱うことができ、文字列や数値の入力を効率的に処理します。

11.3.1 Scannerクラスを使った入力

以下は、Scannerクラスを使って、ユーザーから名前を入力して表示する例です。

import java.util.Scanner;

public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in); // 標準入力を読み取るScannerオブジェクトを作成

System.out.print("名前を入力してください: ");
String name = scanner.nextLine(); // ユーザーからの入力を取得

System.out.println("こんにちは、" + name + "さん!");
}
}

11.3.2 数値入力の処理

Scannerクラスは、数値や他のデータ型も扱うことができます。nextInt()nextDouble()などのメソッドを使って、数値を入力することが可能です。

import java.util.Scanner;

public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);

System.out.print("年齢を入力してください: ");
int age = scanner.nextInt(); // 整数の入力を受け取る

System.out.println("あなたは " + age + " 歳です。");
}
}

11.4 ファイルからの読み込み

Javaでファイルからデータを読み込むには、**FileReaderBufferedReader**を使う方法が一般的です。BufferedReaderを使うことで、ファイルからの読み込みを効率化し、ファイルの内容を一行ずつ処理することができます。

11.4.1 FileReaderBufferedReaderによる読み込み

以下は、BufferedReaderを使ってテキストファイルを一行ずつ読み込む例です。

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class Main {
public static void main(String[] args) {
try {
BufferedReader reader = new BufferedReader(new FileReader("sample.txt"));
String line;
while ((line = reader.readLine()) != null) { // 1行ずつ読み込む
System.out.println(line);
}
reader.close(); // リソースを解放
} catch (IOException e) {
System.out.println("ファイルの読み込み中にエラーが発生しました。");
}
}
}

11.4.2 ファイルの存在チェックと例外処理

ファイルが存在しない場合に、FileNotFoundExceptionIOExceptionが発生するため、例外処理を組み込むことが重要です。上記の例では、catchブロックで例外をキャッチし、エラーが発生した場合にメッセージを表示しています。


11.5 ファイルへの書き込み

ファイルへのデータの書き込みには、**FileWriterBufferedWriter**を使用します。BufferedWriterを使うことで、大量のデータを効率的にファイルに書き込むことが可能です。

11.5.1 FileWriterBufferedWriterによる書き込み

以下は、BufferedWriterを使ってファイルに文字列を書き込む例です。

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;

public class Main {
public static void main(String[] args) {
try {
BufferedWriter writer = new BufferedWriter(new FileWriter("output.txt"));
writer.write("こんにちは、Java!"); // 文字列を書き込む
writer.newLine(); // 改行を追加
writer.write("ファイル操作を学びましょう。");
writer.close(); // リソースを解放
System.out.println("ファイルに書き込みが完了しました。");
} catch (IOException e) {
System.out.println("ファイルの書き込み中にエラーが発生しました。");
}
}
}

11.5.2 追記モードでの書き込み

FileWriterのコンストラクタにtrueを渡すことで、追記モードでファイルにデータを書き込むことができます。

BufferedWriter writer = new BufferedWriter(new FileWriter("output.txt", true));  // 追記モード
writer.write("新しいデータを追加します。");
writer.newLine();
writer.close();

11.6 例外処理を含むファイル操作

ファイル操作では、ファイルが存在しない、読み取り権限がない、ディスクスペースが不足しているなどの問題が発生する可能性があります。これらの問題を正しく処理するために、適切な例外処理を行うことが重要です。

11.6.1 try-with-resources構文

Java 7以降では、**try-with-resources**構文を使用することで、ファイル操作時のリソース(ファイルやストリーム)の自動解放が可能です。try-with-resourcesは、AutoCloseableインターフェースを実装しているオブジェクトを自動的にクローズします。

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class Main {
public static void main(String[] args) {
try (BufferedReader reader = new BufferedReader(new FileReader("sample.txt"))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
System.out.println("ファイルの読み込み中にエラーが発生しました。");
}
}
}

try-with-resources構文では、tryブロックが終了すると、リソースが自動的に閉じられるため、close()メソッドを明示的に呼び出す必要がありません。


11.7 Javaの入出力処理まとめ

この章では、Javaの入出力処理について学びました。ファイルの操作やユーザー入力の処理は、多くのプログラムで必要となる基本的な機能です。Fileクラスを使ったファイルの存在確認や作成、Scannerクラスを使ったユーザー入力、FileReaderBufferedReaderを使ったファイルの読み込み、FileWriterBufferedWriterを使ったファイルへの書き込み、そして、例外処理を通じてエラーハンドリングを行う方法を紹介しました。

これらの基本操作を理解することで、Javaの入出力処理を効率よく実装し、ファイル操作やユーザーインタラクションを含む堅牢なプログラムを作成できるようになります。

【Java入門シリーズ】第12章: コレクションフレームワーク 〜効率的なデータ管理と操作を実現する〜

Javaの**コレクションフレームワーク(Collection Framework)**は、データを効率的に管理・操作するための強力なツール群を提供します。コレクションフレームワークは、複数のデータを扱うためのインターフェースやクラスが体系的に整理されており、リスト、セット、マップなど、さまざまなデータ構造を効率よく利用できます。

この章では、コレクションフレームワークの基本概念から、リストやセット、マップなどの主要なデータ構造の使い方、そしてジェネリクスやストリームAPIを活用した高度な操作方法について解説します。具体的には、以下の内容をカバーします:

  • コレクションフレームワークの基本
  • リスト(List)インターフェース
  • セット(Set)インターフェース
  • マップ(Map)インターフェース
  • ジェネリクスを使ったコレクションの型安全性
  • ストリームAPIによるコレクション操作

12.1 コレクションフレームワークとは?

コレクションフレームワークは、Javaで複数のデータを管理・操作するためのインターフェースやクラスを提供する仕組みです。リストやセット、マップといったデータ構造を使い、配列よりも柔軟で効率的にデータの操作を行うことができます。

コレクションフレームワークは、以下の3つの主要なインターフェースで構成されています:

  • List: 順序を保持し、要素の重複を許容するコレクション。
  • Set: 順序を保持せず、要素の重複を許容しないコレクション。
  • Map: キーと値のペアで要素を保持し、キーの重複を許容しないコレクション。

12.1.1 コレクションフレームワークのメリット

コレクションフレームワークを使うことで、以下のようなメリットがあります:

  • データ構造の一貫性: 共通のインターフェースを使用することで、操作が一貫して行えます。
  • 効率的なメモリ管理: 必要に応じてサイズが変更でき、メモリの無駄を最小限に抑えられます。
  • 豊富な機能: ソートや検索、データの変換など、便利な機能が標準で提供されています。

12.2 Listインターフェース 〜順序を持つデータ構造〜

Listは、順序を持ち、同じ要素を複数回保持できるデータ構造です。リストは、インデックスを使って要素にアクセスできるため、配列に似た構造を持っていますが、配列とは異なり、リストはサイズを動的に変更できます。

12.2.1 ArrayListの使い方

ArrayListは、Listインターフェースを実装した最も一般的なクラスで、可変長の配列として動作します。以下は、ArrayListの基本的な操作です。

import java.util.ArrayList;

public class Main {
public static void main(String[] args) {
// ArrayListの作成
ArrayList<String> fruits = new ArrayList<>();

// 要素の追加
fruits.add("リンゴ");
fruits.add("バナナ");
fruits.add("オレンジ");

// 要素の取得
System.out.println(fruits.get(1)); // インデックス1の要素を取得

// リストの全要素をループ
for (String fruit : fruits) {
System.out.println(fruit);
}

// 要素の削除
fruits.remove("バナナ");
System.out.println(fruits);
}
}

12.2.2 LinkedListの使い方

LinkedListは、Listインターフェースを実装したもう一つのクラスで、双方向の連結リストとして動作します。ArrayListはランダムアクセスに優れていますが、LinkedListは要素の追加や削除が効率的に行えます。

import java.util.LinkedList;

public class Main {
public static void main(String[] args) {
// LinkedListの作成
LinkedList<String> queue = new LinkedList<>();

// 要素の追加
queue.add("ジョン");
queue.add("ポール");
queue.add("ジョージ");

// 先頭の要素を取得(削除せず)
System.out.println(queue.peek());

// 先頭の要素を取得し、リストから削除
System.out.println(queue.poll());

// 残りの要素を表示
System.out.println(queue);
}
}

LinkedListはキューやスタックとしても使用でき、先入れ先出し(FIFO)や後入れ先出し(LIFO)のデータ操作が簡単に実現できます。


12.3 Setインターフェース 〜重複を許さないコレクション〜

Setは、重複する要素を持たないコレクションです。セットは順序を持たないため、追加した順序を保証しませんが、データの一意性を保つ場面で役立ちます。代表的なセット実装には、HashSetTreeSetがあります。

12.3.1 HashSetの使い方

HashSetは、要素をハッシュテーブルを使って管理し、重複する要素を許しません。また、順序も保持しません。

import java.util.HashSet;

public class Main {
public static void main(String[] args) {
// HashSetの作成
HashSet<String> cities = new HashSet<>();

// 要素の追加
cities.add("東京");
cities.add("大阪");
cities.add("名古屋");
cities.add("東京"); // 重複した要素は追加されない

// Setの全要素をループ
for (String city : cities) {
System.out.println(city);
}

// 要素の存在チェック
if (cities.contains("大阪")) {
System.out.println("大阪がセットに含まれています。");
}
}
}

12.3.2 TreeSetの使い方

TreeSetは、要素を自然順序に基づいてソートし、重複を許さないセットです。ソートされた順序で要素が保持されるため、数値やアルファベット順にデータを管理したい場合に便利です。

import java.util.TreeSet;

public class Main {
public static void main(String[] args) {
// TreeSetの作成
TreeSet<Integer> numbers = new TreeSet<>();

// 要素の追加
numbers.add(5);
numbers.add(1);
numbers.add(10);
numbers.add(3);

// 自然順序でソートされた要素を表示
for (int num : numbers) {
System.out.println(num);
}
}
}

TreeSetSortedSetインターフェースを実装しているため、要素が常にソートされた状態で保持されます。


12.4 Mapインターフェース 〜キーと値のペアでデータを管理する〜

Mapは、キーと値のペアでデータを管理するコレクションです。キーは一意でなければならず、同じキーに対して複数の値を設定することはできません。Mapは、検索やデータの関連付けに非常に便利です。

12.4.1 HashMapの使い方

HashMapは、キーと値のペアをハッシュテーブルで管理する一般的なマップです。キーの順序は保持されませんが、要素の追加・削除・検索が高速に行えます。

import java.util.HashMap;

public class Main {
public static void main(String[] args) {
// HashMapの作成
HashMap<String, Integer> ages = new HashMap<>();

// 要素の追加
ages.put("太郎", 25);
ages.put("花子", 30);
ages.put("次郎", 22);

// 特定のキーに対応する値を取得
System.out.println("太郎の年齢: " + ages.get("太郎"));

// Mapの全要素をループ
for (String name : ages.keySet()) {
System.out.println(name + "の年齢は " + ages.get(name) + " 歳です。");
}
}
}

12.4.2 TreeMapの使い方

TreeMapは、キーに基づいてデータをソートして保持するマップです。キーが自然順序に従ってソートされるため、ソートされた順序でデータを管理したい場合に役立ちます。

import java.util.TreeMap;

public class Main {
public static void main(String[] args) {
// TreeMapの作成
TreeMap<String, Integer> scores = new TreeMap<>();

// 要素の追加
scores.put("Alice", 85);
scores.put("Bob", 92);
scores.put("Charlie", 78);

// 自然順序でソートされたMapの要素を表示
for (String name : scores.keySet()) {
System.out.println(name + "のスコアは " + scores.get(name) + " 点です。");
}
}
}

12.5 ジェネリクス 〜型安全なコレクションの実現〜

Javaのコレクションフレームワークでは、デフォルトでジェネリクスが使用されます。**ジェネリクス(Generics)**を使うことで、コレクションに格納できる要素の型を指定し、型安全性を高めることができます。ジェネリクスを使うことで、コンパイル時に型の不一致を防ぐことができます。

12.5.1 ジェネリクスの基本構文

コレクションにジェネリクスを適用する例を以下に示します。ArrayListString型の要素だけを格納する場合、ジェネリクスを使って型を明示的に指定します。

import java.util.ArrayList;

public class Main {
public static void main(String[] args) {
// ジェネリクスを使ってString型のリストを作成
ArrayList<String> names = new ArrayList<>();

// String型の要素を追加
names.add("太郎");
names.add("花子");

// コンパイルエラー:整数は追加できない
// names.add(123);

// リストの全要素を表示
for (String name : names) {
System.out.println(name);
}
}
}

ジェネリクスを使うことで、リストに格納するデータ型を強制し、意図しない型のデータが追加されることを防げます。


12.6 Stream API 〜コレクションの高度な操作〜

Java 8で導入されたStream APIは、コレクションの操作を簡潔かつ効率的に行うための強力なツールです。ストリームは、データの集まりを処理するための抽象化された操作のシーケンスを提供し、フィルタリング、ソート、マッピング、集計などの操作が簡単に行えます。

12.6.1 Streamの基本操作

Streamを使ってコレクションをフィルタリングし、操作する例を示します。

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

public class Main {
public static void main(String[] args) {
List<String> names = new ArrayList<>();
names.add("太郎");
names.add("花子");
names.add("次郎");
names.add("二郎");

// "郎"で終わる名前をフィルタリング
List<String> filteredNames = names.stream()
.filter(name -> name.endsWith("郎"))
.collect(Collectors.toList());

// フィルタリングされた名前を表示
filteredNames.forEach(System.out::println);
}
}

12.6.2 ソートや集計操作

Streamを使えば、簡単にソートや集計などの操作も行えます。

import java.util.Arrays;
import java.util.List;

public class Main {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(5, 2, 9, 3, 7);

// ソート
numbers.stream()
.sorted()
.forEach(System.out::println);

// 合計を計算
int sum = numbers.stream().mapToInt(Integer::intValue).sum();
System.out.println("合計: " + sum);
}
}

12.7 Javaのコレクションフレームワークまとめ

この章では、Javaのコレクションフレームワークについて学びました。コレクションフレームワークは、効率的にデータを管理・操作するための重要なツールであり、ListSetMapなど、さまざまなデータ構造を提供しています。また、ジェネリクスを使って型安全なコレクションを実現し、Stream APIを使うことで、コレクションのデータを高度に処理することが可能です。

【Java入門シリーズ】第13章: ラムダ式と関数型プログラミング 〜Javaプログラムをシンプルにする技術〜

Java 8から導入されたラムダ式(Lambda Expression)は、プログラムをより簡潔に、そして効率的に記述できる強力な機能です。従来のJavaでは、匿名クラスやインターフェースを使ったコールバックや処理の委譲が主流でしたが、ラムダ式を使うことで、これらのコードを大幅に短縮し、可読性を向上させることができます。

この章では、Javaのラムダ式を理解するために、以下の内容をカバーします:

  • ラムダ式の基本
  • 関数型インターフェース
  • メソッド参照
  • ストリームAPIとの組み合わせ
  • ラムダ式のユースケースと実践例

13.1 ラムダ式とは?

ラムダ式(Lambda Expression)は、無名関数(名前のない関数)を表現する手段です。従来、匿名クラスを使用していた箇所をより簡潔に記述でき、コードの可読性が向上します。主に、短い処理を行うメソッドの実装に使われ、Javaの関数型プログラミングの基盤となる要素です。

13.1.1 ラムダ式の基本構文

ラムダ式の基本的な構文は次の通りです。

(引数リスト) -> { 実行する処理 }
  • 引数リスト: メソッドのパラメータと同様に、ラムダ式で処理する値を渡します。引数が1つの場合、カッコ()は省略可能です。
  • ->演算子: ラムダ式において、引数と実行する処理を区切る演算子です。
  • 実行する処理: returnを使う必要がない場合、波括弧 {} を省略できます。

13.1.2 ラムダ式の例

具体的な例として、2つの整数を足し合わせるラムダ式を見てみましょう。

// 2つの引数を持ち、その和を返すラムダ式
(int a, int b) -> { return a + b; }

上記のラムダ式は、次のように書き換えて、より簡潔に表現することが可能です。

// 引数の型は推論できるため省略可能
(a, b) -> a + b

13.1.3 従来の匿名クラスとラムダ式の比較

ラムダ式は、従来の匿名クラスの書き方を簡潔に置き換えることができます。例えば、以下の匿名クラスを使った例と、そのラムダ式での表現を比較してみます。

匿名クラスの例:

// Runnableインターフェースを匿名クラスで実装
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("従来の匿名クラスの例");
}
};
new Thread(runnable).start();

ラムダ式での例:

// Runnableインターフェースをラムダ式で実装
Runnable runnable = () -> System.out.println("ラムダ式の例");
new Thread(runnable).start();

ラムダ式を使うことで、コードが非常に簡潔になり、匿名クラスよりも可読性が向上します。


13.2 関数型インターフェース

ラムダ式を使用するためには、関数型インターフェース(Functional Interface)が必要です。関数型インターフェースとは、1つの抽象メソッドのみを持つインターフェースのことです。ラムダ式は、この1つの抽象メソッドを実装する形で利用されます。

13.2.1 関数型インターフェースの例

次に、1つの抽象メソッドだけを持つインターフェースCalculatorを定義し、それをラムダ式で実装してみます。

// 関数型インターフェースの定義
@FunctionalInterface
interface Calculator {
int calculate(int a, int b);
}

public class Main {
public static void main(String[] args) {
// 2つの数値の足し算を行うラムダ式
Calculator adder = (a, b) -> a + b;
System.out.println(adder.calculate(10, 20)); // 30
}
}

この例では、Calculatorインターフェースにラムダ式を使用して計算処理を実装しています。

13.2.2 @FunctionalInterfaceアノテーション

Java 8では、関数型インターフェースとして定義されたインターフェースに対して、@FunctionalInterfaceアノテーションを付けることができます。これにより、1つの抽象メソッドしか持たないことが保証され、誤って複数の抽象メソッドを定義した場合にコンパイルエラーを発生させます。

@FunctionalInterface
interface MyFunction {
void apply();
}

13.3 Java標準の関数型インターフェース

Java 8では、ラムダ式をより便利に使うために、いくつかの標準の関数型インターフェースが提供されています。これらはjava.util.functionパッケージに含まれており、最もよく使われる関数型インターフェースには以下があります:

13.3.1 Predicateインターフェース

Predicate<T>は、条件を評価してtrueまたはfalseを返す関数型インターフェースです。

import java.util.function.Predicate;

public class Main {
public static void main(String[] args) {
// 数値が10より大きいかを判定するPredicate
Predicate<Integer> isGreaterThanTen = x -> x > 10;

System.out.println(isGreaterThanTen.test(5)); // false
System.out.println(isGreaterThanTen.test(15)); // true
}
}

13.3.2 Functionインターフェース

Function<T, R>は、入力を受け取って処理を行い、結果を返す関数型インターフェースです。

import java.util.function.Function;

public class Main {
public static void main(String[] args) {
// 整数を受け取り、その2倍を返すFunction
Function<Integer, Integer> doubleValue = x -> x * 2;

System.out.println(doubleValue.apply(10)); // 20
}
}

13.3.3 Consumerインターフェース

Consumer<T>は、引数を受け取って処理を行うが、結果を返さない関数型インターフェースです。

import java.util.function.Consumer;

public class Main {
public static void main(String[] args) {
// 文字列を表示するConsumer
Consumer<String> printer = message -> System.out.println(message);

printer.accept("Javaのラムダ式を学ぼう"); // "Javaのラムダ式を学ぼう"が出力される
}
}

13.3.4 Supplierインターフェース

Supplier<T>は、引数を取らずに結果を返す関数型インターフェースです。

import java.util.function.Supplier;

public class Main {
public static void main(String[] args) {
// 現在の時刻を返すSupplier
Supplier<Long> currentTime = () -> System.currentTimeMillis();

System.out.println(currentTime.get());
}
}

13.4 メソッド参照

メソッド参照は、ラムダ式のシンプルな代替として使用される記法です。特定のクラスのメソッドやインスタンスのメソッドを直接参照して利用できます。

メソッド参照の基本構文は以下の通りです。

クラス名::メソッド名

13.4.1 インスタンスメソッドの参照

次の例では、インスタンスメソッドをメソッド参照で使う例です。

import java.util.function.Consumer;

public class Main {
public static void main(String[] args) {
Consumer<String> printer = System.out::println; // メソッド参照
printer.accept("メソッド参照の例");
}
}

13.4.2 コンストラクタの参照

コンストラクタもメソッド参照で利用できます。

import java.util.function.Supplier;

class Person {
String name;
Person(String name) {
this.name = name;
}
}

public class Main {
public static void main(String[] args) {
Supplier<Person> personFactory = () -> new Person("太郎");
Person person = personFactory.get();
System.out.println(person.name); // "太郎"と出力
}
}

13.5 ラムダ式とストリームAPI

JavaのストリームAPIとラムダ式は非常に強力な組み合わせです。ストリームAPIを使うことで、コレクションのデータを簡潔に操作でき、ラムダ式を使ってフィルタリング、マッピング、ソート、集計などの操作を効率的に行えます。

13.5.1 フィルタリング

ストリームAPIとラムダ式を使って、リストの要素をフィルタリングする例を示します。

import java.util.Arrays;
import java.util.List;

public class Main {
public static void main(String[] args) {
List<String> names = Arrays.asList("太郎", "花子", "次郎", "二郎");

// 名前が"郎"で終わるものをフィルタリング
names.stream()
.filter(name -> name.endsWith("郎"))
.forEach(System.out::println); // "太郎", "次郎" が出力される
}
}

13.5.2 ソート

ストリームAPIとラムダ式を使って、リストの要素をソートする例です。

import java.util.Arrays;
import java.util.List;

public class Main {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(5, 3, 9, 1, 7);

// 昇順にソート
numbers.stream()
.sorted()
.forEach(System.out::println); // 1, 3, 5, 7, 9 が出力される
}
}

13.5.3 集計

Streamを使って、数値のリストを集計する例を示します。

import java.util.Arrays;
import java.util.List;

public class Main {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(5, 3, 9, 1, 7);

// 合計を計算
int sum = numbers.stream()
.mapToInt(Integer::intValue)
.sum();

System.out.println("合計: " + sum); // 25
}
}

13.6 ラムダ式と関数型プログラミングのまとめ

この章では、Javaのラムダ式関数型プログラミングについて学びました。ラムダ式は、無名関数をシンプルに表現できる便利な構文であり、コードの簡潔化と可読性の向上を実現します。関数型インターフェースと組み合わせることで、ラムダ式を使って柔軟なプログラムを設計することが可能です。

また、メソッド参照やJava標準の関数型インターフェース、ストリームAPIとの連携により、より高度な操作も簡単に行えます。これらの技術を活用することで、コードの冗長性を減らし、より直感的で効率的なプログラムを作成できるようになります。

【Java入門シリーズ】第14章: Javaのマルチスレッド 〜並行処理で効率的なプログラムを作る〜

現代のコンピュータでは、複数の処理を同時に行うことが求められます。これを実現するための仕組みがマルチスレッドです。Javaは、強力なマルチスレッドサポートを提供しており、複数のタスクを同時に実行することで、プログラムのパフォーマンスを大幅に向上させることができます。特に、複雑な計算やI/O処理、リアルタイム処理を行う際に、マルチスレッドを活用することが重要です。

この章では、Javaのマルチスレッドプログラミングの基本から、スレッドの作成と管理、同期の仕組み、デッドロック回避、スレッドプールといった高度なトピックまでを解説します。

具体的には、以下の内容をカバーします:

  • スレッドの基本
  • スレッドの作成方法
  • スレッドの同期と排他制御
  • デッドロックとその回避
  • スレッドプールの利用

14.1 マルチスレッドとは?

スレッドとは、プログラム内で実行される処理の最小単位を指します。Javaプログラムはデフォルトではシングルスレッド(単一のスレッド)で動作しますが、マルチスレッドを使用することで、複数のスレッドが同時に実行されるようになります。

例えば、1つのスレッドで計算を行いながら、もう1つのスレッドでファイルの読み込みを並行して行うことができます。これにより、プログラムの応答性やパフォーマンスを向上させることができます。

14.1.1 マルチスレッドの利点

  • 並行処理の実現: 複数のタスクを同時に実行することで、時間を節約し、処理効率を高めることができる。
  • リソースの効率的な活用: CPUやメモリなどのリソースを最大限に活用できる。
  • 応答性の向上: ユーザーインターフェースのプログラムなどでは、バックグラウンドで処理を行いながら、メインスレッドでの応答を維持することが可能。

14.2 Javaにおけるスレッドの作成方法

Javaでは、スレッドを作成して実行するために、2つの主な方法が用意されています:

  1. Threadクラスを継承する方法
  2. Runnableインターフェースを実装する方法

14.2.1 Threadクラスを継承する方法

Threadクラスを継承してスレッドを作成する方法は、最も基本的なアプローチの1つです。この方法では、Threadクラスのrun()メソッドをオーバーライドして、実行したい処理を記述します。

// Threadクラスを継承してスレッドを定義
class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(getName() + "の実行: " + i);
}
}
}

public class Main {
public static void main(String[] args) {
// スレッドのインスタンスを作成
MyThread thread1 = new MyThread();
MyThread thread2 = new MyThread();

// スレッドの開始
thread1.start();
thread2.start();
}
}

このコードでは、MyThreadクラスがThreadクラスを継承しており、2つのスレッドが同時に実行されます。start()メソッドを呼び出すことで、スレッドが起動し、run()メソッドの処理が実行されます。

14.2.2 Runnableインターフェースを実装する方法

より柔軟なスレッドの作成方法として、Runnableインターフェースを実装する方法があります。この方法では、Threadクラスを継承せずに、Runnableインターフェースのrun()メソッドを実装します。

// Runnableインターフェースを実装
class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + "の実行: " + i);
}
}
}

public class Main {
public static void main(String[] args) {
// Runnableを実装したクラスのインスタンスを作成
MyRunnable runnable = new MyRunnable();

// ThreadクラスのコンストラクタにRunnableを渡してスレッドを作成
Thread thread1 = new Thread(runnable);
Thread thread2 = new Thread(runnable);

// スレッドの開始
thread1.start();
thread2.start();
}
}

Runnableインターフェースを使うことで、クラスの多重継承が可能となり、柔軟な設計が可能です。また、スレッドに渡す処理を明示的に分離できるため、コードが整理されやすくなります。

14.2.3 ラムダ式を使ったスレッドの作成

Java 8以降、Runnableインターフェースをラムダ式で簡潔に表現することができます。

public class Main {
public static void main(String[] args) {
// ラムダ式でRunnableを実装してスレッドを作成
Thread thread = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + "の実行: " + i);
}
});

// スレッドの開始
thread.start();
}
}

ラムダ式を使うことで、スレッドの作成がさらに簡潔になります。


14.3 スレッドの同期と排他制御

マルチスレッドプログラミングでは、複数のスレッドが同時に同じリソースにアクセスすることがあり、競合状態(Race Condition)が発生する可能性があります。これを避けるために、スレッド間の同期が必要です。

Javaでは、synchronizedキーワードを使って、複数のスレッドが同時にリソースにアクセスしないようにする排他制御が提供されています。

14.3.1 synchronizedキーワードの使い方

synchronizedキーワードを使うことで、特定のメソッドやブロックをクリティカルセクションとして指定し、その部分に同時にアクセスできるスレッドを1つに制限します。

class Counter {
private int count = 0;

// synchronizedメソッドで排他制御
public synchronized void increment() {
count++;
}

public int getCount() {
return count;
}
}

public class Main {
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();

// 複数のスレッドでカウンタを同時に操作
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
});

Thread thread2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
});

thread1.start();
thread2.start();

// スレッドの終了を待機
thread1.join();
thread2.join();

System.out.println("カウンタの値: " + counter.getCount());
}
}

このコードでは、increment()メソッドがsynchronizedで保護されているため、複数のスレッドが同時にこのメソッドにアクセスすることが防がれ、正しいカウンタの値が保証されます。

14.3.2 synchronizedブロックの使い方

メソッド全体を同期するのではなく、特定のブロックだけを同期する場合は、synchronizedブロックを使います。

class Counter {
private int count = 0;

public void increment() {
// このブロックだけを同期
synchronized (this) {
count++;
}
}

public int getCount() {
return count;
}
}

14.4 デッドロックとその回避

デッドロックは、2つ以上のスレッドがそれぞれ相手の持つリソースを待ち続ける状況です。この状態になると、全てのスレッドが停止してしまい、プログラムが動かなくなります。デッドロックを回避するためには、適切な設計とリソース管理が必要です。

14.4.1 デッドロックの例

以下のコードは、2つのスレッドがデッドロックに陥る例です。

class Resource {
public synchronized void methodA(Resource other) {
System.out.println("Method A is running...");
other.methodB();
}

public synchronized void methodB() {
System.out.println("Method B is running...");
}
}

public class Main {
public static void main(String[] args) {
Resource resource1 = new Resource();
Resource resource2 = new Resource();

// スレッド1がresource1のmethodAを呼び出し、resource2を待機
Thread thread1 = new Thread(() -> resource1.methodA(resource2));

// スレッド2がresource2のmethodAを呼び出し、resource1を待機
Thread thread2 = new Thread(() -> resource2.methodA(resource1));

thread1.start();
thread2.start();
}
}

この例では、スレッド1とスレッド2が互いに相手のリソースを待ち続けることで、デッドロックが発生します。

14.4.2 デッドロック回避策

デッドロックを回避するための一般的な方法には、次のようなものがあります:

  • リソース取得の順序を統一することで、競合の可能性をなくす。
  • タイムアウトを設定し、スレッドが特定の時間内にリソースを取得できなかった場合に中断させる。
  • 手動でリソースの競合を管理し、デッドロックを予防するロジックを設計する。

14.5 スレッドプール

マルチスレッドの処理では、頻繁にスレッドを作成・破棄することがパフォーマンスに悪影響を与える可能性があります。そこで、スレッドプールを利用することで、スレッドの再利用と管理を効率化できます。

14.5.1 ExecutorServiceを使ったスレッドプールの作成

Javaでは、ExecutorServiceインターフェースを使ってスレッドプールを管理できます。これにより、スレッドの再利用やタスクのキュー処理が可能です。

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++) {
final int taskNumber = i;
executor.submit(() -> {
System.out.println("タスク " + taskNumber + " を実行中: " +
Thread.currentThread().getName());
});
}

// スレッドプールを終了
executor.shutdown();
}
}

このコードでは、3つのスレッドを持つスレッドプールが作成され、5つのタスクが順次実行されます。スレッドプールは、タスクの並列処理やスレッドの効率的な管理に最適です。


14.6 Javaのマルチスレッドまとめ

この章では、Javaにおけるマルチスレッドの基本から、スレッドの作成方法、スレッド間の同期、デッドロックの回避、そしてスレッドプールの利用までを学びました。マルチスレッドプログラミングは、効率的な並行処理を実現し、複雑なタスクを効率的に処理するために欠かせない技術です。

【プログラミング(JAVA)】Recordクラス

Java 14でプレビュー機能として導入され、Java 16から正式にサポートされたRecordクラスは、データキャリアとしてのシンプルなクラスを簡潔に定義するための新しい構文を提供します。従来のPOJO(Plain Old Java Object)やDTO(Data Transfer Object)に代わるものとして、より簡潔かつ安全にデータを表現できます。

本記事では、Recordクラスの基本文法から、コードサンプル、応用的な使い方、利用ケース、実装時の注意点について解説します。

Recordクラスの基本文法

Recordクラスは、イミュータブルなデータクラスを簡単に定義するためのJavaの新しい構文です。Recordクラスを定義すると、自動的に以下のメソッドが生成されます。

  1. コンストラクタ
  2. getterメソッド(name()形式)
  3. equals()メソッド
  4. hashCode()メソッド
  5. toString()メソッド

基本文法

public record Person(String name, int age) {}

このコードで、PersonというRecordクラスが定義され、以下の機能が自動的に提供されます。

  • コンストラクタ: new Person(String name, int age)
  • getter: name()age()
  • equals()hashCode(): オブジェクトの内容に基づく比較とハッシュコード生成
  • toString(): "Person[name=John, age=30]"のような形式でオブジェクトを文字列化

Recordクラスのコードサンプル

以下は、Recordクラスを使用した基本的なコードサンプルです。

public class RecordExample {
public static void main(String[] args) {
Person person = new Person("John Doe", 30);

// getterメソッドを使用して値を取得
System.out.println(person.name()); // 出力: John Doe
System.out.println(person.age()); // 出力: 30

// 自動生成されたtoString()メソッドの使用
System.out.println(person); // 出力: Person[name=John Doe, age=30]

// equals()とhashCode()の使用
Person anotherPerson = new Person("John Doe", 30);
System.out.println(person.equals(anotherPerson)); // 出力: true
System.out.println(person.hashCode() == anotherPerson.hashCode()); // 出力: true
}

public record Person(String name, int age) {}
}

応用的な使い方

Recordクラスは、コンパクトなデータクラスとしてだけでなく、様々な応用的なシナリオにも活用できます。

1. バリデーション付きコンストラクタの定義

Recordクラスでも、コンストラクタに追加のロジックを含めることができます。例えば、引数のバリデーションを行う場合、以下のように定義します。

public record Person(String name, int age) {
public Person {
if (age < 0) {
throw new IllegalArgumentException("Age cannot be negative");
}
}
}

2. ネストされたRecordクラス

Recordクラスは、他のRecordクラスやクラス内にネストして定義することも可能です。

public record Address(String street, String city) {}

public record Person(String name, int age, Address address) {}

3. レコードのパターンマッチング

Java 16以降では、パターンマッチングとRecordクラスを組み合わせることで、より強力なデータ操作が可能になります。

public static String getName(Object obj) {
if (obj instanceof Person(String name, int age)) {
return name;
}
return "Unknown";
}

Recordクラスを利用すべきケース

Recordクラスは、以下のようなケースで特に有効です。

  1. シンプルなデータキャリア
    • 値を保持するだけのシンプルなクラスに最適です。従来のPOJOやDTOの代わりに利用することで、コードの冗長性を減らし、明確さを向上させます。
  2. 不変データの表現
    • イミュータブルなデータ構造が求められる場合に適しています。スレッドセーフな設計が必要な場合にも効果的です。
  3. 簡潔なデータ転送オブジェクト
    • データ転送オブジェクト(DTO)として、外部とのデータ交換や、APIレスポンスの形式定義に使用することで、コードが簡潔になります。

実装時に気を付けるべき事項

  1. 不変性の理解
    • Recordクラスは不変(イミュータブル)であり、定義されたフィールドは不変です。この特性により、データが変更されることがないことを前提に設計する必要があります。
  2. 継承の制限
    • Recordクラスは、他のクラスを継承することができません。また、Recordクラス自体を継承することもできません。この制約を理解し、他のデザインパターンを利用する必要があります。
  3. バリデーションの注意
    • Recordクラスでは、コンストラクタ内で引数のバリデーションを行うことができますが、複雑なロジックが必要な場合は、従来のクラスで実装した方が適切な場合もあります。
  4. データサイズの管理
    • Recordクラスは簡潔にデータを表現できますが、大量のフィールドやデータが含まれる場合、適切なデータモデリングを行わないと、パフォーマンスに影響を及ぼす可能性があります。

公式ドキュメント

Recordクラスに関する詳細なリファレンスは、公式ドキュメントを参照してください。

まとめ

JavaのRecordクラスは、データキャリアとしてのクラス定義を簡潔に行える強力なツールです。不変性を持ち、シンプルなデータを扱う場面で特に有効です。Recordクラスを適切に利用することで、コードの明確さと保守性を大幅に向上させることができます。実装時の注意点を守りつつ、適切なケースで活用しましょう。

【プログラミング(JAVA)】Stream APIについて

Java 8で導入されたStream APIは、コレクションや配列などのデータソースに対して、データの操作を宣言的に行うための強力なツールです。この記事では、Stream APIの基本文法、コードサンプル、応用的な使い方、利用ケース、実装時の注意点について詳しく解説します。

Stream APIとは?

Stream APIは、データ処理のための宣言的なアプローチを提供します。従来の外部イテレーション(forループなど)と異なり、内部イテレーションを使用して、データを操作します。これにより、コードが簡潔かつ読みやすくなり、並列処理の恩恵も得やすくなります。

基本文法

Stream APIの基本的な流れは次のようになります。

  1. データソース: コレクションや配列、ファイルなどからStreamを生成します。
  2. 中間操作: フィルタリング、マッピング、ソートなどの操作を行います。中間操作は遅延評価されます。
  3. 終端操作: 集約、収集、出力などの操作を行います。終端操作が実行されると、Streamは消費されます。

基本的な操作例

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class StreamExample {
public static void main(String[] args) {
List<String> names = Arrays.asList("John", "Jane", "Jack", "Doe");

List<String> result = names.stream()
.filter(name -> name.startsWith("J")) // フィルタリング
.map(String::toUpperCase) // 文字列を大文字に変換
.sorted() // アルファベット順にソート
.collect(Collectors.toList()); // 結果をリストに収集

result.forEach(System.out::println); // 結果を出力
}
}

応用的な使い方

Stream APIは、基本的なフィルタリングやマッピングだけでなく、複雑なデータ処理も簡潔に表現できます。以下に、いくつかの応用的な使い方を紹介します。

1. 複数条件のフィルタリング

List<String> filteredNames = names.stream()
.filter(name -> name.startsWith("J") && name.length() > 3)
.collect(Collectors.toList());

2. 並列ストリームを使用したパフォーマンス向上

並列ストリームを使用すると、大量のデータを効率的に処理できます。

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
int sum = numbers.parallelStream()
.reduce(0, Integer::sum); // 並列で集約処理

3. グループ化と集計

Collectors.groupingByを使用して、データをグループ化し、さらに集計処理を行います。

import java.util.Map;
import java.util.stream.Collectors;

Map<Integer, List<String>> groupedByLength = names.stream()
.collect(Collectors.groupingBy(String::length));

4. Optionalと組み合わせた利用

Stream APIとOptionalを組み合わせることで、安全な処理を実現できます。

import java.util.Arrays;
import java.util.List;
import java.util.Optional;

public class StreamOptionalExample {
public static void main(String[] args) {
List<String> names = Arrays.asList("John", "Jane", "Jack", "Doe");

// Streamでフィルタリングして最初の要素を取得
Optional<String> firstNameStartingWithJ = names.stream()
.filter(name -> name.startsWith("J"))
.findFirst();

// Optionalを使って結果を安全に扱う
firstNameStartingWithJ.ifPresentOrElse(
name -> System.out.println("First name starting with J: " + name),
() -> System.out.println("No name starting with J found")
);
}

※findFirst()やfindAny()といった終端操作は、該当する要素が見つからない場合にOptionalを返します。これにより、結果が存在しないケースにも安全に対処できます。

解説
  • filter: Stream内で特定の条件に合致する要素をフィルタリングします。
  • findFirst:条件に合致する最初の要素をOptionalとして返します。
  • Optional.ifPresentOrElse :値が存在する場合には処理を行い、存在しない場合には代替の処理を行います。

Stream APIを利用すべきケース

Stream APIは以下のようなケースで有効です。

  • 大量のデータを効率的に処理したい場合: 内部イテレーションによる遅延評価と並列処理の組み合わせにより、大規模データを効率的に処理できます。
  • コードの可読性を向上させたい場合: Stream APIを使用すると、複雑な処理を簡潔に表現できます。
  • データの変換、フィルタリング、集計が必要な場合: Stream APIは、データの操作をチェーン形式で記述でき、柔軟かつ強力なデータ処理が可能です。

実装時に気を付けるべき事項

  1. 遅延評価の理解
    Streamの中間操作は遅延評価され、終端操作が実行されるまで実行されません。この特性を理解していないと、意図しない結果を招くことがあります。
  2. 並列ストリームの適用
    並列ストリームを使用することでパフォーマンスが向上することがありますが、適用が不適切な場合、逆にオーバーヘッドが発生することがあります。データのサイズや処理の内容に応じて適用を判断する必要があります。
  3. 可変データ構造の扱い
    可変データ構造(例: リスト)をStream APIで操作する際、破壊的な変更が加わらないように注意が必要です。並列処理では特に注意が必要です。
  4. 終端操作の一度限りの利用
    Streamは一度消費されると再利用できません。同じデータに対して複数回の操作が必要な場合は、新しいStreamを生成する必要があります。

公式ドキュメント

詳細なリファレンスや追加の情報は、公式ドキュメントを参照してください。

まとめ

Java Stream APIは、コレクションや配列などのデータを効率的に操作するための強力なツールです。基本的なフィルタリングやマッピングから、並列処理やグループ化、Optionalとの組み合わせまで、さまざまな操作を簡潔に記述できます。適切なケースで利用することで、コードの可読性やパフォーマンスを向上させることが可能です。ただし、遅延評価や並列処理の特性を理解し、実装時の注意点を守ることが重要です。

【プログラミング(JAVA)】Ver12以降のAPI差分一覧

Javaはバージョンごとに新しいAPIや機能が追加され、開発者にとってはこれらの変更を把握することが重要です。本記事では、Java 12以降の各バージョンごとの主要なAPI差分を一覧形式で紹介します。各バージョンの新機能や変更点を理解することで、最新のJavaを効果的に活用できます。

参考サイト


Java 12 (2019年3月)

  • JEP 189: Shenandoah: A Low-Pause-Time Garbage Collector (Experimental)
    新しいガベージコレクターShenandoahが追加され、低レイテンシアプリケーションに対応しました。
  • JEP 334: JVM Constants API
    新しいjava.lang.invoke.constantパッケージが追加され、定数プールの管理が強化されました。
  • JEP 325: Switch Expressions (Preview)
    switch文が式として使用可能になり、コードの簡潔さと安全性が向上しました。

Java 13 (2019年9月)

  • JEP 354: Switch Expressions (Preview)
    Java 12で導入されたswitch式が改善され、再度プレビューとして提供されました。
  • JEP 355: Text Blocks (Preview)
    複数行の文字列を簡単に記述できる「テキストブロック」が導入され、可読性が向上しました。
  • JEP 350: Dynamic CDS Archives
    クラスデータ共有(CDS)アーカイブを動的に作成できるようになり、アプリケーションの起動時間が短縮されました。

Java 14 (2020年3月)

  • JEP 361: Switch Expressions
    switch式が正式に導入され、プレビューから標準機能になりました。
  • JEP 368: Text Blocks
    複数行文字列の記述が正式にサポートされ、コードの記述が簡潔に。
  • JEP 359: Records (Preview)
    データキャリア用のクラスを簡単に定義できるrecordがプレビュー機能として導入。
  • JEP 305: Pattern Matching for instanceof (Preview)
    instanceof演算子にパターンマッチングが追加され、キャストの冗長性が減少。

Java 15 (2020年9月)

  • JEP 360: Sealed Classes (Preview)
    クラス階層を制限できるシールドクラスが導入され、コードの安全性が向上。
  • JEP 378: Text Blocks
    テキストブロックが標準機能に。
  • JEP 339: Edwards-Curve Digital Signature Algorithm (EdDSA)
    セキュリティアルゴリズムとしてEdDSAが追加されました。
  • JEP 371: Hidden Classes
    JVMで動的に生成され、外部からのアクセスを制限できる「隠れクラス」が導入。

Java 16 (2021年3月)

  • JEP 394: Pattern Matching for instanceof
    パターンマッチングが正式に導入され、instanceofの使い勝手が向上。
  • JEP 395: Records
    recordクラスが標準機能に。
  • JEP 338: Vector API (Incubator)
    ベクトル操作を効率的に行うAPIがインキュベータモジュールとして追加。
  • JEP 376: ZGC: Concurrent Thread-Stack Processing
    ZGC(Z Garbage Collector)でスレッドスタックの同時処理が可能になり、パフォーマンスが向上。

Java 17 (2021年9月) – LTS (Long-Term Support)

  • JEP 409: Sealed Classes
    シールドクラスが正式に導入され、クラスの継承を制限する機能が利用可能に。
  • JEP 356: Enhanced Pseudo-Random Number Generators
    擬似乱数生成器(PRNG)が強化され、ランダム数の生成が柔軟に。
  • JEP 382: New macOS Rendering Pipeline
    macOS向けの新しいレンダリングパイプラインが追加され、UIの描画が改善。
  • JEP 406: Pattern Matching for switch (Preview)
    switch文にパターンマッチングが導入され、条件分岐がより柔軟に。

Java 18 (2022年3月)

  • JEP 413: Code Snippets in Java API Documentation
    JavaDocにコードスニペットを埋め込む機能が追加され、ドキュメントの可読性が向上。
  • JEP 408: Simple Web Server
    簡単なWebサーバーをJavaで実行できる機能が追加され、開発者向けのテストが容易に。
  • JEP 416: Reimplement Core Reflection with Method Handles
    コアリフレクションがメソッドハンドルを使って再実装され、パフォーマンスが改善。

Java 19 (2022年9月)

  • JEP 405: Record Patterns (Preview)
    recordのパターンマッチングがプレビューとして導入され、より簡潔なコードが記述可能に。
  • JEP 424: Foreign Function & Memory API (Preview)
    JNIの代替として、他言語関数やメモリ操作のためのAPIがプレビューとして追加。
  • JEP 422: Linux/RISC-V Port
    Linux/RISC-V向けのポートが追加され、Javaのプラットフォームサポートが拡充。

Java 20 (2023年3月)

  • JEP 429: Scoped Values (Incubator)
    スレッドローカルの代替となる、スコープ付きの値を扱うAPIがインキュベータモジュールとして導入。
  • JEP 432: Record Patterns (Second Preview)
    レコードパターンが第2のプレビューとして再導入され、さらなる改善が。
  • JEP 433: Pattern Matching for switch (Fourth Preview)
    switch文のパターンマッチングが第4のプレビューとして更新され、より強力な機能に。

Java 21 (2023年9月) – LTS

  • JEP 445: Unnamed Patterns and Variables (Preview)
    名前のないパターンと変数を扱う新しい構文がプレビューとして導入され、簡潔なコードが記述可能に。
  • JEP 441: Pattern Matching for switch
    パターンマッチングがswitch文の正式機能として導入され、柔軟な条件分岐が可能に。
  • JEP 430: String Templates (Preview)
    テンプレート文字列を簡単に扱うための構文がプレビューとして追加され、文字列操作が強化。

Java 22 (2024年3月予定)

  • JEP 453: Structured Concurrency (Preview)
    構造化された並行処理をサポートする新しいAPIがプレビューとして導入され、並行処理の管理が簡素化。
  • JEP 442: Foreign Function & Memory API (Third Preview)
    他言語との相互運用性を高めるためのAPIが第3のプレビューとして追加され、さらなる安定性と機能強化が。
  • JEP 452: Key Encapsulation Mechanism API
    鍵カプセル化メカニズム(KEM)のサポートが追加され、暗号化関連の操作が強化。

まとめ

Javaの各バージョンで導入されたAPIや機能の変更点を理解することは、最新のJavaを活用する上で非常に重要です。Java 12以降、さまざまな新機能や改善が行われており、これらを効果的に取り入れることで、開発効率とコードのパフォーマンスを向上させることができます。詳細については、Java New API since JDK 11 を参照してください。

Javaの進化を追いかけ、最新の技術をプロジェクトに取り入れましょう。