データ型・ラッパークラス・String・StringBuilder
プリミティブ型8種・型プロモーション・キャスト・ラッパークラスのキャッシュ・String不変性とプール・StringBuilderを完全解説
Chapter 01 ─ データ型・ラッパークラス・String・StringBuilder
この章を読めば: プリミティブ型8種・型変換・ラッパークラスのキャッシュ罠・String の不変性・StringBuilder の使い方が完全に理解できる。Silver 試験の序盤でここの理解が甘いと後の章が全部ぐらつく。基礎だからこそ、ここは完璧に仕上げる。
1-1. Java の型の2大分類
Java のすべての値は「プリミティブ型」か「参照型」のどちらかに属する。
| 分類 | 何を格納するか | 例 |
|---|---|---|
| プリミティブ型 | 値そのものをスタックメモリに格納 | int, double, boolean など8種 |
| 参照型 | オブジェクトの**アドレス(参照)**を格納 | String, 配列, 自作クラス |
プリミティブ型は8種しかない。それ以外はすべて参照型だ。これを知っておくと、String や配列が「参照型だから null を持てる」「== が参照比較になる」などの挙動が自然に理解できるようになる。
イメージで理解する
プリミティブ型は「財布に現金を入れてる」状態。int x = 42 と書いたら、変数 x の箱の中に直接 42 という数字が入っている。
参照型は「財布に銀行のカード(口座番号)を入れてる」状態。String s = "hello" と書いたとき、変数 s の箱に入っているのは "hello" という文字列そのものではなく、「ヒープメモリ上のここに置いてあるよ」というアドレスだ。
この違いが後で出てくる == の挙動の違いを生む。
1-2. プリミティブ型8種 ─ 完全整理
プリミティブ型は以下の8種のみ。すべて小文字で書く(int, double など)。
| 型 | ビット数 | 範囲 / 特徴 | フィールドのデフォルト値 |
|---|---|---|---|
byte | 8 bit | -128 〜 127 | 0 |
short | 16 bit | -32,768 〜 32,767 | 0 |
int | 32 bit | 約 ±21億(-2,147,483,648 〜 2,147,483,647) | 0 |
long | 64 bit | 約 ±922京 | 0L |
float | 32 bit | 単精度浮動小数点(約7桁) | 0.0f |
double | 64 bit | 倍精度浮動小数点(約15桁)。小数リテラルのデフォルト | 0.0 |
char | 16 bit | Unicode 1文字(0〜65535)。シングルクォート | '\u0000' |
boolean | — | true / false のみ。他の型と互換なし | false |
リテラルの書き方と注意点
リテラルとはソースコードに直接書いた値のこと(42 や "hello" など)。
// int: 特別な記号不要(デフォルト整数リテラル)
int a = 42;
int b = 0xFF; // 16進数リテラル。0xFF = 255
int c = 0b1010; // 2進数リテラル(Java 7+)。0b1010 = 10
int d = 1_000_000; // アンダースコアで桁区切り可(Java 7+)。読みやすくなるだけ
// long: 末尾に L(または l)が必須
long e = 100L; // OK
long f = 100; // OK(int リテラルを long に自動拡大)
long g = 10000000000L; // 21億を超えるなら L が必須(なければコンパイルエラー)
// float: 末尾に f(または F)が必須
float h = 3.14f; // OK
// float i = 3.14; // ❌ コンパイルエラー!3.14 は double 型なので float に代入不可
// double: 何も付けなくてよい(小数リテラルのデフォルト)
double j = 3.14; // OK
double k = 3.14d; // d を付けても OK(省略一般的)
// char: シングルクォートで囲む
char l = 'A';
char m = 65; // 整数でも可('A' の Unicode 番号。0〜65535)
char n = '\u0041'; // Unicode エスケープ('A')
// boolean: true か false のみ
boolean o = true;
// boolean p = 1; // ❌ コンパイルエラー!int と boolean は互換なし
試験で問われる頻出パターン
float f = 3.14;→ コンパイルエラー(3.14 は double で、double → float は縮小変換のためキャストが必要)
long l = 10000000000;→ コンパイルエラー(L なしの整数リテラルは int 型として解釈されるが、この値は int の最大値 21億を超えている)
デフォルト値の適用ルール ─「フィールドのみ」
デフォルト値が自動的に適用されるのはクラスのフィールド(インスタンス変数・クラス変数)だけ。メソッド内のローカル変数には適用されない。
class Demo {
static int classVar; // static フィールド → デフォルト 0
int instanceVar; // インスタンスフィールド → デフォルト 0
boolean flag; // デフォルト false
String text; // 参照型のデフォルト null
void method() {
int localVar; // ローカル変数:初期化なし
System.out.println(localVar); // ❌ コンパイルエラー!
// 「変数 localVar が初期化されていない可能性がある」
}
}
なぜローカル変数はデフォルト値がないのか?
ローカル変数は使う直前に必ず値をセットするのがプログラマの責任、というJavaの設計思想による。「うっかり初期化し忘れてゴミ値が入る」バグをコンパイルエラーで防ぐ仕組みだ。
試験でよく出る引っかけ
フィールドは初期化せずに読んでもOK(デフォルト値が使われる)。
ローカル変数を初期化せずに読もうとするとコンパイルエラー。
この違いを問う問題が頻出。
1-3. 型変換とキャスト
拡大変換(Widening Conversion)─ 自動でできる
「小さい型 → 大きい型」の変換はキャスト(型変換の明示)が不要。コンパイラが自動でやってくれる。「大は小を兼ねる」と覚えよう。
byte → short → int → long → float → double
↑
char ─┘ (char は int に自動変換できる)
int i = 100;
long l = i; // OK: int → long(拡大変換。自動)
double d = i; // OK: int → double(拡大変換。自動)
float f = i; // OK: int → float(拡大変換。自動。精度は落ちる場合あり)
char c = 'A';
int n = c; // OK: char → int('A' の Unicode 番号 65 が入る)
豆知識:
int→floatは拡大変換でも「精度が失われる」場合がある。intは 32 ビット整数、floatも 32 ビットだが整数部と小数部に分かれているため、大きい整数は正確に表現できない。試験でこの罠が問われることはまれだが知っておくと安心。
縮小変換(Narrowing Conversion)─ キャスト必須
「大きい型 → 小さい型」への変換は明示的なキャスト (型名) が必要。コンパイラはデフォルトで拒否するので、「私は分かった上でやります」という宣言をしなければならない。
double d = 3.99;
int i = d; // ❌ コンパイルエラー!double は int より大きいので自動変換できない
int i = (int) d; // ✅ OK。(int) がキャストの構文
System.out.println(i); // 3(小数点以下は切り捨て!四捨五入ではない)
long l = 300L;
byte b = (byte) l; // ✅ OK(コンパイルは通る)
System.out.println(b); // 44(オーバーフロー。後述)
オーバーフローしてもエラーにならないことが重要。「入りきらない部分は捨てる」動作で黙って続行する。これはバグの温床になるので試験でも問われる。
// 300 を8ビットで表すとどうなるか
// 300 = 256 + 44 = 0b100101100
// 下位8ビットだけ取り出すと: 0b00101100 = 44
// これを符号ありで解釈すると: 44
// もっと大きい例
byte b = (byte) 128; // 128 = 0b10000000 → 符号あり8ビットで -128
byte b2 = (byte) 256; // 256 = 0b100000000 → 下位8ビット = 0
試験で超頻出の引っかけ2つ
(int) 3.99→3。切り捨て(floor でも round でもない、truncate)- 縮小変換でオーバーフローしても実行時エラーが起きない。値が壊れるだけ。
キャストの優先順位
キャストの (型名) がどの範囲に効くかで結果が変わる。
double a = 9.5;
double b = 4.0;
System.out.println((int)(a / b)); // (9.5 / 4.0) = 2.375 → (int) 2.375 = 2
System.out.println((int)a / b); // (int)9.5 = 9 → 9 / 4.0 = 2.25(double)
System.out.println((int)(a / (int)b)); // (int)4.0 = 4 → 9.5 / 4 = 2.375 → 2
キャストは直後の1トークンにしか効かない。(int)a / b は a だけをキャストするが、(int)(a / b) は式全体の結果をキャストする。
1-4. 型プロモーション ─ 試験最頻出の落とし穴
型プロモーション(型昇格): 式の中で byte・short・char を演算すると、計算前に自動的に int に昇格するという仕様。
byte a = 10;
byte b = 20;
byte c = a + b; // ❌ コンパイルエラー!
「byte 同士を足したんだから byte じゃないの?」と思うかもしれないが、Java の仕様では byte + byte → int になる。なぜなら、CPUのほとんどは32ビット幅の演算レジスタを持つため、小さい型はレジスタに入れる際に自動で32ビットに広げられる。その結果が int になり、それを byte に入れようとするとコンパイルエラーになる。
byte a = 10;
byte b = 20;
// 解決策1: キャストする
byte c = (byte)(a + b); // ✅
// 解決策2: int で受ける
int c2 = a + b; // ✅
複合代入演算子(+= など)は例外。暗黙のキャストが含まれている。
byte x = 10;
x = x + 5; // ❌ エラー。x + 5 は int 型
x += 5; // ✅ OK。+= にはキャストが含まれている(x = (byte)(x + 5) と同等)
x++; // ✅ OK。++ も同様
この「x = x + 1 はエラーで x += 1 は OK」という区別は試験に頻繁に出る。
式全体の型(プロモーションのルール)
| 演算の組み合わせ | 結果の型 |
|---|---|
byte, short, char 同士または int との演算 | int に昇格 |
int + long | long |
int か long + float | float |
何か + double | double |
ルールは「演算に出てくる最も大きい型に揃える。ただし byte/short/char は最低でも int まで昇格」。
byte by = 10;
short sh = 20;
int i = 30;
long l = 40L;
float f = 1.5f;
double d = 2.5;
// 型を確認する式例
var r1 = by + sh; // int(byte + short → int)
var r2 = by + i; // int(byte + int → int)
var r3 = i + l; // long(int + long → long)
var r4 = l + f; // float(long + float → float)
var r5 = f + d; // double(float + double → double)
オンライン受験者の声(試験傾向)
「byte+shortの型を聞く問題が複数出た」
「short s = 1; s = s + 1;がコンパイルエラーになる問題が出た」
「+=はOKで= ... + 1がNGというのは絶対に覚えた方が良い」
文字列と数値の変換
数値を文字列に変換する方法は複数ある。
// 数値 → 文字列(3通り)
String s1 = String.valueOf(42); // "42"(推奨。null 安全)
String s2 = Integer.toString(42); // "42"
String s3 = "" + 42; // "42"(空文字との連結。分かりにくいので非推奨)
// 文字列 → 数値
int n1 = Integer.parseInt("42"); // 42
long n2 = Long.parseLong("100"); // 100
double n3 = Double.parseDouble("3.14"); // 3.14
float n4 = Float.parseFloat("1.5"); // 1.5f
// 失敗した場合
int bad = Integer.parseInt("abc"); // 実行時に NumberFormatException!
int bad2 = Integer.parseInt("12.34"); // 実行時に NumberFormatException!(小数点はint変換不可)
int ok = Integer.parseInt("-42"); // -42(マイナスはOK)
✏️ 練習問題
次のコードでコンパイルエラーになる行を答えよ。
byte a = 10; // 行1
short b = a; // 行2
int c = b; // 行3
long d = c; // 行4
float e = d; // 行5
double f = e; // 行6
float g = 3.14; // 行7
byte h = a + 1; // 行8
byte i = 127 + 1; // 行9
答え
- 行2:
byte → shortは拡大変換 → OK - 行5:
long → floatは拡大変換(サイズ的には float の方が小さいが変換上は「拡大」扱い)→ OK - 行7:
3.14はdouble型リテラル。double → floatは縮小変換 → エラー(3.14fか(float)3.14が必要) - 行8:
a + 1はbyte + int→intに昇格。int → byteの縮小変換は自動では行われない → エラー - 行9:
127 + 1はコンパイル時定数128。これはbyteの最大値 127 を超えているためコンパイラが検出 → エラー
エラーになるのは行7・行8・行9。
1-5. ラッパークラス ─ プリミティブのオブジェクト版
プリミティブ型は「オブジェクトではない」ため、コレクション(List<int> のような書き方)に直接入れられない。null を持てない。また、メソッドを呼べない(42.toString() は書けない)。
そこで各プリミティブ型に対応するラッパークラス(オブジェクト版の型)が用意されている。
| プリミティブ型 | ラッパークラス | 注意 |
|---|---|---|
byte | Byte | |
short | Short | |
int | Integer | ※ Int ではない! |
long | Long | |
float | Float | |
double | Double | |
char | Character | ※ Char ではない! |
boolean | Boolean |
int → Integer、char → Character はミスが多い。大文字で始まる完全な英単語になっているものが2つある(Integer と Character)。
オートボクシングとアンボクシング
Java 5 以降、プリミティブ型とラッパークラスの相互変換はコンパイラが自動的に行う(オートボクシング / アンボクシング)。
// オートボクシング: プリミティブ → ラッパー(コンパイラが自動挿入)
Integer obj = 42; // 内部では Integer.valueOf(42) が呼ばれる
Boolean b = true; // 内部では Boolean.valueOf(true)
Double d = 3.14; // 内部では Double.valueOf(3.14)
// アンボクシング: ラッパー → プリミティブ(コンパイラが自動挿入)
int n = obj; // 内部では obj.intValue() が呼ばれる
// コレクションでの利用(コレクションはオブジェクトしか格納できない)
List<Integer> list = new ArrayList<>();
list.add(10); // int → Integer(オートボクシング)
int val = list.get(0); // Integer → int(アンボクシング)
null のアンボクシングは NullPointerException
ラッパーは参照型なので null を持てる。しかし null をアンボクシング(プリミティブに変換)しようとすると、内部で null.intValue() を呼ぶことになり NullPointerException が発生する。
Integer x = null;
int n = x; // ❌ 実行時 NullPointerException
// 内部では x.intValue() が呼ばれる。null のメソッドは呼べない
// よくある罠パターン
Integer score = getScore(); // null を返す可能性があるメソッド
int s = score + 10; // score が null なら NPE!
試験で頻出
「次のコードはどのような例外が発生するか?」という問題で NPE が正解になるパターン。
Integer num = null; if (num == 0)→ この比較でも NPE が起きる(num.intValue()が呼ばれるため)。
ラッパークラスの == 比較 ─ キャッシュの罠(超頻出)
Java の Integer.valueOf() は -128〜127 の範囲の値をキャッシュ(JVM 起動時に作り置き)している。この範囲内の値は毎回同じオブジェクトを返す。範囲外は毎回新しいオブジェクトを生成する。
Integer a = 100; // Integer.valueOf(100) → キャッシュされたオブジェクト
Integer b = 100; // Integer.valueOf(100) → 同じキャッシュを返す
System.out.println(a == b); // true(同じオブジェクト)
System.out.println(a.equals(b)); // true
Integer c = 200; // Integer.valueOf(200) → キャッシュ範囲外。新オブジェクト生成
Integer d = 200; // Integer.valueOf(200) → 別の新オブジェクト生成
System.out.println(c == d); // false(別々のオブジェクト)
System.out.println(c.equals(d)); // true(値は同じ)
なぜ -128〜127 なのか?
この範囲は日常的によく使われる値(ループのカウンタなど)なので、キャッシュすることでメモリと速度を節約するという Java の最適化。ただしこれを == 比較に使うのは危険なので、ラッパーの値比較は常に equals() を使うこと。
試験で出た声
「Integer a = 127; Integer b = 127; a == bは true か?」
「Integer a = 128; Integer b = 128; a == bは false になるがequalsは true」
「境界値 127 と 128 の違いを問う問題が必ず出る感じがする」
プリミティブとの == 比較は例外:
Integer obj = 200;
int prim = 200;
System.out.println(obj == prim); // true!(アンボクシングされてプリミティブ同士の比較になる)
片方がプリミティブの場合、ラッパーは自動でアンボクシングされる。この場合は値比較になるので true。
ラッパークラスの便利メソッド
// 最大値・最小値(試験頻出)
System.out.println(Integer.MAX_VALUE); // 2147483647(約21億)
System.out.println(Integer.MIN_VALUE); // -2147483648
System.out.println(Long.MAX_VALUE); // 9223372036854775807(約922京)
// 文字列 ↔ 数値変換
int n = Integer.parseInt("42"); // "42" → 42
String s = Integer.toString(42); // 42 → "42"
String s2 = String.valueOf(42); // 同様。null 安全(引数が null でも "null" を返す)
// 基数変換
System.out.println(Integer.toBinaryString(10)); // "1010"(2進数)
System.out.println(Integer.toHexString(255)); // "ff"(16進数)
System.out.println(Integer.toOctalString(8)); // "10"(8進数)
// 比較ユーティリティ
System.out.println(Integer.max(3, 7)); // 7
System.out.println(Integer.min(3, 7)); // 3
System.out.println(Integer.compare(3, 7)); // 負の値(3 < 7)
System.out.println(Integer.sum(3, 7)); // 10
✏️ 練習問題
次のコードの出力をすべて答えよ。
Integer a = 127;
Integer b = 127;
Integer c = 128;
Integer d = 128;
Integer e = -128;
Integer f = -128;
Integer g = -129;
Integer h = -129;
System.out.println(a == b); // 1
System.out.println(c == d); // 2
System.out.println(e == f); // 3
System.out.println(g == h); // 4
System.out.println(c.equals(d)); // 5
答え
true // 1: 127 はキャッシュ範囲内
false // 2: 128 はキャッシュ範囲外(-128〜127 のみ)
true // 3: -128 はキャッシュ範囲内(下限ちょうど)
false // 4: -129 はキャッシュ範囲外(下限を1つ超える)
true // 5: equals は値比較
キャッシュ範囲は -128 以上 127 以下(両端を含む)。
1-6. String ─ 不変性・プール・メソッド完全解説
String の最大の特徴:不変(Immutable)
String オブジェクトは一度作ると中身を絶対に変更できない。これを「不変(immutable)」という。
「変更しているように見える」メソッド(toUpperCase() や replace() など)は、実際には新しい String オブジェクトを作って返す。元のオブジェクトは一切変わらない。
String s = "hello";
s.toUpperCase(); // 新しい String を作って返すが……捨てている
s.replace("h", "H"); // これも同様
s.concat(" world"); // これも同様
System.out.println(s); // "hello"(まったく変わっていない!)
これは Silver 試験で最も頻繁に出る String の罠だ。「このコードの出力は?」という問題で、メソッドを呼んでいるのに元の変数の値を聞いてくる。
String s = "hello";
String upper = s.toUpperCase(); // 返り値を受け取る!
System.out.println(s); // "hello"(変わっていない)
System.out.println(upper); // "HELLO"(新しい String)
なぜ String は不変なのか?
- スレッドセーフ: 変更できないので複数スレッドで安全に共有できる
- セキュリティ: パスワードや URL を途中で書き換えられない
- 文字列プール(後述): 同じ文字列を共有するためには不変でなければならない
- hashCode のキャッシュ:
HashMapのキーとして効率よく使える
文字列プール(String Pool)
文字列リテラル("hello" のようにダブルクォートで囲んだもの)は、JVM がヒープ内の文字列プールという専用エリアに格納する。同じ内容のリテラルは1つのオブジェクトだけ作られ、共有される。
String s1 = "hello"; // プールに "hello" を作成
String s2 = "hello"; // プールの既存オブジェクトを再利用
String s3 = new String("hello"); // ヒープに「新しい」オブジェクトを強制生成(プール外)
System.out.println(s1 == s2); // true(同じプールオブジェクト)
System.out.println(s1 == s3); // false(s3 はヒープ上の別オブジェクト)
System.out.println(s1.equals(s3)); // true(内容は同じ)
== は「参照が同じか(同一オブジェクトか)」の比較。equals() は「内容が同じか」の比較。文字列の比較では必ず equals() を使う。== で true になるのはプールで同じオブジェクトを指しているときだけで、コードの書き方次第で変わってしまう。
new String() はプールを使わない
// NG パターン(意図的に別オブジェクトを作りたい場合を除いて使う理由がない)
String s = new String("hello");
// こう書けばよい
String s = "hello";
new String("hello") は引数に渡した文字列リテラルとは別のオブジェクトを作る。余分なメモリを使うだけなので、通常は書かない。
intern() メソッド
new String() で作ったオブジェクトをプールに登録(または既存のプール上のオブジェクトを取得)するメソッド。
String s1 = "hello";
String s2 = new String("hello").intern(); // プールの "hello" を返す
System.out.println(s1 == s2); // true(同じプールオブジェクト)
試験では intern() の存在と動作を確認する問題が出ることがある。
+ 演算子による連結の挙動(左から右に評価される)
文字列の + は左から順番に評価される。途中で String が登場すると、それ以降はすべて String への変換が行われる。
System.out.println(1 + 2 + "abc"); // "3abc"
// 左から: 1+2=3 (int計算) → 3+"abc"="3abc"
System.out.println("abc" + 1 + 2); // "abc12"
// 左から: "abc"+1="abc1" → "abc1"+2="abc12"
System.out.println("abc" + (1 + 2)); // "abc3"
// 括弧で先に計算: 1+2=3 → "abc"+3="abc3"
System.out.println(1 + 2.0 + "x"); // "3.0x"
// 1+2.0=3.0 (double) → 3.0+"x"="3.0x"
// null との連結(NPE にならず "null" 文字列になる)
String s = null;
System.out.println("値: " + s); // "値: null"
System.out.println(s + "end"); // "nullend"
試験でよく出る問題形式
System.out.println("result: " + 2 + 3);の出力は?
→"result: 23"(+は左から。"result: " + 2 = "result: 2"→"result: 2" + 3 = "result: 23")
String の主要メソッド ─ 完全整理
String s = "Hello, World!";
// 0123456789012
// インデックスは0始まり
// ─── 長さ・文字取得 ───
s.length() // 13(String はメソッド呼び出し: length())
s.charAt(0) // 'H'(インデックス0の文字)
s.charAt(7) // 'W'
// ─── 部分文字列 ───
s.substring(7) // "World!"(7以降すべて)
s.substring(7, 12) // "World"(7以上12未満)
// 覚え方: substring(begin, end) の end は「含まない」
// ─── 検索 ───
s.indexOf('o') // 4(最初の 'o' の位置)
s.indexOf('o', 5) // 8(インデックス5以降で検索)
s.lastIndexOf('o') // 8(最後の 'o' の位置)
s.indexOf("xyz") // -1(見つからない場合 -1)
s.contains("World") // true
s.startsWith("Hello") // true
s.endsWith("!") // true
// ─── 変換 ───
s.toLowerCase() // "hello, world!"
s.toUpperCase() // "HELLO, WORLD!"
s.trim() // 前後の半角空白を除去(制御文字含む)
s.strip() // 前後の空白を除去(Unicode 全角スペースも含む。Java 11+)
s.replace('l', 'L') // "HeLLo, WorLd!"(文字単位の置換。すべての出現箇所)
s.replace("World", "Java") // "Hello, Java!"(文字列単位の置換)
s.replaceAll("[aeiou]", "*") // 正規表現で置換
s.replaceFirst("l", "L") // 最初の1箇所だけ置換
// ─── 判定 ───
"".isEmpty() // true(length() == 0 か)
" ".isEmpty() // false(スペースは文字なので空でない)
"".isBlank() // true(空白文字のみかまたは空か。Java 11+)
" \t\n".isBlank() // true
s.equals("Hello, World!") // true(内容比較)
s.equalsIgnoreCase("hello, world!") // true(大文字小文字を無視して比較)
// ─── 分割・結合 ───
"a,b,c".split(",") // ["a", "b", "c"]
"a".split(",") // ["a"](区切り文字がなければ要素1つの配列)
String.join("-", "a", "b", "c") // "a-b-c"
String.join(", ", List.of("x", "y")) // "x, y"
// ─── その他 ───
s.toCharArray() // char[] {'H','e','l','l','o',',', ...}
String.valueOf(42) // "42"(様々な型を String に変換)
s.formatted("Hello %s", "World") // Java 15+
よく間違える substring の境界
"abcde".substring(1, 3)→"bc"
インデックス1(b)から インデックス3(d)の手前まで。end は含まない。
「begin は含む、end は含まない」。
compareTo の挙動
辞書順(Unicode 順)の比較。
System.out.println("apple".compareTo("banana")); // 負の値(apple < banana)
System.out.println("banana".compareTo("apple")); // 正の値(banana > apple)
System.out.println("apple".compareTo("apple")); // 0(等しい)
System.out.println("A".compareTo("a")); // -32('A'=65, 'a'=97, 65-97=-32)
返り値の「値」が何かは試験ではあまり問われない。「負なら左辺が小さい、正なら左辺が大きい、0なら等しい」を覚えれば十分。
✏️ 練習問題
次のコードの出力をすべて答えよ。
String s = "Java Silver";
System.out.println(s.length()); // 1
System.out.println(s.substring(5)); // 2
System.out.println(s.substring(5, 8)); // 3
System.out.println(s.toUpperCase()); // 4(s の値に注目)
System.out.println(s); // 5
System.out.println(s.replace("Silver", "Gold")); // 6
System.out.println(s.contains("Java")); // 7
System.out.println(s.indexOf("l")); // 8
答え
11 // 1: "Java Silver" は11文字
Silver // 2: インデックス5以降
Sil // 3: インデックス5以上8未満(S=5, i=6, l=7、8は含まない)
JAVA SILVER // 4: 新しい String が返される(s は変わらない)
Java Silver // 5: s は不変。toUpperCase() の返り値を受け取っていない
Java Gold // 6: 新しい String(s は変わらない)
true // 7
8 // 8: "Java Silver" で最初の 'l' はインデックス8(Java_Sil... の l)
インデックスを数える:J(0)a(1)v(2)a(3) (4)S(5)i(6)l(7)v(8)e(9)r(10)
最初の ‘l’ は "Java Silver" の中では l の位置 → インデックス 8
1-7. StringBuilder ─ 可変な文字列バッファ
なぜ StringBuilder が必要か
String は不変なので、+ で連結するたびに新しいオブジェクトが作られる。
// ❌ 非効率:ループ1万回でオブジェクトが1万個生成される
String s = "";
for (int i = 0; i < 10000; i++) {
s += i; // s + i の結果が新しい String。元の s は捨てられる
}
// 内部では: s = new String(s + i) が毎回実行されている
StringBuilder は内部に char 配列(バッファ)を持ち、そのバッファを直接書き換える可変な文字列クラス。オブジェクトを作り直さないので高速。
// ✅ 効率的:同じオブジェクトのバッファに追記する
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10000; i++) {
sb.append(i); // 同じオブジェクトの内部バッファを拡張
}
String result = sb.toString(); // 最後に一度だけ String に変換
StringBuilder の主要メソッド(戻り値はすべて StringBuilder 自身)
StringBuilder のメソッドはすべて自分自身(this)を返す。これによりメソッドチェーンが書ける。
StringBuilder sb = new StringBuilder("Hello");
// ─── 追加 ───
sb.append(", World"); // 末尾に追加 → "Hello, World"
sb.append('!'); // char も追加可 → "Hello, World!"
sb.append(42); // int・double 等も文字列として追加
sb.insert(5, " Java"); // インデックス5に挿入 → "Hello Java, World!"
// ─── 削除 ───
sb.delete(5, 10); // インデックス5以上10未満を削除(5文字分)
sb.deleteCharAt(0); // インデックス0の1文字を削除
// ─── 置換 ───
// ※ String.replace(String, String) とは引数が違う!
sb.replace(0, 5, "Hi"); // インデックス0以上5未満を "Hi" に置換
// ─── その他 ───
sb.reverse(); // 全体を逆順にする
sb.length(); // 現在の文字数
sb.charAt(0); // 指定インデックスの文字
sb.indexOf("World"); // 部分文字列の検索
sb.substring(3); // String として部分文字列を返す(sb 自身は変わらない)
sb.toString(); // String に変換
メソッドチェーンの例:
String result = new StringBuilder()
.append("Hello") // "Hello"
.append(", ") // "Hello, "
.append("World") // "Hello, World"
.reverse() // "dlroW ,olleH"
.toString();
System.out.println(result); // "dlroW ,olleH"
String と StringBuilder の replace は引数が違う(超頻出ミス)
String.replace | StringBuilder.replace | |
|---|---|---|
| 引数 | (CharSequence old, CharSequence new) | (int start, int end, String str) |
| 使い方 | s.replace("abc", "xyz") | sb.replace(0, 3, "xyz") |
| 動作 | 文字列を文字列で置換 | 範囲をインデックス指定で置換 |
String s = "hello";
String replaced = s.replace("ell", "ELL"); // "hELLo"(文字列で指定)
StringBuilder sb = new StringBuilder("hello");
sb.replace(1, 4, "ELL"); // インデックス1以上4未満を置換 → "hELLo"
delete と deleteCharAt
StringBuilder sb = new StringBuilder("ABCDE");
sb.delete(1, 3); // インデックス1以上3未満(B,C)を削除 → "ADE"
sb.deleteCharAt(0); // インデックス0(A)を削除 → "DE"
delete(begin, end) の end は「含まない」(String の substring と同じルール)。
String vs StringBuilder vs StringBuffer
String | StringBuilder | StringBuffer | |
|---|---|---|---|
| 変更可能か | 不変 | 可変 | 可変 |
| スレッドセーフか | ○(変更できないので) | ✗ | ○(同期化あり) |
| 速度 | 遅(連結が多い場合) | 速い | 遅め(同期のオーバーヘッド) |
| 推奨場面 | 通常の文字列 | 単スレッドでの大量操作 | マルチスレッド環境 |
Silver 試験では StringBuffer は「スレッドセーフな StringBuilder」という知識だけあればよい。使い方は StringBuilder とほぼ同じ。
✏️ 練習問題
次のコードの出力を答えよ。
StringBuilder sb = new StringBuilder("ABCDE");
sb.delete(1, 3);
System.out.println(sb); // 1
sb.replace(1, 2, "XYZ");
System.out.println(sb); // 2
sb.reverse();
System.out.println(sb); // 3
System.out.println(sb.length()); // 4
答え
delete(1, 3): インデックス 1 以上 3 未満(B=1, C=2)を削除 →"ADE"replace(1, 2, "XYZ"): インデックス 1 以上 2 未満(D=1 の 1 文字)を"XYZ"に置換 →"AXYZE"reverse():"AXYZE"を逆順にする →"EZYXA"length():"EZYXA"は 5 文字 →5
ADE
AXYZE
EZYXA
5
1-8. var ─ ローカル変数の型推論(Java 10+)
var を使うと、ローカル変数の型宣言を省略してコンパイラに推論させられる。
var x = 42; // int と推論
var s = "hello"; // String と推論
var d = 3.14; // double と推論
var list = new ArrayList<String>(); // ArrayList<String> と推論
var が使える場所・使えない場所:
// ✅ 使える場所
void method() {
var x = 10; // ローカル変数(初期化あり)
for (var item : list) { } // 拡張 for 文の変数
try (var br = new BufferedReader(...)) { } // try-with-resources
}
// ❌ 使えない場所
class Sample {
var field = 10; // ❌ フィールド
}
void method(var x) { } // ❌ メソッド引数
var method() { } // ❌ 戻り値の型
void method() {
var v; // ❌ 初期化なし(型が推論できない)
var n = null; // ❌ null のみでは型が推論できない
var a = {1, 2, 3}; // ❌ 配列初期化リスト単体では不可
}
varはあくまで「書く手間を省く」だけ。Java は依然として静的型付け言語。varを使っても型は変わらない。一度intとして推論された変数に後からStringを代入はできない。
var x = 10; // int として推論
x = "hello"; // ❌ コンパイルエラー(int に String は代入不可)
Chapter 01 まとめチェックリスト
- プリミティブ型は8種。それ以外はすべて参照型
-
longは末尾にL、floatは末尾にfが必要 -
float f = 3.14;→ コンパイルエラー(3.14 は double) - ローカル変数にはデフォルト値なし。初期化しないとコンパイルエラー
- フィールドは自動でデフォルト値が設定される(int: 0, boolean: false, 参照型: null)
-
byte + byteの結果はint(型プロモーション) -
byte x; x = x + 1;→ エラー。x += 1;→ OK(複合代入には暗黙キャスト) - キャストの切り捨て:
(int) 3.99→3(四捨五入ではない) - 縮小変換でオーバーフローしてもエラーにならない(値が壊れるだけ)
-
Integerの==はキャッシュ範囲 -128〜127 内のみtrue -
nullをアンボクシングするとNullPointerException -
Stringは不変。メソッドは新しい String を返す(元は変わらない) - String の内容比較は
equals()。==は参照比較 -
new String("hello")はプールを使わない -
"1" + 2 + 3→"123"、1 + 2 + "3"→"33"(左から評価) -
nullとの+連結は例外にならず"null"という文字列になる -
StringBuilderは可変。メソッドはthisを返すのでチェーン可能 -
StringBuilder.replace(int, int, String)は範囲指定(String.replace(String, String)と違う) -
varはローカル変数のみ。フィールド・引数・戻り値・null初期化はコンパイルエラー