Java Silver SE17のString・StringBuilder問題10選|equals・==・文字列の不変性を解説

Java Silver SE17のString・StringBuilder問題10選を猫とコード画面で紹介するアイキャッチ画像

Java Silver SE17では、StringStringBuilderの実行結果を問うコード問題がよく登場します。

特に間違えやすいのが、==equals()の違い、Stringプール、文字列の不変性、substring()の範囲、StringBuilderの変更系メソッドです。

この記事では、Java Silver対策として押さえておきたいString・StringBuilder問題を10問用意しました。最初にコードを見て実行結果を予想し、そのあとに「答えと解説を見る」を開いて確認してください。

先に答えを見ず、自分で「参照を比較しているのか」「文字列の内容を比較しているのか」「戻り値を代入しているのか」を確認するのがポイントです。

Java Silver SE17の無料問題を解く

目次

Java SilverのString・StringBuilderで間違えやすい5つのポイント

1.==とequals()を同じ比較だと思ってしまう

参照型に対する==は、原則として2つの変数が同じオブジェクトを参照しているかを比較します。一方、Stringクラスのequals()は文字列の内容を比較します。

2.文字列リテラルはStringプールで共有される

"Java"のような文字列リテラルはStringプールで管理されます。同じ内容のリテラルは、同じStringオブジェクトを参照することがあります。

ただし、new String("Java")は新しいオブジェクトを生成するため、リテラルと==で比較すると通常はfalseです。

3.Stringのメソッドが元の文字列を書き換えると思ってしまう

Stringは不変(immutable)です。concat()replace()trim()などを呼び出しても、元のStringオブジェクトは変わりません。変更結果を使うには、戻り値を変数へ代入する必要があります。

4.substring()の終了位置を含めてしまう

substring(beginIndex, endIndex)では、開始位置は含み、終了位置は含みません。「begin以上、end未満」と覚えましょう。

5.StringBuilderも不変だと考えてしまう

StringBuilderは可変(mutable)です。append()insert()delete()reverse()などを呼び出すと、原則として同じオブジェクトの内容が変更されます。

Java Silver SE17のString・StringBuilder問題10選

問題1:文字列リテラルとnew String()

public class Main {
    public static void main(String[] args) {
        String a = "Java";
        String b = new String("Java");

        System.out.print(a == b);
        System.out.print(":" + a.equals(b));
    }
}

選択肢:

  1. true:true
  2. true:false
  3. false:true
  4. false:false
答えと解説を見る

正解は3.false:trueです。

aはStringプールにある文字列リテラルを参照します。一方、new String("Java")は新しいStringオブジェクトを生成します。

そのため、参照先が同じかを調べる==falseです。文字列の内容を比較するequals()は、どちらも「Java」なのでtrueになります。

問題2:コンパイル時に連結される文字列

public class Main {
    public static void main(String[] args) {
        String a = "Java";
        String b = "Ja" + "va";

        System.out.print(a == b);
        System.out.print(":" + a.equals(b));
    }
}

選択肢:

  1. true:true
  2. true:false
  3. false:true
  4. コンパイルエラー
答えと解説を見る

正解は1.true:trueです。

"Ja" + "va"は、どちらもコンパイル時に値が決まる文字列リテラルです。そのため、コンパイラによって"Java"としてまとめられます。

abはStringプール内の同じ文字列を参照するため、==equals()trueです。

ただし、連結部分に実行時に値が決まる変数が含まれると、同じ結果になるとは限りません。「リテラル同士の定数式か」を確認しましょう。

問題3:Stringプールとintern()

public class Main {
    public static void main(String[] args) {
        String a = new String("Silver");
        String b = a.intern();
        String c = "Silver";

        System.out.print(a == c);
        System.out.print(":" + (b == c));
    }
}

選択肢:

  1. true:true
  2. true:false
  3. false:true
  4. false:false
答えと解説を見る

正解は3.false:trueです。

anewによって生成された別のStringオブジェクトを参照するため、リテラルを参照するcとは==で一致しません。

intern()は、Stringプールにある同じ内容の文字列への参照を返します。そのため、bc==で比較した結果はtrueです。

問題4:Stringの不変性

public class Main {
    public static void main(String[] args) {
        String text = "Java";
        text.toUpperCase();
        text.replace("J", "L");

        System.out.print(text);
    }
}

選択肢:

  1. JAVA
  2. Lava
  3. LAVA
  4. Java
答えと解説を見る

正解は4.Javaです。

Stringは不変なので、toUpperCase()replace()も元の文字列を書き換えません。各メソッドは処理結果を表す新しいStringを返しますが、このコードでは戻り値を受け取っていません。

変更結果を使うなら、text = text.toUpperCase();のように再代入する必要があります。

