初級 75分 Lesson 1

データ型・ラッパークラス・String・StringBuilder

プリミティブ型8種・型プロモーション・キャスト・ラッパークラスのキャッシュ・String不変性とプール・StringBuilderを完全解説

Java Java Silver SE21 型変換 String

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 など)。

ビット数範囲 / 特徴フィールドのデフォルト値
byte8 bit-128 〜 1270
short16 bit-32,768 〜 32,7670
int32 bit約 ±21億(-2,147,483,648 〜 2,147,483,647)0
long64 bit約 ±922京0L
float32 bit単精度浮動小数点(約7桁)0.0f
double64 bit倍精度浮動小数点(約15桁)。小数リテラルのデフォルト0.0
char16 bitUnicode 1文字(0〜65535)。シングルクォート'\u0000'
booleantrue / 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 が入る)

豆知識: intfloat は拡大変換でも「精度が失われる」場合がある。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つ

  1. (int) 3.993切り捨て(floor でも round でもない、truncate)
  2. 縮小変換でオーバーフローしても実行時エラーが起きない。値が壊れるだけ。

キャストの優先順位

キャストの (型名) がどの範囲に効くかで結果が変わる。

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 / ba だけをキャストするが、(int)(a / b) は式全体の結果をキャストする。


1-4. 型プロモーション ─ 試験最頻出の落とし穴

型プロモーション(型昇格): 式の中で byteshortchar を演算すると、計算前に自動的に int に昇格するという仕様。

byte a = 10;
byte b = 20;
byte c = a + b;    // ❌ コンパイルエラー!

「byte 同士を足したんだから byte じゃないの?」と思うかもしれないが、Java の仕様では byte + byteint になる。なぜなら、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 + longlong
intlong + floatfloat
何か + doubledouble

ルールは「演算に出てくる最も大きい型に揃える。ただし 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.14double 型リテラル。double → float は縮小変換 → エラー3.14f(float)3.14 が必要)
  • 行8: a + 1byte + intint に昇格。int → byte の縮小変換は自動では行われない → エラー
  • 行9: 127 + 1 はコンパイル時定数 128。これは byte の最大値 127 を超えているためコンパイラが検出 → エラー

エラーになるのは行7・行8・行9


1-5. ラッパークラス ─ プリミティブのオブジェクト版

プリミティブ型は「オブジェクトではない」ため、コレクション(List<int> のような書き方)に直接入れられない。null を持てない。また、メソッドを呼べない(42.toString() は書けない)。

そこで各プリミティブ型に対応するラッパークラス(オブジェクト版の型)が用意されている。

プリミティブ型ラッパークラス注意
byteByte
shortShort
intIntegerInt ではない!
longLong
floatFloat
doubleDouble
charCharacterChar ではない!
booleanBoolean

intIntegercharCharacter はミスが多い。大文字で始まる完全な英単語になっているものが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 は不変なのか?

  1. スレッドセーフ: 変更できないので複数スレッドで安全に共有できる
  2. セキュリティ: パスワードや URL を途中で書き換えられない
  3. 文字列プール(後述): 同じ文字列を共有するためには不変でなければならない
  4. 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.replaceStringBuilder.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

StringStringBuilderStringBuffer
変更可能か不変可変可変
スレッドセーフか○(変更できないので)○(同期化あり)
速度遅(連結が多い場合)速い遅め(同期のオーバーヘッド)
推奨場面通常の文字列単スレッドでの大量操作マルチスレッド環境

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
答え
  1. delete(1, 3): インデックス 1 以上 3 未満(B=1, C=2)を削除 → "ADE"
  2. replace(1, 2, "XYZ"): インデックス 1 以上 2 未満(D=1 の 1 文字)を "XYZ" に置換 → "AXYZE"
  3. reverse(): "AXYZE" を逆順にする → "EZYXA"
  4. 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 は末尾に Lfloat は末尾に 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.993(四捨五入ではない)
  • 縮小変換でオーバーフローしてもエラーにならない(値が壊れるだけ)
  • 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初期化はコンパイルエラー