変数とデータ型 宣言・初期化・基本型・参照型
変数を宣言し、値を割り当てる基本的な方法です。
基本構文
// 宣言のみ
データ型 変数名;
// 宣言と初期化
データ型 変数名 = 初期値;
// final修飾子 (定数)
final データ型 定数名 = 値;
プリミティブ型 (基本データ型)
メモリ上に直接値を保持する型です。
型 | 説明 | サイズ | 例 |
---|---|---|---|
byte |
符号付き整数 | 1バイト | byte b = 100; |
short |
符号付き整数 | 2バイト | short s = 30000; |
int |
符号付き整数 (デフォルト) | 4バイト | int i = 1234567890; |
long |
符号付き整数 (大きい値) | 8バイト | long l = 1234567890123456789L; (L/lを付ける) |
float |
単精度浮動小数点数 | 4バイト | float f = 3.14f; (f/Fを付ける) |
double |
倍精度浮動小数点数 (デフォルト) | 8バイト | double d = 3.1415926535; |
boolean |
真偽値 | 1ビット (理論上) | boolean flag = true; |
char |
単一文字 (Unicode) | 2バイト | char c = 'A'; , char kanji = '字'; |
参照型
オブジェクトへの参照(メモリアドレス)を保持する型です。
- クラス型 (例:
String
,ArrayList
, 自作クラス) - インターフェース型 (例:
List
,Map
) - 配列型 (例:
int[]
,String[]
)
// String (文字列 - 特別扱いされることが多い参照型)
String message = "こんにちは Java!";
String emptyString = new String(); // 空の文字列オブジェクト
// 配列
int[] scores = new int[5]; // int型の要素を5つ持つ配列
scores[0] = 100;
String[] names = {"Alice", "Bob", "Charlie"}; // 初期化子を使った配列生成
// クラスのインスタンス化
java.util.Date today = new java.util.Date(); // Dateクラスのオブジェクト生成
MyClass myObject = new MyClass(); // 自作クラスのオブジェクト生成
💡 参照型の変数は、null
を代入することができます。これは、どのオブジェクトも参照していない状態を示します。
String text = null;
MyClass obj = null;
演算子 ⚙️
計算、比較、論理演算などを行うための記号です。
算術演算子
演算子 | 意味 | 例 (int x = 10, y = 3; ) |
---|---|---|
+ | 加算 | x + y (結果: 13) |
- | 減算 | x - y (結果: 7) |
* | 乗算 | x * y (結果: 30) |
/ | 除算 (整数同士の場合は小数点以下切り捨て) | x / y (結果: 3) |
% | 剰余 (余り) | x % y (結果: 1) |
++ | インクリメント (1増やす) | x++ (後置), ++x (前置) |
-- | デクリメント (1減らす) | y-- (後置), --y (前置) |
int a = 5;
a++; // a は 6 になる
int b = 10;
int c = ++b; // b は 11 になり、c も 11 になる
int d = 10;
int e = d++; // e は 10 になり、d は 11 になる (後置)
double result = 10.0 / 3.0; // double型同士の除算 (結果: 3.333...)
比較演算子
結果は boolean
型 (true
または false
) になります。
演算子 | 意味 | 例 (int x = 10, y = 5; ) |
---|---|---|
== | 等しい | x == y * 2 (結果: true) |
!= | 等しくない | x != y (結果: true) |
> | より大きい | x > y (結果: true) |
< | より小さい | x < y (結果: false) |
>= | 以上 | x >= 10 (結果: true) |
<= | 以下 | y <= 5 (結果: true) |
⚠️ 参照型変数に対して ==
を使用すると、同じオブジェクトを参照しているかどうかの比較になります。内容が同じかどうかを比較するには .equals()
メソッドを使用します(特に String)。
String s1 = new String("abc");
String s2 = new String("abc");
String s3 = s1;
System.out.println(s1 == s2); // false (異なるオブジェクト)
System.out.println(s1.equals(s2)); // true (内容は同じ)
System.out.println(s1 == s3); // true (同じオブジェクトを参照)
論理演算子
boolean
値に対して演算を行います。
演算子 | 意味 | 例 (boolean a = true, b = false; ) |
---|---|---|
&& | 論理 AND (かつ) – 短絡評価あり | a && b (結果: false) |
|| | 論理 OR (または) – 短絡評価あり | a || b (結果: true) |
! | 論理 NOT (否定) | !a (結果: false) |
& | 論理 AND (短絡評価なし) | a & b (結果: false) |
| | 論理 OR (短絡評価なし) | a | b (結果: true) |
^ | 排他的論理 OR (XOR) | a ^ b (結果: true), a ^ a (結果: false) |
💡 短絡評価: &&
では左辺が false
なら右辺を評価しません。||
では左辺が true
なら右辺を評価しません。これにより、nullチェックなどを安全に行えます。
String str = null;
// strがnullでも str.isEmpty() は評価されないため NullPointerException が起きない
if (str != null && str.isEmpty()) {
System.out.println("文字列は空です");
}
// strがnullの場合、str.length() が評価されて NullPointerException が発生する可能性がある
// if (str == null | str.length() == 0) { ... } // & や | は短絡評価しないので注意
代入演算子
演算子 | 意味 | 例 (int x = 10; ) |
---|---|---|
= | 代入 | x = 20; (xは20になる) |
+= | 加算して代入 | x += 5; (x = x + 5; と同じ。xは15になる) |
-= | 減算して代入 | x -= 3; (x = x - 3; と同じ。xは7になる) |
*= | 乗算して代入 | x *= 2; (x = x * 2; と同じ。xは20になる) |
/= | 除算して代入 | x /= 4; (x = x / 4; と同じ。xは2になる) |
%= | 剰余を計算して代入 | x %= 3; (x = x % 3; と同じ。xは1になる) |
&= | ビット AND して代入 | x &= 6; (x = x & 6; xは2になる ※1010 & 0110 = 0010) |
|= | ビット OR して代入 | x |= 3; (x = x | 3; xは11になる ※1010 | 0011 = 1011) |
^= | ビット XOR して代入 | x ^= 5; (x = x ^ 5; xは15になる ※1010 ^ 0101 = 1111) |
<<= | 左シフトして代入 | x <<= 1; (x = x << 1; xは20になる) |
>>= | 右シフト(算術)して代入 | x >>= 1; (x = x >> 1; xは5になる) |
>>>= | 右シフト(論理)して代入 | x >>>= 1; (x = x >>> 1; xは5になる。負数の場合に >> と異なる) |
ビット演算子
整数型のビットパターンに対して直接操作を行います。
演算子 | 意味 | 例 (int a = 5, b = 3; ) |
ビット表現 |
---|---|---|---|
& | ビット AND | a & b (結果: 1) | 0101 & 0011 = 0001 |
| | ビット OR | a | b (結果: 7) | 0101 | 0011 = 0111 |
^ | ビット XOR | a ^ b (結果: 6) | 0101 ^ 0011 = 0110 |
~ | ビット NOT (補数) | ~a (結果: -6) | ~0000…0101 = 1111…1010 |
<< | 左シフト | a << 1 (結果: 10) | 0101 << 1 = 1010 |
>> | 右シフト (算術シフト) | a >> 1 (結果: 2) / (-5) >> 1 (結果: -3) | 符号ビットを維持 |
>>> | 右シフト (論理シフト) | a >>> 1 (結果: 2) / (-5) >>> 1 (結果: 大きな正の数) | 左端に0を詰める |
三項演算子
条件に基づいて値を割り当てる簡潔な方法です。
条件式 ? trueの場合の値 : falseの場合の値
int score = 75;
String result = (score >= 60) ? "合格" : "不合格"; // result は "合格" になる
int x = 10;
int y = 20;
int max = (x > y) ? x : y; // max は 20 になる
instanceof 演算子
オブジェクトが特定のクラスまたはそのサブクラスのインスタンスであるか、あるいは特定のインターフェースを実装しているかを確認します。
オブジェクト参照変数 instanceof 型
String str = "Hello";
if (str instanceof String) {
System.out.println("strはStringのインスタンスです。"); // 出力される
}
Object obj = java.time.LocalDate.now();
if (obj instanceof String) {
System.out.println("objはStringのインスタンスです。");
} else if (obj instanceof java.time.LocalDate) {
System.out.println("objはLocalDateのインスタンスです。"); // 出力される
}
// Java 14 以降のパターンマッチング
if (obj instanceof String s) { // instanceof と同時にキャストと変数束縛
System.out.println("文字列の長さ: " + s.length());
} else if (obj instanceof java.time.LocalDate d) {
System.out.println("年: " + d.getYear());
}
制御フロー ➡️
プログラムの実行順序を制御する構文です。
条件分岐 (if-else if-else)
int score = 85;
if (score >= 90) {
System.out.println("優");
} else if (score >= 75) {
System.out.println("良"); // これが実行される
} else if (score >= 60) {
System.out.println("可");
} else {
System.out.println("不可");
}
条件分岐 (switch)
特定の値に基づいて処理を分岐します。byte
, short
, char
, int
, enum
, String
(Java 7以降) 型が使用できます。
int dayOfWeek = 3;
String dayName;
switch (dayOfWeek) {
case 1:
dayName = "月曜日";
break; // breakがないと次のcaseも実行される (フォールスルー)
case 2:
dayName = "火曜日";
break;
case 3:
dayName = "水曜日";
break; // dayNameは "水曜日" になる
case 4:
dayName = "木曜日";
break;
case 5:
dayName = "金曜日";
break;
case 6:
case 7: // 複数のcaseをまとめる
dayName = "週末";
break;
default: // どのcaseにも一致しない場合
dayName = "不明な曜日";
break;
}
System.out.println(dayName); // 水曜日
// --- Java 14以降の switch 式 (値を返す) ---
String season = switch (month) {
case 12, 1, 2 -> "冬"; // -> を使うと break 不要
case 3, 4, 5 -> "春";
case 6, 7, 8 -> "夏";
case 9, 10, 11 -> "秋";
default -> { // 複数行の処理も可能
System.out.println("不正な月です: " + month);
yield "不明"; // yield で値を返す
}
};
⚠️ switch
文では、各 case
の終わりに break;
を書かないと、後続の case
ブロックが実行されてしまう「フォールスルー」に注意が必要です。意図しない限り break;
を記述します。Java 14以降の ->
を使う形式では不要です。
繰り返し (for)
指定回数、または条件が満たされる間、処理を繰り返します。
// 基本的なforループ (カウンタ変数)
for (int i = 0; i < 5; i++) { // 初期化式; 条件式; 更新式
System.out.println("繰り返し " + (i + 1) + " 回目");
}
// 0, 1, 2, 3, 4 で実行される (計5回)
// 拡張forループ (forEach) - 配列やコレクションの要素を順に処理
String[] fruits = {"Apple", "Banana", "Cherry"};
for (String fruit : fruits) {
System.out.println("フルーツ: " + fruit);
}
java.util.List numbers = java.util.Arrays.asList(10, 20, 30);
for (int num : numbers) {
System.out.println("数値: " + num);
}
繰り返し (while)
条件式が true
の間、処理を繰り返します。最初に条件を評価します。
int count = 0;
while (count < 3) {
System.out.println("whileループ: " + count);
count++;
}
// 0, 1, 2 で実行される
// 無限ループ (意図的に作る場合。通常はループ内に脱出条件が必要)
// while (true) {
// // ... 処理 ...
// if (/* 脱出条件 */) {
// break;
// }
// }
繰り返し (do-while)
処理を一度実行してから、条件式が true
の間、処理を繰り返します。最後に条件を評価します。
int num = 5;
do {
System.out.println("do-whileループ: " + num); // 最初の一回は必ず実行される
num--;
} while (num > 0);
// 5, 4, 3, 2, 1 で実行される
ループの制御 (break, continue)
break
: 現在のループ(for
,while
,do-while
,switch
)を即座に終了します。continue
: 現在のループの今回の反復処理をスキップし、次の反復処理(または条件判定)に進みます。
// break の例
for (int i = 0; i < 10; i++) {
if (i == 5) {
break; // iが5になったらループを抜ける
}
System.out.print(i + " "); // 0 1 2 3 4 が出力される
}
System.out.println("\nループ終了");
// continue の例
for (int i = 0; i < 5; i++) {
if (i == 2) {
continue; // iが2の場合は、以下のprintlnをスキップして次の反復へ
}
System.out.print(i + " "); // 0 1 3 4 が出力される
}
System.out.println("\nループ終了");
// ラベル付き break/continue (ネストしたループで使用)
outerLoop: // ラベル定義
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
if (i == 1 && j == 1) {
System.out.println("\n外側ループを終了");
break outerLoop; // outerLoop ラベルが付いたループを抜ける
}
if (i == 0 && j == 1) {
System.out.println("\n内側ループの次の反復へ");
continue outerLoop; // outerLoop ラベルが付いたループの次の反復へ (i=1 になる)
}
System.out.print("[" + i + "," + j + "] ");
}
}
// 出力例:
// [0,0]
// 内側ループの次の反復へ
// [1,0]
// 外側ループを終了
メソッド (関数) 🧩
特定の処理をまとめて名前を付け、再利用可能にする仕組みです。
メソッド定義
修飾子 戻り値の型 メソッド名(引数リスト) {
// メソッドの処理本体
return 戻り値; // 戻り値がある場合
}
// 例: 二つの整数の合計を返すメソッド
public static int add(int a, int b) {
int sum = a + b;
return sum;
}
// 例: メッセージを表示するメソッド (戻り値なし)
public static void printMessage(String message) {
System.out.println("メッセージ: " + message);
// void 型は return 文が不要 (または return; と書ける)
}
// 例: 引数なしで現在時刻を返すメソッド
public java.time.LocalTime getCurrentTime() {
return java.time.LocalTime.now();
}
- 修飾子:
public
,private
,protected
(アクセス制御),static
(静的メソッド),final
(オーバーライド禁止),abstract
(抽象メソッド) など。 - 戻り値の型: メソッドが返す値のデータ型。値を返さない場合は
void
を指定。 - メソッド名: 処理内容を表す動詞を含む名前が一般的 (例:
calculateSum
,getUserName
)。 - 引数リスト: メソッドが処理に必要な値を受け取るための変数宣言 (
型名 引数名
)。複数ある場合はカンマで区切る。引数がない場合は()
と書く。
メソッド呼び出し
// static メソッドの呼び出し (クラス名.メソッド名)
int result = MyMathUtils.add(10, 5); // 上記の add メソッドを想定
System.out.println("合計: " + result); // 合計: 15
MyMessager.printMessage("Hello!"); // 上記の printMessage メソッドを想定
// インスタンスメソッドの呼び出し (オブジェクト変数.メソッド名)
TimeProvider provider = new TimeProvider(); // 上記の getCurrentTime を持つクラスを想定
java.time.LocalTime now = provider.getCurrentTime();
System.out.println("現在時刻: " + now);
// 戻り値を使わない呼び出しも可能
provider.getCurrentTime(); // 時刻を取得するが、その値は使わない
メソッドオーバーロード
同じクラス内で、メソッド名は同じでも引数の型、数、または順序が異なるメソッドを複数定義することです。
public class Calculator {
// int 型の加算
public int add(int a, int b) {
System.out.println("int + int");
return a + b;
}
// double 型の加算
public double add(double a, double b) {
System.out.println("double + double");
return a + b;
}
// 3つの int 型の加算
public int add(int a, int b, int c) {
System.out.println("int + int + int");
return a + b + c;
}
// 呼び出し例
Calculator calc = new Calculator();
int sum1 = calc.add(5, 3); // int + int が呼ばれる
double sum2 = calc.add(2.5, 3.1); // double + double が呼ばれる
int sum3 = calc.add(1, 2, 3); // int + int + int が呼ばれる
}
💡 戻り値の型だけが異なるメソッドはオーバーロードできません。
可変長引数 (varargs)
メソッドの引数として、同じ型の引数を0個以上、任意の数だけ受け取れるようにする機能です。メソッド内では配列として扱われます。
// 任意の数の int を受け取り合計を返すメソッド
public static int sum(String label, int... numbers) { // 型... 引数名 (可変長引数は最後に置く)
System.out.print(label + ": ");
int total = 0;
for (int num : numbers) { // numbers は int[] 配列として扱われる
total += num;
System.out.print(num + " ");
}
System.out.println("= " + total);
return total;
}
// 呼び出し例
sum("合計1", 1, 2, 3); // label="合計1", numbers={1, 2, 3} -> 合計1: 1 2 3 = 6
sum("合計2", 10, 20); // label="合計2", numbers={10, 20} -> 合計2: 10 20 = 30
sum("合計3"); // label="合計3", numbers={} (空配列) -> 合計3: = 0
int[] data = {5, 5, 5, 5};
sum("合計4", data); // 配列を渡すことも可能 -> 合計4: 5 5 5 5 = 20
⚠️ 可変長引数は、メソッドの引数リストの中で最後に1つだけ定義できます。
再帰メソッド
メソッドが自分自身の処理の中で、自分自身を呼び出すことです。終了条件を正しく設定しないと無限ループ(StackOverflowError
)になります。
// 階乗を計算する再帰メソッド (n!)
public static long factorial(int n) {
if (n < 0) {
throw new IllegalArgumentException("負の数の階乗は定義されません。");
}
if (n == 0 || n == 1) {
return 1; // ベースケース (終了条件)
} else {
return n * factorial(n - 1); // 再帰呼び出し
}
}
// 呼び出し例
long fact5 = factorial(5); // 5 * factorial(4) -> 5 * 4 * factorial(3) -> ... -> 5 * 4 * 3 * 2 * 1 * factorial(0) -> 120 * 1 = 120
System.out.println("5! = " + fact5);
配列 🔢
同じ型の複数のデータを、連続したメモリ領域に格納するデータ構造です。一度作成するとサイズは変更できません。
配列の宣言と生成
// 宣言 (推奨される形式)
データ型[] 配列名;
int[] scores;
String[] names;
// 宣言 (C言語スタイルの形式、非推奨)
// データ型 配列名[];
// int scores[];
// 配列オブジェクトの生成 (要素数を指定)
// これにより、指定された要素数のメモリ領域が確保され、デフォルト値で初期化される
// 数値型: 0, boolean: false, 参照型: null
scores = new int[5]; // int 型の要素を 5 つ格納できる配列を生成
names = new String[3]; // String 型の要素を 3 つ格納できる配列を生成
// 宣言と生成を同時に行う
double[] temperatures = new double[10];
boolean[] flags = new boolean[100];
配列の初期化
// 生成と同時に初期値を指定 (初期化子)
int[] numbers = {10, 20, 30, 40, 50}; // 要素数は自動的に 5 となる
String[] colors = {"Red", "Green", "Blue"};
// 宣言後に初期化子を使う場合は new データ型[] が必要
int[] data;
// data = {1, 2, 3}; // これはコンパイルエラー
data = new int[]{1, 2, 3};
// 配列生成後に個別に値を代入
scores = new int[3];
scores[0] = 100;
scores[1] = 85;
scores[2] = 92;
// scores[3] = 70; // ArrayIndexOutOfBoundsException が発生 (範囲外アクセス)
配列要素へのアクセス
インデックス(添え字)を使用して個々の要素にアクセスします。インデックスは 0 から始まります。
int[] numbers = {10, 20, 30};
// 値の取得
int firstNumber = numbers[0]; // 10 が取得される
int thirdNumber = numbers[2]; // 30 が取得される
System.out.println("2番目の要素: " + numbers[1]); // 2番目の要素: 20
// 値の更新
numbers[1] = 25;
System.out.println("更新後の2番目の要素: " + numbers[1]); // 更新後の2番目の要素: 25
// 配列の長さ (要素数) の取得
int length = numbers.length; // 3 が取得される
System.out.println("配列の長さ: " + length);
⚠️ インデックスは 0
から 配列の長さ - 1
の範囲です。範囲外のインデックスでアクセスしようとすると ArrayIndexOutOfBoundsException
が発生します。
配列の繰り返し処理
String[] fruits = {"Apple", "Banana", "Cherry"};
// 基本的な for ループを使用
System.out.println("--- 基本 for ループ ---");
for (int i = 0; i < fruits.length; i++) {
System.out.println("インデックス " + i + ": " + fruits[i]);
}
// 拡張 for ループ (forEach) を使用
System.out.println("--- 拡張 for ループ ---");
for (String fruit : fruits) {
System.out.println(fruit);
}
多次元配列
配列の配列として、表形式のようなデータを扱うことができます。
// 2次元配列の宣言と生成 (3行2列)
int[][] matrix = new int[3][2];
// 初期化子を使った生成と初期化
int[][] table = {
{1, 2, 3}, // 1行目 (要素数3)
{4, 5}, // 2行目 (要素数2) - 不揃いな配列も可能 (ジャグ配列/Ragged Array)
{6, 7, 8, 9} // 3行目 (要素数4)
};
// 要素へのアクセス (行インデックス、列インデックス)
matrix[0][0] = 10;
matrix[1][1] = 20;
System.out.println("table[0][1]: " + table[0][1]); // 2
System.out.println("table[1][0]: " + table[1][0]); // 4
System.out.println("table[2][3]: " + table[2][3]); // 9
// 多次元配列の繰り返し処理 (ネストした for ループ)
System.out.println("--- table の内容 ---");
for (int i = 0; i < table.length; i++) { // 行のループ (外側)
for (int j = 0; j < table[i].length; j++) { // 各行の列のループ (内側)
System.out.print(table[i][j] + " ");
}
System.out.println(); // 各行の後に改行
}
// 出力:
// 1 2 3
// 4 5
// 6 7 8 9
// 拡張 for ループを使った繰り返し
System.out.println("--- 拡張 for ループで ---");
for (int[] row : table) { // 各行 (int[] 型) を取り出す
for (int value : row) { // 各行の要素 (int 型) を取り出す
System.out.print(value + " ");
}
System.out.println();
}
配列の操作 (java.util.Arrays クラス)
java.util.Arrays
クラスは、配列のソート、検索、比較、コピーなどの便利な静的メソッドを提供します。
import java.util.Arrays; // インポートが必要
int[] nums = {5, 2, 8, 1, 9};
// 配列の内容を表示 (デバッグ用)
System.out.println("元の配列: " + Arrays.toString(nums)); // [5, 2, 8, 1, 9]
// 配列のソート (昇順)
Arrays.sort(nums);
System.out.println("ソート後の配列: " + Arrays.toString(nums)); // [1, 2, 5, 8, 9]
// 配列の検索 (バイナリサーチ - 事前にソートが必要)
int index = Arrays.binarySearch(nums, 5); // ソート済み配列から 5 を検索
System.out.println("5 のインデックス: " + index); // 2
int indexNotFound = Arrays.binarySearch(nums, 3); // 存在しない要素を検索
System.out.println("3 のインデックス: " + indexNotFound); // 負の値 (-挿入ポイント - 1) -> -3
// 配列の特定の値での埋め尽くし
int[] filledArray = new int[5];
Arrays.fill(filledArray, 100);
System.out.println("埋め尽くした配列: " + Arrays.toString(filledArray)); // [100, 100, 100, 100, 100]
// 配列のコピー (指定範囲)
int[] copiedArray = Arrays.copyOfRange(nums, 1, 4); // インデックス 1 から 4 の手前まで (1, 2, 3)
System.out.println("コピーした配列: " + Arrays.toString(copiedArray)); // [2, 5, 8]
// 配列のコピー (全体)
int[] fullCopy = Arrays.copyOf(nums, nums.length);
System.out.println("全体のコピー: " + Arrays.toString(fullCopy)); // [1, 2, 5, 8, 9]
// 配列の比較
int[] nums2 = {1, 2, 5, 8, 9};
boolean areEqual = Arrays.equals(nums, nums2);
System.out.println("配列は等しいか: " + areEqual); // true
コレクションフレームワーク 📚
複数のオブジェクト(要素)をまとめて格納し、効率的に管理するためのクラスやインターフェースの集まりです。配列とは異なり、サイズを動的に変更できるものが多くあります。
主なインターフェース:
List
: 順序付けられた要素の集まり。重複を許容します。 (例:ArrayList
,LinkedList
)Set
: 順序を持たない(または特定の順序を持つ)要素の集まり。重複を許容しません。 (例:HashSet
,TreeSet
)Map
: キーと値のペアを格納する集まり。キーは重複しません。 (例:HashMap
,TreeMap
)Queue
: 先入れ先出し (FIFO) のデータ構造。 (例:LinkedList
,PriorityQueue
)Deque
: 両端キュー。先頭と末尾の両方で要素の追加・削除が可能。 (例:ArrayDeque
,LinkedList
)
List インターフェース
順序を保持し、インデックスで要素にアクセスできます。
ArrayList
内部的に配列を使用。要素へのランダムアクセス(get
, set
)が高速。要素の追加・削除(特に中間)は比較的低速(要素のシフトが発生するため)。
import java.util.ArrayList;
import java.util.List;
List<String> names = new ArrayList<>(); // ダイヤモンド演算子 (型推論)
// 要素の追加 (末尾)
names.add("Alice");
names.add("Bob");
names.add("Charlie");
System.out.println("ArrayList: " + names); // [Alice, Bob, Charlie]
// 要素の追加 (指定位置)
names.add(1, "Brian"); // インデックス1に挿入、以降の要素がずれる
System.out.println("挿入後: " + names); // [Alice, Brian, Bob, Charlie]
// 要素の取得
String secondName = names.get(1); // Brian
System.out.println("2番目の名前: " + secondName);
// 要素の更新
names.set(0, "Alicia"); // インデックス0を更新
System.out.println("更新後: " + names); // [Alicia, Brian, Bob, Charlie]
// 要素の削除 (インデックス指定)
names.remove(2); // インデックス2 (Bob) を削除
System.out.println("削除後 (インデックス): " + names); // [Alicia, Brian, Charlie]
// 要素の削除 (要素指定 - 最初に見つかったもの)
names.remove("Charlie");
System.out.println("削除後 (要素): " + names); // [Alicia, Brian]
// サイズの取得
System.out.println("サイズ: " + names.size()); // 2
// 要素が含まれているか確認
boolean containsBob = names.contains("Bob"); // false
System.out.println("Bobを含むか: " + containsBob);
// リストが空か確認
System.out.println("空か: " + names.isEmpty()); // false
// 全要素の削除
names.clear();
System.out.println("クリア後: " + names); // []
System.out.println("クリア後 空か: " + names.isEmpty()); // true
// 繰り返し処理 (拡張 for)
names.add("X"); names.add("Y"); names.add("Z");
for(String name : names) {
System.out.print(name + " "); // X Y Z
}
System.out.println();
// 繰り返し処理 (forEach メソッド + ラムダ式 - Java 8以降)
names.forEach(name -> System.out.print(name + " ")); // X Y Z
System.out.println();
LinkedList
要素が前後の要素へのリンクを持つ双方向リスト構造。要素の追加・削除(特に先頭・末尾・中間)が高速。要素へのランダムアクセスは低速(先頭または末尾から順にたどるため)。List
, Queue
, Deque
インターフェースを実装。
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.Deque;
// List として利用
List<Integer> numbersList = new LinkedList<>();
numbersList.add(10);
numbersList.add(0, 5); // 先頭に追加
numbersList.add(20);
System.out.println("ListとしてのLinkedList: " + numbersList); // [5, 10, 20]
System.out.println("インデックス1の値: " + numbersList.get(1)); // 10
// Queue として利用 (FIFO)
Queue<String> taskQueue = new LinkedList<>();
taskQueue.offer("Task A"); // 要素を追加 (addも使えるが、容量制限がある場合にofferが推奨される)
taskQueue.offer("Task B");
taskQueue.offer("Task C");
System.out.println("Queue: " + taskQueue); // [Task A, Task B, Task C]
String nextTask = taskQueue.poll(); // 先頭要素を取得して削除 (peekは削除しない)
System.out.println("次のタスク: " + nextTask); // Task A
System.out.println("残りのQueue: " + taskQueue); // [Task B, Task C]
// Deque として利用 (両端キュー)
Deque<String> browserHistory = new LinkedList<>();
browserHistory.addLast("Page 1"); // 末尾に追加 (offerLast も可)
browserHistory.addLast("Page 2");
browserHistory.addFirst("Page 0"); // 先頭に追加 (offerFirst も可)
System.out.println("Deque: " + browserHistory); // [Page 0, Page 1, Page 2]
String currentPage = browserHistory.getLast(); // 末尾要素を取得 (削除しない, peekLast も可)
System.out.println("現在のページ: " + currentPage); // Page 2
String previousPage = browserHistory.removeLast(); // 末尾要素を取得して削除 (pollLast も可)
System.out.println("戻る: " + previousPage); // Page 2
System.out.println("Deque после戻る: " + browserHistory); // [Page 0, Page 1]
String firstPage = browserHistory.removeFirst(); // 先頭要素を取得して削除 (pollFirst も可)
System.out.println("最初のページ: " + firstPage); // Page 0
System.out.println("Deque после削除: " + browserHistory); // [Page 1]
Set インターフェース
重複しない要素の集まりです。
HashSet
ハッシュテーブルに基づいて実装されており、要素の追加、削除、検索が非常に高速(平均的に O(1))。要素の順序は保証されません。null
を一つだけ格納できます。
import java.util.HashSet;
import java.util.Set;
Set<String> uniqueNames = new HashSet<>();
uniqueNames.add("Alice");
uniqueNames.add("Bob");
uniqueNames.add("Alice"); // 重複する要素を追加しようとしても無視される
uniqueNames.add("Charlie");
uniqueNames.add(null); // null も追加可能
System.out.println("HashSet: " + uniqueNames); // 順序は不定 [null, Bob, Alice, Charlie] のような出力 (実行ごとに異なる可能性)
System.out.println("サイズ: " + uniqueNames.size()); // 4 (重複はカウントされない)
// 要素の存在確認
boolean hasBob = uniqueNames.contains("Bob"); // true
System.out.println("Bobを含むか: " + hasBob);
// 要素の削除
boolean removed = uniqueNames.remove("Alice"); // true (削除成功)
System.out.println("削除後のSet: " + uniqueNames); // [null, Bob, Charlie] (順序不定)
// 繰り返し処理 (拡張 for) - 順序は保証されない
for (String name : uniqueNames) {
System.out.print(name + " "); // null Bob Charlie (順不同)
}
System.out.println();
TreeSet
赤黒木(Red-Black Tree)に基づいて実装されており、要素が常にソートされた状態(自然順序または指定された Comparator による順序)で保持されます。要素の追加、削除、検索は比較的効率的(O(log n))。null
を格納できません(比較できないため)。
import java.util.TreeSet;
import java.util.Set;
import java.util.Comparator;
// 自然順序 (String は辞書順)
Set<String> sortedNames = new TreeSet<>();
sortedNames.add("Charlie");
sortedNames.add("Alice");
sortedNames.add("Bob");
sortedNames.add("Alice"); // 重複は無視
System.out.println("TreeSet (自然順序): " + sortedNames); // [Alice, Bob, Charlie] (ソートされている)
// sortedNames.add(null); // NullPointerException が発生
// Comparator を使った逆順ソート
Set<Integer> reverseSortedNumbers = new TreeSet<>(Comparator.reverseOrder());
reverseSortedNumbers.add(10);
reverseSortedNumbers.add(5);
reverseSortedNumbers.add(20);
reverseSortedNumbers.add(5);
System.out.println("TreeSet (逆順): " + reverseSortedNumbers); // [20, 10, 5]
// 繰り返し処理 (拡張 for) - ソート順で処理される
for (String name : sortedNames) {
System.out.print(name + " "); // Alice Bob Charlie
}
System.out.println();
Map インターフェース
キーと値のペアを格納します。キーは一意である必要があります。
HashMap
ハッシュテーブルに基づいて実装されており、キーによる値の追加、削除、検索が非常に高速(平均的に O(1))。キーと値の順序は保証されません。キー、値ともに null
を許容します(キーとしての null
は一つだけ)。
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
Map<String, Integer> scores = new HashMap<>();
// 要素の追加 (put) - キーが存在しない場合は追加、存在する場合は値を上書き
scores.put("Alice", 90);
scores.put("Bob", 85);
scores.put("Charlie", 95);
scores.put("Alice", 92); // "Alice" の値が 92 に上書きされる
scores.put(null, 70); // null キーも可能
scores.put("David", null); // null 値も可能
System.out.println("HashMap: " + scores); // 順序不定 {null=70, Bob=85, David=null, Alice=92, Charlie=95} (実行ごとに異なる可能性)
// 値の取得 (get) - キーが存在しない場合は null を返す
Integer bobScore = scores.get("Bob"); // 85
System.out.println("Bob のスコア: " + bobScore);
Integer frankScore = scores.get("Frank"); // null
System.out.println("Frank のスコア: " + frankScore);
// キーの存在確認
boolean hasCharlie = scores.containsKey("Charlie"); // true
System.out.println("Charlie は存在するか: " + hasCharlie);
// 値の存在確認
boolean hasScore95 = scores.containsValue(95); // true
System.out.println("95点は存在するか: " + hasScore95);
// 要素の削除 (remove)
scores.remove("Bob");
System.out.println("削除後: " + scores); // {null=70, David=null, Alice=92, Charlie=95} (順序不定)
// サイズの取得
System.out.println("サイズ: " + scores.size()); // 4
// Map の繰り返し処理
// 1. キーの Set を取得して繰り返す
System.out.println("--- キーによる繰り返し ---");
Set<String> keys = scores.keySet(); // キーの Set を取得 {null, David, Alice, Charlie}
for (String key : keys) {
Integer value = scores.get(key);
System.out.println("キー: " + key + ", 値: " + value);
}
// 2. エントリ (キーと値のペア) の Set を取得して繰り返す (効率的)
System.out.println("--- エントリによる繰り返し ---");
Set<Map.Entry<String, Integer>> entries = scores.entrySet();
for (Map.Entry<String, Integer> entry : entries) {
String key = entry.getKey();
Integer value = entry.getValue();
System.out.println("キー: " + key + ", 値: " + value);
}
// 3. forEach メソッド (Java 8以降)
System.out.println("--- forEach メソッド ---");
scores.forEach((key, value) -> {
System.out.println("キー: " + key + ", 値: " + value);
});
TreeMap
赤黒木に基づいて実装されており、キーが常にソートされた状態(自然順序または指定された Comparator による順序)で保持されます。キーによる値の追加、削除、検索は比較的効率的(O(log n))。キーは null
を許容しませんが、値は null
を許容します。
import java.util.TreeMap;
import java.util.Map;
import java.util.Comparator;
// 自然順序 (String は辞書順)
Map<String, Integer> sortedScores = new TreeMap<>();
sortedScores.put("Charlie", 95);
sortedScores.put("Alice", 90);
sortedScores.put("Bob", 85);
// sortedScores.put(null, 70); // NullPointerException
System.out.println("TreeMap (自然順序): " + sortedScores); // {Alice=90, Bob=85, Charlie=95} (キーでソート)
// Comparator を使った逆順ソート
Map<Integer, String> reverseSortedMap = new TreeMap<>(Comparator.reverseOrder());
reverseSortedMap.put(10, "Apple");
reverseSortedMap.put(5, "Banana");
reverseSortedMap.put(20, "Cherry");
System.out.println("TreeMap (逆順): " + reverseSortedMap); // {20=Cherry, 10=Apple, 5=Banana} (キーで逆順ソート)
// 繰り返し処理 (keySet, entrySet, forEach) はソート順で行われる
System.out.println("--- TreeMap の繰り返し (キー順) ---");
reverseSortedMap.forEach((key, value) -> {
System.out.println("キー: " + key + ", 値: " + value);
});
例外処理 ⚠️
プログラム実行中に発生する可能性のあるエラー(例外)に対処するための仕組みです。
try-catch-finally ブロック
try
: 例外が発生する可能性のあるコードを囲みます。catch
:try
ブロック内で特定の例外が発生した場合に実行されるコードを記述します。複数のcatch
ブロックを記述できます。finally
: 例外の発生有無にかかわらず、必ず最後に実行されるコードを記述します。リソースの解放処理などによく使われます。(省略可能)
import java.io.FileReader;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.FileNotFoundException;
public class ExceptionHandlingDemo {
public static void main(String[] args) {
BufferedReader reader = null;
try {
// --- 例外が発生する可能性のある処理 ---
int[] numbers = {1, 2, 3};
System.out.println("要素アクセス: " + numbers[5]); // ArrayIndexOutOfBoundsException が発生
reader = new BufferedReader(new FileReader("nonexistentfile.txt")); // FileNotFoundException が発生する可能性
String line = reader.readLine();
System.out.println("ファイル内容: " + line);
System.out.println("tryブロックの終わり"); // 上で例外が発生すると、ここは実行されない
} catch (ArrayIndexOutOfBoundsException e) {
// --- 配列インデックス範囲外例外が発生した場合の処理 ---
System.err.println("エラー: 配列の範囲外にアクセスしました。");
System.err.println("例外メッセージ: " + e.getMessage()); // エラー詳細メッセージ
// e.printStackTrace(); // スタックトレースを出力 (デバッグ用)
} catch (FileNotFoundException e) {
// --- ファイルが見つからない例外が発生した場合の処理 ---
System.err.println("エラー: 指定されたファイルが見つかりません。");
System.err.println("例外オブジェクト: " + e);
} catch (IOException e) {
// --- その他のI/O関連例外が発生した場合の処理 ---
// FileNotFoundException は IOException のサブクラスなので、
// FileNotFoundException の catch ブロックの後ろに書く必要がある。
System.err.println("エラー: ファイルの読み書き中にエラーが発生しました。");
e.printStackTrace();
} catch (Exception e) {
// --- その他の予期せぬ例外が発生した場合の処理 ---
// より具体的な catch ブロックの後ろに書く。
System.err.println("予期せぬエラーが発生しました。");
e.printStackTrace();
} finally {
// --- 例外発生の有無に関わらず、必ず実行される処理 ---
System.out.println("finallyブロックが実行されました。");
if (reader != null) {
try {
reader.close(); // リソースの解放
System.out.println("ファイルをクローズしました。");
} catch (IOException e) {
System.err.println("ファイルクローズ中にエラーが発生しました。");
e.printStackTrace();
}
}
}
System.out.println("プログラムの終わり"); // catch で処理されれば、ここまで実行される
}
}
💡 Java 7以降では、リソースを自動的に解放する try-with-resources 構文が利用でき、finally
での close()
処理を簡潔かつ安全に記述できます。
// try-with-resources の例
try (BufferedReader reader = new BufferedReader(new FileReader("myfile.txt"))) { // () 内で AutoCloseable を実装したリソースを宣言
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
// try ブロックを抜ける際に自動的に reader.close() が呼ばれる
} catch (FileNotFoundException e) {
System.err.println("ファイルが見つかりません: " + e.getMessage());
} catch (IOException e) {
System.err.println("ファイル読み込みエラー: " + e.getMessage());
}
// finally ブロックでの close 処理が不要になる
例外のスロー (throw)
メソッド内で意図的に例外を発生させる場合に使用します。
public static double divide(int a, int b) {
if (b == 0) {
// 0除算はできないので、例外を発生させる
throw new ArithmeticException("ゼロで除算しようとしました。");
}
return (double) a / b;
}
public static void checkAge(int age) {
if (age < 0) {
// 不正な値の場合、独自の例外や既存の例外をスローする
throw new IllegalArgumentException("年齢に負の値は設定できません: " + age);
}
System.out.println("有効な年齢です: " + age);
}
// 呼び出し側での対処例
try {
double result = divide(10, 0);
System.out.println("結果: " + result);
checkAge(-5);
} catch (ArithmeticException | IllegalArgumentException e) { // Java 7以降のマルチキャッチ
System.err.println("エラー発生: " + e.getMessage());
}
例外の伝搬 (throws)
メソッド内で発生する可能性のあるチェック例外(Exception
のサブクラスで RuntimeException
のサブクラスではないもの。例: IOException
, SQLException
)を、呼び出し元に処理させることを宣言します。
import java.io.IOException;
import java.sql.SQLException;
// IOException が発生する可能性があることを宣言
public static String readFile(String filePath) throws IOException {
// ファイル読み込み処理...
// もし IOException が発生したら、呼び出し元にスローされる
if (/* エラー条件 */) {
throw new IOException("ファイル読み込みエラー");
}
return "ファイルの内容";
}
// 複数の例外を宣言
public static void processData() throws IOException, SQLException {
readFile("data.txt"); // IOException が発生する可能性
// データベース処理... SQLException が発生する可能性
}
// throws 宣言されたメソッドの呼び出し側
public static void main(String[] args) {
try {
String content = readFile("config.txt");
System.out.println(content);
processData();
} catch (IOException e) {
System.err.println("IOエラー: " + e.getMessage());
} catch (SQLException e) {
System.err.println("SQLエラー: " + e.getMessage());
}
// または、main メソッド自身が throws で宣言することも可能 (推奨されない場合が多い)
// public static void main(String[] args) throws IOException, SQLException { ... }
}
💡 RuntimeException
またはそのサブクラス(例: NullPointerException
, ArrayIndexOutOfBoundsException
, ArithmeticException
など)は非チェック例外と呼ばれ、throws
宣言は必須ではありません(が、宣言しても良い)。
カスタム例外
独自の例外クラスを定義して、特定のアプリケーションエラーを示すことができます。
// 独自のチェック例外 (Exception を継承)
class InsufficientFundsException extends Exception {
private double shortfall;
public InsufficientFundsException(String message, double shortfall) {
super(message); // スーパークラス (Exception) のコンストラクタを呼び出す
this.shortfall = shortfall;
}
public double getShortfall() {
return shortfall;
}
}
// 独自の非チェック例外 (RuntimeException を継承)
class InvalidTransactionException extends RuntimeException {
public InvalidTransactionException(String message) {
super(message);
}
}
// 使用例
class BankAccount {
private double balance;
public void withdraw(double amount) throws InsufficientFundsException {
if (amount <= 0) {
throw new InvalidTransactionException("引き出し額は正の数である必要があります。"); // 非チェック例外
}
if (balance < amount) {
double shortfall = amount - balance;
throw new InsufficientFundsException("残高不足です。", shortfall); // チェック例外
}
balance -= amount;
System.out.println(amount + "円引き出しました。残高: " + balance);
}
// ... deposit メソッドなど ...
}
// 呼び出し側
BankAccount account = new BankAccount();
// account.deposit(1000);
try {
account.withdraw(500);
account.withdraw(700); // InsufficientFundsException がスローされる
} catch (InsufficientFundsException e) {
System.err.println("エラー: " + e.getMessage() + " 不足額: " + e.getShortfall() + "円");
} catch (InvalidTransactionException e) {
System.err.println("不正な取引: " + e.getMessage());
}
文字列操作 (String, StringBuilder, etc.) 📝
Javaでの文字列の基本的な扱い方と、効率的な操作方法です。
String クラス (不変オブジェクト)
Java の文字列は String
クラスのオブジェクトとして扱われ、不変 (Immutable) です。つまり、一度作成された String
オブジェクトの内容は変更できません。文字列を操作するメソッドは、新しい String
オブジェクトを生成して返します。
String str1 = "Hello";
String str2 = " World";
String str3 = str1 + str2; // "+" 演算子による連結 (新しい String オブジェクトが生成される)
System.out.println(str3); // "Hello World"
String str4 = str1.concat(str2); // concat メソッドによる連結
System.out.println(str4); // "Hello World"
System.out.println("元の str1: " + str1); // "Hello" (str1 自体は変更されていない)
// よく使う String メソッド
String text = " Java Programming Language ";
// 長さ
System.out.println("長さ: " + text.length()); // 27 (前後の空白も含む)
// 特定位置の文字
System.out.println("1番目の文字: " + text.charAt(1)); // ' ' (空白)
// 部分文字列 (substring)
System.out.println("部分文字列(6から): " + text.substring(6)); // "Programming Language "
System.out.println("部分文字列(6から16の手前): " + text.substring(6, 17)); // "Programming"
// 文字列の比較 (大文字小文字を区別)
String s1 = "java";
String s2 = "Java";
System.out.println("equals('Java'): " + s1.equals(s2)); // false
System.out.println("equalsIgnoreCase('Java'): " + s1.equalsIgnoreCase(s2)); // true
// 文字列の検索 (indexOf, lastIndexOf)
System.out.println("最初の 'a' の位置: " + text.indexOf('a')); // 2
System.out.println("最初の 'Lang' の位置: " + text.indexOf("Lang")); // 18
System.out.println("最後の 'a' の位置: " + text.lastIndexOf('a')); // 22
System.out.println("存在しない文字 'z' の位置: " + text.indexOf('z')); // -1
// 文字列の置換 (replace)
System.out.println("置換 ('a'->'A'): " + text.replace('a', 'A')); // " JAvA ProgrAmming LAnguAge "
System.out.println("置換 ('Lang'->'Code'): " + text.replace("Lang", "Code")); // " Java Programming Codeuage "
// 前後の空白除去 (trim)
System.out.println("トリム前: '" + text + "'");
System.out.println("トリム後: '" + text.trim() + "'"); // "'Java Programming Language'"
// 大文字・小文字変換
System.out.println("大文字: " + text.toUpperCase()); // " JAVA PROGRAMMING LANGUAGE "
System.out.println("小文字: " + text.toLowerCase()); // " java programming language "
// 特定の文字列で始まるか/終わるか (startsWith, endsWith)
System.out.println("' Java'で始まるか: " + text.startsWith(" Java")); // true
System.out.println("'age 'で終わるか: " + text.endsWith("age ")); // true
// 文字列の分割 (split)
String csv = "apple,banana,cherry";
String[] fruits = csv.split(","); // "," で分割し、文字列配列にする
System.out.println("分割結果: " + java.util.Arrays.toString(fruits)); // [apple, banana, cherry]
// 文字列が空か判定 (isEmpty) - 長さが0か
String emptyStr = "";
String blankStr = " ";
System.out.println("emptyStr は空か: " + emptyStr.isEmpty()); // true
System.out.println("blankStr は空か: " + blankStr.isEmpty()); // false (空白文字があるので空ではない)
// 文字列がブランクか判定 (isBlank - Java 11以降) - 空白文字のみで構成されているか
System.out.println("emptyStr はブランクか: " + emptyStr.isBlank()); // true
System.out.println("blankStr はブランクか: " + blankStr.isBlank()); // true
System.out.println("text はブランクか: " + text.isBlank()); // false
// 文字列フォーマット (String.format) - printf と同様の書式指定
String formatted = String.format("名前: %s, 年齢: %d, 身長: %.2f m", "Alice", 30, 1.658);
System.out.println(formatted); // 名前: Alice, 年齢: 30, 身長: 1.66 m
⚠️ ループ内で String
の連結(+
や concat
)を繰り返すと、その都度新しい String
オブジェクトが生成されるため、パフォーマンスが悪化する可能性があります。このような場合は StringBuilder
または StringBuffer
を使用します。
StringBuilder クラス (可変オブジェクト, 非同期)
文字列を効率的に変更(追加、挿入、削除など)するためのクラスです。内部的に文字バッファを持ち、操作はバッファに対して行われます。String
と異なり 可変 (Mutable) です。スレッドセーフではないため、単一スレッド環境での使用に適しています。高速です。
// StringBuilder の生成
StringBuilder sb = new StringBuilder(); // 空の StringBuilder
StringBuilder sb2 = new StringBuilder("Initial"); // 初期文字列を指定
StringBuilder sb3 = new StringBuilder(100); // 初期容量を指定
// 文字列の追加 (append) - 様々な型を追加可能
sb.append("Hello");
sb.append(" ");
sb.append("World");
sb.append("!");
sb.append(2025);
System.out.println("Append 結果: " + sb); // "Hello World!2025" (toString() が暗黙的に呼ばれる)
// 文字列の挿入 (insert)
sb.insert(6, "Java "); // インデックス6の位置に挿入
System.out.println("Insert 結果: " + sb); // "Hello Java World!2025"
// 文字列の削除 (delete)
sb.delete(6, 11); // インデックス6から11の手前まで ("Java ") を削除
System.out.println("Delete 結果: " + sb); // "Hello World!2025"
// 文字列の置換 (replace)
sb.replace(12, 16, "Year"); // インデックス12から16の手前まで ("2025") を "Year" に置換
System.out.println("Replace 結果: " + sb); // "Hello World!Year"
// 文字列の反転 (reverse)
sb.reverse();
System.out.println("Reverse 結果: " + sb); // "raeY!dlroW olleH"
sb.reverse(); // 元に戻す
// 長さの取得 (length)
System.out.println("現在の長さ: " + sb.length()); // 16
// 容量の取得 (capacity) - 内部バッファのサイズ
System.out.println("現在の容量: " + sb.capacity());
// 最終的に String オブジェクトを取得
String finalString = sb.toString();
System.out.println("最終的な String: " + finalString);
// メソッドチェーンによる効率的な記述
StringBuilder message = new StringBuilder()
.append("User: ")
.append("Alice")
.append(", Age: ")
.append(30);
System.out.println(message.toString()); // User: Alice, Age: 30
StringBuffer クラス (可変オブジェクト, 同期)
StringBuilder
とほぼ同じ機能を持つ可変な文字列クラスですが、メソッドが 同期 (Synchronized) されています。そのため、複数のスレッドから同時にアクセスされる可能性がある環境(マルチスレッド環境)で安全に使用できます。ただし、同期のためのオーバーヘッドがあるため、StringBuilder
よりも若干低速です。
// StringBuffer の基本的な使い方は StringBuilder と同じ
StringBuffer sbuf = new StringBuffer();
sbuf.append("Thread-safe ");
sbuf.append("string modification.");
System.out.println(sbuf.toString()); // Thread-safe string modification.
// マルチスレッド環境で共有する場合などに StringBuffer を選択する
💡 単一スレッドで文字列操作を行う場合は StringBuilder
を、マルチスレッド環境で共有の文字列バッファを操作する必要がある場合は StringBuffer
を使用するのが一般的です。
StringJoiner クラス (Java 8以降)
複数の文字列を指定された区切り文字で連結するためのユーティリティクラスです。接頭辞(Prefix)や接尾辞(Suffix)も指定できます。
import java.util.StringJoiner;
// 区切り文字を指定
StringJoiner sj1 = new StringJoiner(", "); // 区切り文字は ", "
sj1.add("Apple");
sj1.add("Banana");
sj1.add("Cherry");
System.out.println(sj1.toString()); // Apple, Banana, Cherry
// 区切り文字、接頭辞、接尾辞を指定
StringJoiner sj2 = new StringJoiner(" | ", "[", "]"); // 区切り文字 "|", 接頭辞 "[", 接尾辞 "]"
sj2.add("Red");
sj2.add("Green");
sj2.add("Blue");
System.out.println(sj2.toString()); // [Red | Green | Blue]
// add の代わりに merge も使用可能 (他の StringJoiner を結合)
StringJoiner sj3 = new StringJoiner("-");
sj3.add("A").add("B");
sj2.merge(sj3); // sj2 の現在の状態に sj3 の内容が結合される(sj2の区切り文字が使われる)
System.out.println("Merge後: " + sj2.toString()); // [Red | Green | Blue | A-B] (結合される側の区切り文字は無視され、結合する側の区切り文字が使われる)
// 空の場合の挙動
StringJoiner sjEmpty = new StringJoiner(", ", "(", ")");
System.out.println("空の場合: " + sjEmpty.toString()); // () - 接頭辞と接尾辞のみ
StringJoiner sjEmptyNoPrefix = new StringJoiner(",");
System.out.println("空(接頭辞なし): " + sjEmptyNoPrefix.toString()); // "" - 空文字列
// setEmptyValue で空の場合のデフォルト値を設定可能
sjEmpty.setEmptyValue("EMPTY");
System.out.println("空(デフォルト値あり): " + sjEmpty.toString()); // EMPTY
💡 ストリーム API の Collectors.joining()
は、内部的に StringJoiner
を利用しています。
コメント