問題5:concat()の戻り値

public class Main {
    public static void main(String[] args) {
        String a = "Java";
        String b = a.concat("Silver");

        System.out.print(a);
        System.out.print(":" + b);
    }
}

選択肢:

  1. Java:JavaSilver
  2. JavaSilver:JavaSilver
  3. Java:Silver
  4. コンパイルエラー
答えと解説を見る

正解は1.Java:JavaSilverです。

concat("Silver")は「Java」と「Silver」を連結した新しいStringを返します。その戻り値を受け取ったbは「JavaSilver」です。

元のStringであるaは変更されないため、引き続き「Java」です。「メソッドを呼んだか」だけでなく、「戻り値をどこへ代入したか」を追ってください。

問題6:substring()の開始位置と終了位置

public class Main {
    public static void main(String[] args) {
        String text = "Silver";
        System.out.print(text.substring(1, 4));
    }
}

選択肢:

  1. Sil
  2. ilv
  3. ilve
  4. lve
答えと解説を見る

正解は2.ilvです。

文字列「Silver」のインデックスは、S=0、i=1、l=2、v=3、e=4、r=5です。

substring(1, 4)はインデックス1を含み、インデックス4を含みません。そのため、1〜3番目のilvが返されます。

2つの引数を取るsubstring()は「開始位置以上、終了位置未満」です。

問題7:trim()とreplace()の連続呼び出し

public class Main {
    public static void main(String[] args) {
        String a = " Java ";
        String b = a.trim().replace('a', 'X');

        System.out.print(a.length());
        System.out.print(":" + b);
    }
}

選択肢:

  1. 4:JXvX
  2. 6:JXvX
  3. 6:Java
  4. 4:Java
答えと解説を見る

正解は2.6:JXvXです。

aには前後の半角スペースも含まれるため、長さは6です。Stringは不変なので、trim()を呼んでもa自体は変わりません。

trim()が返した「Java」に対してreplace('a', 'X')が実行され、その結果「JXvX」がbへ代入されます。

メソッドチェーンでは、左側のメソッドが返したオブジェクトに対して、次のメソッドが呼び出されます。

問題8:StringBuilderのappend()とinsert()

public class Main {
    public static void main(String[] args) {
        StringBuilder sb = new StringBuilder("Java");
        sb.append("17").insert(4, "SE");

        System.out.print(sb);
    }
}

選択肢:

  1. Java17SE
  2. JavaSE17
  3. SEJava17
  4. コンパイルエラー
答えと解説を見る

正解は2.JavaSE17です。

まずappend("17")によって内容は「Java17」になります。

次のinsert(4, "SE")は、インデックス4の位置へ「SE」を挿入します。「Java」の直後で「17」の直前に挿入されるため、結果は「JavaSE17」です。

StringBuilderの変更系メソッドは同じStringBuilderを返すため、このように続けて呼び出せます。

問題9:StringBuilderのdelete()とreverse()

public class Main {
    public static void main(String[] args) {
        StringBuilder sb = new StringBuilder("abcdef");
        sb.delete(1, 4).reverse();

        System.out.print(sb);
    }
}

選択肢:

  1. fea
  2. feba
  3. aef
  4. fedcba
答えと解説を見る

正解は1.feaです。

delete(1, 4)では、インデックス1以上4未満の文字、つまりbcdが削除されます。この時点で内容は「aef」です。

続いてreverse()が実行されるため、「aef」が逆順になり、最終結果は「fea」です。

delete()substring()と同様に、開始位置を含み、終了位置を含みません。

問題10:StringとStringBuilderの代入

public class Main {
    public static void main(String[] args) {
        String text = new StringBuilder("Java");
        System.out.print(text);
    }
}

選択肢:

  1. Java
  2. StringBuilder
  3. 実行時に例外が発生する
  4. コンパイルエラー
答えと解説を見る

正解は4.コンパイルエラーです。

StringとStringBuilderは別のクラスであり、継承関係もありません。String型の変数へStringBuilderオブジェクトをそのまま代入することはできません。

Stringへ変換するには、toString()を呼び出します。

String text = new StringBuilder("Java").toString();

この形ならStringBuilderが表す文字列をStringとして受け取れるため、正常にコンパイルできます。

==とequals()の違いを問題で見抜く方法

Stringの比較問題では、まず比較演算子を確認します。

比較方法確認するもの主な判断材料
==同じオブジェクトを参照しているかリテラル、newintern()、定数式
equals()文字列の内容が同じか大文字・小文字、空白、文字数、並び

次に、文字列がどのように作られたかを確認します。

  • 同じ文字列リテラル同士:Stringプールで共有される可能性がある
  • new String():新しいオブジェクトを生成する
  • リテラルだけの定数式:コンパイル時に連結される
  • intern()の戻り値:Stringプール内の文字列を参照する

実務では文字列の内容を比較するとき、基本的にequals()を使います。試験ではあえて==を使い、Stringプールや参照の理解を確認する問題が出されます。

Stringの不変性を問題で見抜く方法

Stringのメソッドを見つけたら、戻り値が利用されているか確認してください。

String text = "java";
text.toUpperCase();
System.out.println(text);

このコードの結果はjavaです。toUpperCase()が返す新しいStringを受け取っていないからです。

String text = "java";
text = text.toUpperCase();
System.out.println(text);

こちらは戻り値を再代入しているため、結果はJAVAです。

次のメソッドが登場したら、特に代入先を確認しましょう。

  • concat()
  • replace()
  • substring()
  • trim()
  • toUpperCase()toLowerCase()

StringBuilderの実行結果を追う方法

StringBuilderでは、処理のたびに現在の内容を書き出すと間違いにくくなります。

メソッド動作範囲の注意点
append(value)末尾に追加現在の長さの後ろへ追加
insert(index, value)指定位置に挿入指定インデックスの文字の前へ挿入
delete(start, end)範囲を削除start以上、end未満
deleteCharAt(index)1文字削除指定位置の文字を削除
reverse()文字順を反転変更後の全体を逆順にする
setCharAt(index, ch)1文字置換戻り値はvoid

たとえば、append()insert()が連続している場合、最後の状態だけを頭の中で考えると混乱します。

StringBuilder sb = new StringBuilder("Java"); // Java
sb.append("17");                              // Java17
sb.insert(4, "SE");                           // JavaSE17

このように1行ずつ内容を更新すると、複雑な問題でも正確に追えます。

StringとStringBuilderの違い

比較項目StringStringBuilder
変更可能か不変可変
変更系メソッド新しいStringを返す自身の内容を変更するものが多い
内容の比較equals()で内容を比較equals()は内容比較用にオーバーライドされていない
Stringへの変換変換不要toString()を使う
向いている処理変更が少ない文字列文字列を何度も編集・連結する処理

StringBuilderのequals()についても注意が必要です。Stringのような内容比較にはならないため、2つのStringBuilderの文字列内容を比較したい場合は、Stringへ変換して比較します。

sb1.toString().equals(sb2.toString())

Java SilverのString問題を解く5ステップ

  1. 変数の型がStringかStringBuilderか確認する
  2. ==equals()か確認する
  3. リテラル、newintern()の有無を確認する
  4. Stringメソッドの戻り値が代入されているか確認する
  5. StringBuilderは1メソッドごとに変更後の内容を書き出す

選択肢に「コンパイルエラー」がある場合は、StringとStringBuilderを直接代入していないか、メソッドの引数や戻り値の型が正しいかも確認してください。

10問の正答一覧

問題正解学習ポイント
1false:true==は参照、equals()は内容を比較
2true:trueリテラルだけの定数式はコンパイル時に連結
3false:trueintern()はプール内の参照を返す
4JavaStringは不変
5Java:JavaSilverconcat()は新しいStringを返す
6ilv終了インデックスは含まない
76:JXvX元のStringとメソッドチェーンの戻り値を分ける
8JavaSE17append()後にインデックス4へ挿入
9feadelete()後にreverse()
10コンパイルエラーStringBuilderはStringへ直接代入できない

まとめ:Stringは戻り値、StringBuilderは変更後の状態を追おう

Java Silver SE17のString・StringBuilder問題では、次のポイントが重要です。

  • ==は参照、Stringのequals()は内容を比較する
  • 同じ文字列リテラルはStringプールで共有される
  • new String()は新しいオブジェクトを生成する
  • Stringは不変なので、メソッドの戻り値を追う
  • substring()delete()の終了位置は含まない
  • StringBuilderは可変なので、処理ごとの内容を追う
  • StringBuilderをStringにするにはtoString()を使う

ルールを読んで理解しただけでは、少し形を変えられた問題で迷ってしまいます。コードを見て実行結果を予想する練習を繰り返し、判断手順を身につけましょう。

当サイトでは、Java Silver SE17対応のコード読解問題と模擬試験を無料で公開しています。登録不要で利用できるので、String以外の分野も続けて確認してみてください。

Java Silver SE17の無料問題を解く

例外処理も復習したい方は、Java Silver SE17の例外処理問題10選もあわせてご覧ください。

参考資料

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

tibiyaのアバター tibiya ITエンジニア

文系卒エンジニア(8年目)
Java / JavaScript / C# を中心に業務システム開発を経験。
Java資格・IT就活・文系エンジニアの実体験を発信しています。

コメント

コメントする

目次